From 75297ac974cf93e8615d2d093715a260fa5cef4d Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Fri, 13 Oct 2023 22:30:47 -0500 Subject: [PATCH 01/69] Refactor collision methods --- hoomd/RNGIdentifiers.h | 1 + hoomd/mpcd/ATCollisionMethod.cc | 4 +- hoomd/mpcd/ATCollisionMethod.h | 8 +- hoomd/mpcd/CellList.cc | 47 +- hoomd/mpcd/CellList.h | 39 +- hoomd/mpcd/CellListGPU.cc | 7 +- hoomd/mpcd/CellListGPU.h | 2 +- hoomd/mpcd/CollisionMethod.cc | 59 +-- hoomd/mpcd/CollisionMethod.h | 32 +- hoomd/mpcd/ConfinedStreamingMethodGPU.h | 5 + hoomd/mpcd/Integrator.cc | 35 +- hoomd/mpcd/Integrator.h | 19 +- hoomd/mpcd/SRDCollisionMethod.cc | 25 +- hoomd/mpcd/SRDCollisionMethod.h | 18 +- hoomd/mpcd/SRDCollisionMethodGPU.cc | 17 +- hoomd/mpcd/SRDCollisionMethodGPU.h | 2 +- hoomd/mpcd/collide.py | 463 ++++++------------ hoomd/mpcd/test/at_collision_method_test.cc | 6 +- hoomd/mpcd/test/cell_communicator_mpi_test.cc | 4 +- hoomd/mpcd/test/cell_list_mpi_test.cc | 6 +- hoomd/mpcd/test/cell_list_test.cc | 8 +- .../mpcd/test/cell_thermo_compute_mpi_test.cc | 2 +- hoomd/mpcd/test/cell_thermo_compute_test.cc | 4 +- hoomd/mpcd/test/communicator_mpi_test.cc | 8 +- .../test/slit_geometry_filler_mpi_test.cc | 2 +- hoomd/mpcd/test/slit_geometry_filler_test.cc | 3 +- .../slit_pore_geometry_filler_mpi_test.cc | 3 +- .../test/slit_pore_geometry_filler_test.cc | 3 +- hoomd/mpcd/test/sorter_test.cc | 4 +- hoomd/mpcd/test/srd_collision_method_test.cc | 23 +- hoomd/mpcd/test/streaming_method_test.cc | 2 +- 31 files changed, 386 insertions(+), 475 deletions(-) diff --git a/hoomd/RNGIdentifiers.h b/hoomd/RNGIdentifiers.h index 6de0a44e15..4961bb16e5 100644 --- a/hoomd/RNGIdentifiers.h +++ b/hoomd/RNGIdentifiers.h @@ -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/mpcd/ATCollisionMethod.cc b/hoomd/mpcd/ATCollisionMethod.cc index 8c7b22c2a7..da51f4dae6 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 2c853c2265..9ad4378401 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/CellList.cc b/hoomd/mpcd/CellList.cc index 515287563c..00ee49c427 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, double, 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 033eb68ad2..45668a2f9e 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; @@ -243,8 +265,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 4b646e1f13..63d495eed0 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, double, bool>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/CellListGPU.h b/hoomd/mpcd/CellListGPU.h index 5abb290cf2..8e2066fc79 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 8064990517..3f8674384e 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,13 @@ void mpcd::detail::export_CollisionMethod(pybind11::module& m) m, "CollisionMethod") .def(pybind11::init, uint64_t, uint64_t, int>()) - .def("enableGridShifting", &mpcd::CollisionMethod::enableGridShifting) - .def("setEmbeddedGroup", &mpcd::CollisionMethod::setEmbeddedGroup) - .def("setPeriod", &mpcd::CollisionMethod::setPeriod) - .def_property("instance", - &mpcd::CollisionMethod::getInstance, - &mpcd::CollisionMethod::setInstance); + .def_property_readonly("embed", + [](const std::shared_ptr method) + { + return (method) ? method->getEmbeddedGroup()->getFilter() + : std::shared_ptr(); + }) + .def_property_readonly("every", &mpcd::CollisionMethod::getPeriod); } } // end namespace hoomd diff --git a/hoomd/mpcd/CollisionMethod.h b/hoomd/mpcd/CollisionMethod.h index 3040bd38c3..1f78feadad 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.h b/hoomd/mpcd/ConfinedStreamingMethodGPU.h index f33a1e7ada..586b1e10c6 100644 --- a/hoomd/mpcd/ConfinedStreamingMethodGPU.h +++ b/hoomd/mpcd/ConfinedStreamingMethodGPU.h @@ -66,6 +66,11 @@ template void ConfinedStreamingMethodGPU::stream(uint6 if (!this->shouldStream(timestep)) return; + if (!this->m_cl) + { + throw std::runtime_error("Cell list has not been set"); + } + // 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) diff --git a/hoomd/mpcd/Integrator.cc b/hoomd/mpcd/Integrator.cc index 4086727d7a..ef7eead2a0 100644 --- a/hoomd/mpcd/Integrator.cc +++ b/hoomd/mpcd/Integrator.cc @@ -55,7 +55,6 @@ void mpcd::Integrator::update(uint64_t timestep) if (checkCollide(timestep)) { m_sysdef->getMPCDParticleData()->removeVirtualParticles(); - m_collide->drawGridShift(timestep); } #ifdef ENABLE_MPI @@ -146,10 +145,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 @@ -181,6 +181,33 @@ void mpcd::Integrator::addFiller(std::shared_ptr fi m_fillers.push_back(filler); } +void mpcd::Integrator::syncCellList() + { + if (m_collide) + { + 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); + } + } + /*! * \param m Python module to export to */ diff --git a/hoomd/mpcd/Integrator.h b/hoomd/mpcd/Integrator.h index f4bfdb1c15..61f794cb08 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) @@ -147,6 +160,7 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep } 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 @@ -163,6 +177,9 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep { return (m_collide && m_collide->peekCollide(timestep)); } + + //! Synchronize cell list to integrator dependencies + void syncCellList(); }; namespace detail diff --git a/hoomd/mpcd/SRDCollisionMethod.cc b/hoomd/mpcd/SRDCollisionMethod.cc index bb2be05bfe..0c801d8c7d 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) + double 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, double>()) + .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 47c0f89053..6f2f6176b3 100644 --- a/hoomd/mpcd/SRDCollisionMethod.h +++ b/hoomd/mpcd/SRDCollisionMethod.h @@ -30,14 +30,14 @@ class PYBIND11_EXPORT SRDCollisionMethod : public mpcd::CollisionMethod unsigned int cur_timestep, unsigned int period, int phase, - uint16_t seed); + double angle); //! Destructor virtual ~SRDCollisionMethod(); void setCellList(std::shared_ptr cl); - //! Get the MPCD rotation angle + //! Get the MPCD rotation angles double getRotationAngle() const { return m_angle; @@ -45,7 +45,7 @@ class PYBIND11_EXPORT SRDCollisionMethod : public mpcd::CollisionMethod //! Set the MPCD rotation angle /*! - * \param angle MPCD rotation angle in radians + * \param angle MPCD rotation angle in degrees */ void setRotationAngle(double 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 diff --git a/hoomd/mpcd/SRDCollisionMethodGPU.cc b/hoomd/mpcd/SRDCollisionMethodGPU.cc index a22375803b..63573e1fdd 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, double>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/SRDCollisionMethodGPU.h b/hoomd/mpcd/SRDCollisionMethodGPU.h index c13515ffa7..c4580941ed 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); + double angle); void setCellList(std::shared_ptr cl); diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 0b2b9fc51f..baa3abd89e 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -1,367 +1,226 @@ # Copyright (c) 2009-2023 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 and solvent +properties determine the transport coefficients. """ import hoomd -from hoomd.md import _md +from hoomd.data.parameterdicts import ParameterDict +from hoomd.data.typeconverter import OnlyTypes +from hoomd.mpcd import _mpcd +from hoomd.operation import AutotunedObject -from . import _mpcd -import numpy as np - -class _collision_method(): - """ Base collision method +class CellList(AutotunedObject): + """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 - - 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. - - """ - - 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' - ) - - # 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') - - # 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') - - self.period = period - self.seed = seed - self.group = None - self.shift = True - self.enabled = True - self._cpp = None + cell_size (float): Size of a collision cell. + shift (bool): When True, randomly shift underlying collision cells. - self.enable() + Attributes: + cell_size (float): Edge length of a collision cell. - def embed(self, group): - """ Embed a particle group into the MPCD collision + Each collision cell is a cube. The box must be orthorhombic with + each edge length a multiple of `cell_size`. - Args: - group (``hoomd.group``): Group of particles to embed + shift (bool): When True, randomly shift underlying collision cells. - 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. + When the total mean-free path of the MPCD particles is small, the + underlying MPCD cell list should be randomly shifted in order to + ensure Galilean invariance. The performance penalty from grid + shifting is small, so it is recommended to enable it in all + simulations. - 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. - - Examples:: - - polymer = hoomd.group.type('P') - md.integrate.nve(group=polymer) - method.embed(polymer) - - """ - - self.group = group - self._cpp.setEmbeddedGroup(group.cpp_group) - - 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 + if isinstance(sim.device, hoomd.device.GPU): + cpp_class = _mpcd.CellListGPU + else: + cpp_class = _mpcd.CellList - """ + self._cpp_obj = cpp_class(sim.state._cpp_sys_def, self.cell_size, + self.shift) - self.enabled = True - hoomd.context.current.mpcd._collide = self + super()._attach_hook() - def disable(self): - """ Disable the collision method - Examples:: +class CollisionMethod(AutotunedObject): + """Base collision method. - method.disable() + Args: + every (int): Number of integration steps between collisions. + embed (hoomd.filter.filter_like): HOOMD particles to include in collision. - 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. + Attributes: + embed (hoomd.filter.filter_like): HOOMD particles to include in collision. - """ + These particles are included in per-cell quantities and have their + velocities updated along with the MPCD particles. - self.enabled = False - hoomd.context.current.mpcd._collide = None + You will need to create an appropriate method to integrate the + positions of these particles. The recommended integrator is + `~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. - def set_period(self, period): - """ Set the collision period. + every (int): Number of integration steps between collisions. - Args: - period (int): New collision period. + A collision is executed each time the `~hoomd.Simulation.timestep` + is a multiple of `every`. It must be a multiple of `every` for the + `~hoomd.mpcd.stream.StreamingMethod` if one is attached to the + `~hoomd.mpcd.Integrator`. - 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. + """ - Examples:: + def __init__(self, every, embed=None): + super().__init__() - # The initial period is 5. - # The period can be updated to 2 on step 10. - hoomd.run_upto(10) - method.set_period(period=2) + param_dict = ParameterDict( + every=int(every), + embed=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=True), + ) + param_dict["embed"] = embed + self._param_dict.update(param_dict) - # The period can be updated to 4 on step 12. - hoomd.run_upto(12) - hoomd.set_period(period=4) - """ +class AndersenThermostat(CollisionMethod): + r"""Andersen thermostat method. - 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' - ) + Args: + every (int): Number of integration steps between collisions. kT + (hoomd.variant.variant_like): Temperature of the solvent + :math:`[\mathrm{energy}]`. + embed (hoomd.filter.ParticleFilter): HOOMD particles to include in + collision. - self._cpp.setPeriod(cur_tstep, period) - self.period = period + This class implements the Andersen thermostat collision rule for MPCD, as + described by `Allahyarov and Gompper + `_. Every `every` steps, the + particles are binned into cells. New particle velocities are then randomly + drawn from a Gaussian distribution 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`. -class at(_collision_method): - r""" Andersen thermostat method + Attributes: + kT (hoomd.variant.variant_like): Temperature of the solvent + :math:`[\mathrm{energy}]`. - 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`). - 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. - - 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`. - - Examples:: - - collide.at(seed=42, period=1, kT=1.0) - collide.at(seed=77, period=50, kT=1.5, group=hoomd.group.all()) + This temperature determines the distribution used to generate the + random numbers. """ - def __init__(self, seed, period, kT, group=None): + def __init__(self, every, kT, embed=None): + super().__init__(every, embed) - _collision_method.__init__(self, seed, period) - self.kT = hoomd.variant._setup_variant_input(kT) + param_dict = ParameterDict(kT=hoomd.variant.Variant) + param_dict["kT"] = kT + self._param_dict.update(param_dict) - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - collide_class = _mpcd.ATCollisionMethod - thermo_class = _mpcd.CellThermoCompute + def _attach_hook(self): + sim = self._simulation + if isinstance(sim.device, hoomd.device.GPU): + cpp_class = _mpcd.ATCollisionMethodGPU else: - collide_class = _mpcd.ATCollisionMethodGPU - thermo_class = _mpcd.CellThermoComputeGPU - - # 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 - - 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) - - if group is not None: - self.embed(group) - - def set_params(self, shift=None, kT=None): - """ Set parameters for the SRD collision method - - 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). + cpp_class = _mpcd.ATCollisionMethod - Examples:: + self._cpp_obj = cpp_class(sim.state._cpp_sys_def, sim.timestep, + self.every, 0, self.kT) - 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]])) + if self.embed is not None: + self._cpp_obj.setEmbeddedGroup(sim.state._get_group(self.embed)) - """ + super()._attach_hook() - 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) - -class srd(_collision_method): - r""" Stochastic rotation dynamics method +class StochasticRotationDynamics(CollisionMethod): + r"""Stochastic rotation dynamics 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 + every (int): Number of integration steps between collisions. + angle (float): Rotation angle (in degrees). + kT (hoomd.variant.variant_like): Temperature for the collision + thermostat :`[\mathrm{energy}]`. If None, no thermostat is used. + embed (hoomd.filter.ParticleFilter): HOOMD particles to include in + collision. 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`. + rule for MPCD as first proposed by `Malevanets and Kapral + `_. Every `every` 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) + 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 linear momentum, but kinetic energy is of course no longer + conserved. + + Attributes: + angle (float): Rotation angle (in degrees) + + kT (hoomd.variant.variant_like): Temperature for the collision + thermostat :math:`[\mathrm{energy}]`. """ - def __init__(self, seed, period, angle, kT=False, group=None): + def __init__(self, every, angle, kT=None, embed=None): + super().__init__(every, embed) - _collision_method.__init__(self, seed, period) + param_dict = ParameterDict( + angle=float(angle), + kT=OnlyTypes(hoomd.variant.Variant, allow_none=True), + ) + param_dict["kT"] = kT + self._param_dict.update(param_dict) - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - collide_class = _mpcd.SRDCollisionMethod + def _attach_hook(self): + sim = self._simulation + 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.every, 0, self.angle) + + if self.embed is not None: + self._cpp_obj.setEmbeddedGroup(sim.state._get_group(self.embed)) + + if self.kT is not None: + self._cpp_obj.setTemperature(self.kT) + + super()._attach_hook() diff --git a/hoomd/mpcd/test/at_collision_method_test.cc b/hoomd/mpcd/test/at_collision_method_test.cc index 26f74d9323..2982fc3927 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 4c2588817c..112733d7f0 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 ed046e417e..538caee16d 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 94b0a8c215..a9f3edba67 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 beccd907c2..445d6c1003 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 c4d678a819..e62017eb62 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 cde0e49f7a..364dafec97 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/slit_geometry_filler_mpi_test.cc b/hoomd/mpcd/test/slit_geometry_filler_mpi_test.cc index 4c55e90ee5..2b867be6b1 100644 --- a/hoomd/mpcd/test/slit_geometry_filler_mpi_test.cc +++ b/hoomd/mpcd/test/slit_geometry_filler_mpi_test.cc @@ -33,7 +33,7 @@ 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); diff --git a/hoomd/mpcd/test/slit_geometry_filler_test.cc b/hoomd/mpcd/test/slit_geometry_filler_test.cc index 524663e6c9..aa5a539010 100644 --- a/hoomd/mpcd/test/slit_geometry_filler_test.cc +++ b/hoomd/mpcd/test/slit_geometry_filler_test.cc @@ -25,8 +25,7 @@ template void slit_fill_basic_test(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 diff --git a/hoomd/mpcd/test/slit_pore_geometry_filler_mpi_test.cc b/hoomd/mpcd/test/slit_pore_geometry_filler_mpi_test.cc index 353ad134ae..626bf14b50 100644 --- a/hoomd/mpcd/test/slit_pore_geometry_filler_mpi_test.cc +++ b/hoomd/mpcd/test/slit_pore_geometry_filler_mpi_test.cc @@ -33,8 +33,7 @@ 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); diff --git a/hoomd/mpcd/test/slit_pore_geometry_filler_test.cc b/hoomd/mpcd/test/slit_pore_geometry_filler_test.cc index 5e1a354b4c..c784e04f63 100644 --- a/hoomd/mpcd/test/slit_pore_geometry_filler_test.cc +++ b/hoomd/mpcd/test/slit_pore_geometry_filler_test.cc @@ -25,8 +25,7 @@ template void slit_pore_fill_basic_test(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 diff --git a/hoomd/mpcd/test/sorter_test.cc b/hoomd/mpcd/test/sorter_test.cc index c18f36852f..cba2912af1 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(); @@ -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(); diff --git a/hoomd/mpcd/test/srd_collision_method_test.cc b/hoomd/mpcd/test/srd_collision_method_test.cc index ed619b1fd2..6fa879a2cd 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 629a031ed3..b575b0c8fd 100644 --- a/hoomd/mpcd/test/streaming_method_test.cc +++ b/hoomd/mpcd/test/streaming_method_test.cc @@ -35,7 +35,7 @@ void streaming_method_basic_test(std::shared_ptr exec_co // 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); + 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 From 6ba760dcfa67bbd159cd6360931b8fe173a57487 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Fri, 13 Oct 2023 22:58:10 -0500 Subject: [PATCH 02/69] Refactor streaming methods (no forces & fillers yet) --- hoomd/mpcd/StreamingMethod.cc | 2 +- hoomd/mpcd/StreamingMethod.h | 8 +- hoomd/mpcd/stream.py | 579 +++++++--------------------------- 3 files changed, 128 insertions(+), 461 deletions(-) diff --git a/hoomd/mpcd/StreamingMethod.cc b/hoomd/mpcd/StreamingMethod.cc index d6c9b06984..d12479cf2a 100644 --- a/hoomd/mpcd/StreamingMethod.cc +++ b/hoomd/mpcd/StreamingMethod.cc @@ -120,7 +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_property_readonly("every", &mpcd::StreamingMethod::getPeriod) .def("setField", &mpcd::StreamingMethod::setField) .def("removeField", &mpcd::StreamingMethod::removeField); } diff --git a/hoomd/mpcd/StreamingMethod.h b/hoomd/mpcd/StreamingMethod.h index 92270374ea..6d5aa22d9d 100644 --- a/hoomd/mpcd/StreamingMethod.h +++ b/hoomd/mpcd/StreamingMethod.h @@ -78,7 +78,13 @@ class PYBIND11_EXPORT StreamingMethod : public Autotuned m_field.reset(); } - //! Set the period of the streaming method + //! Get the streaming period + unsigned int getPeriod() const + { + return m_period; + } + + //! Set the streaming period void setPeriod(unsigned int cur_timestep, unsigned int period); //! Set the cell list used for collisions diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index ff6525b12b..fc37a0f72c 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -1,11 +1,11 @@ # Copyright (c) 2009-2023 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 +It is meant to be used in conjunction with an `.mpcd.Integrator` +and `.mpcd.collide.CollisionMethod`. Particle positions are propagated ballistically according to Newton's equations using a velocity-Verlet scheme for a time :math:`\Delta t`: @@ -34,408 +34,154 @@ 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`). + or a bounce-back method. 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. + must initialize your system carefully to ensure all particles are "inside" the + geometry. An error will be raised otherwise. """ import hoomd -from hoomd import _hoomd +from hoomd.data.parameterdicts import ParameterDict +from hoomd.mpcd import _mpcd +from hoomd.operation import AutotunedObject -from . import _mpcd - -class _streaming_method: - """Base streaming method +# TODO: add force and filler +class StreamingMethod(AutotunedObject): + """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. - - Args: - period (int): New streaming period. + every (int): Number of integration steps covered by streaming step. - 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. + Attributes: + every (int): Number of integration steps covered by streaming step. - Examples:: + The MPCD particles with be streamed every time the `~hoomd.Simulation` + timestep is a multiple of `every`. The streaming time is hence equal + to `every` steps of the `~hoomd.mpcd.Integrator`. Typically `every` + should be equal to `~hoomd.mpcd.CollisionMethod.every` 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. - # 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. + def __init__(self, every): + super().__init__() - 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. + param_dict = ParameterDict(every=int(every),) + self._param_dict.update(param_dict) - """ - 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") - -class bulk(_streaming_method): - """Bulk fluid streaming geometry. +class Bulk(StreamingMethod): + """Bulk fluid. Args: - period (int): Number of integration steps between collisions. - - :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. + every (int): Number of integration steps covered by streaming step. - Example for pure MPCD fluid:: - - mpcd.integrator(dt=0.1) - mpcd.collide.srd(seed=42, period=1, angle=130.) - mpcd.stream.bulk(period=1) - - 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) + `Bulk` streams the MPCD particles in a fully periodic geometry (2D or 3D). + This geometry is appropriate for modeling bulk fluids. """ - def __init__(self, period=1): - _streaming_method.__init__(self, period) - - # create the base streaming class - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - stream_class = _mpcd.ConfinedStreamingMethodBulk + def _attach_hook(self): + sim = self._simulation + if isinstance(sim.device, hoomd.device.GPU): + class_ = _mpcd.ConfinedStreamingMethodGPUBulk else: - stream_class = _mpcd.ConfinedStreamingMethodGPUBulk - self._cpp = stream_class( - hoomd.context.current.mpcd.data, - hoomd.context.current.system.getCurrentTimeStep(), - self.period, + class_ = _mpcd.ConfinedStreamingMethodBulk + + self._cpp_obj = class_( + sim.state._cpp_sys_def, + sim.timestep, + self.every, 0, _mpcd.BulkGeometry(), ) + super()._attach_hook() + -class slit(_streaming_method): - r"""Parallel plate (slit) streaming geometry. +class ParallelPlates(StreamingMethod): + r"""Parallel-plate channel. 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 + every (int): Number of integration steps covered by streaming step. + H (float): Channel half-width. + V (float): Wall speed. + no_slip (bool): If True, plates have no-slip boundary condition. + Otherwise, they have the slip boundary condition. - The slit geometry represents a fluid confined between two infinite parallel + `Slit` streams the MPCD particles 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*. + at :math:`z=-H` and :math:`z=+H`, so the total channel width is :math:`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. - The "inside" of the :py:class:`slit` is the space where :math:`|z| < H`. + Attributes: + H (float): Channel half-width. - Examples:: + V (float): Wall speed. - stream.slit(period=10, H=30.) - stream.slit(period=1, H=25., V=0.1) + no_slip (bool): If True, plates have no-slip boundary condition. + Otherwise, they have the slip boundary condition. - .. versionadded:: 2.6 + `V` will have no effect if `no_slip` is False because the slip + surface cannot generate shear stress. """ - def __init__(self, H, V=0.0, boundary="no_slip", period=1): - _streaming_method.__init__(self, period) + def __init__(self, every, H, V=0.0, no_slip=True): + super().__init__(every) - self.H = H - self.V = V - self.boundary = boundary - - bc = self._process_boundary(boundary) + param_dict = ParameterDict( + H=float(H), + V=float(V), + no_slip=bool(no_slip), + ) + self._param_dict.update(param_dict) - # create the base streaming class - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - stream_class = _mpcd.ConfinedStreamingMethodSlit + def _attach_hook(self): + sim = self._simulation + if isinstance(sim.device, hoomd.device.GPU): + class_ = _mpcd.ConfinedStreamingMethodGPUSlit else: - stream_class = _mpcd.ConfinedStreamingMethodGPUSlit - self._cpp = stream_class( - hoomd.context.current.mpcd.data, - hoomd.context.current.system.getCurrentTimeStep(), - self.period, + class_ = _mpcd.ConfinedStreamingMethodSlit + + bc = _mpcd.boundary.no_slip if self.no_slip else _mpcd.boundary.slip + slit = _mpcd.SlitGeometry(self.H, self.V, bc) + self._cpp_obj = class_( + sim.state._cpp_sys_def, + sim.timestep, + self.every, 0, - _mpcd.SlitGeometry(H, V, bc), + slit, ) - def set_filler(self, density, kT, seed, type="A"): - r"""Add virtual particles to slit channel. - - 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 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. - - 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.set_filler(density=5.0, kT=1.0, seed=42) - - .. 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:: - - slit.remove_filler() - - .. versionadded:: 2.6 - - """ - - self._filler = None - - 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 (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.set_params(H=15.0) - slit.set_params(V=0.2, boundary="no_slip") - - .. versionadded:: 2.6 - - """ - - if H is not None: - self.H = H + super()._attach_hook() - 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. +class PlanarPore(StreamingMethod): + r"""Parallel plate pore. 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 + every (int): Number of integration steps covered by streaming step. + H (float): Channel half-width. + L (float): Pore half-length. + no_slip (bool): If True, plates have no-slip boundary condition. + Otherwise, they have the slip boundary condition. + + `PlanarPore` is a finite-length pore 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 *y*. Outside the pore, the simulation box has full periodic boundaries; it is not confined by any walls. This model @@ -443,126 +189,41 @@ class slit_pore(_streaming_method): .. 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`. + Attributes: + H (float): Channel half-width. - Examples:: + L (float): Pore half-length. - stream.slit_pore(period=10, H=30., L=10.) - stream.slit_pore(period=1, H=25., L=25.) - - .. versionadded:: 2.7 + no_slip (bool): If True, plates have no-slip boundary condition. + Otherwise, they have the slip boundary condition. """ - def __init__(self, H, L, boundary="no_slip", period=1): - _streaming_method.__init__(self, period) - - self.H = H - self.L = L - self.boundary = boundary + def __init__(self, trigger, H, L, no_slip=True): + super().__init__(trigger) - bc = self._process_boundary(boundary) + param_dict = ParameterDict( + H=float(H), + L=float(L), + no_slip=bool(no_slip), + ) + self._param_dict.update(param_dict) - # create the base streaming class - if not hoomd.context.current.device.mode == "gpu": - stream_class = _mpcd.ConfinedStreamingMethodSlitPore + def _attach_hook(self): + sim = self._simulation + if isinstance(sim.device, hoomd.device.GPU): + class_ = _mpcd.ConfinedStreamingMethodGPUSlitPore else: - stream_class = _mpcd.ConfinedStreamingMethodGPUSlitPore - self._cpp = stream_class( - hoomd.context.current.mpcd.data, - hoomd.context.current.system.getCurrentTimeStep(), - self.period, + class_ = _mpcd.ConfinedStreamingMethodSlitPore + + bc = _mpcd.boundary.no_slip if self.no_slip else _mpcd.boundary.slip + slit = _mpcd.SlitPoreGeometry(self.H, self.L, bc) + self._cpp_obj = class_( + sim.state._cpp_sys_def, + sim.timestep, + self.every, 0, - _mpcd.SlitPoreGeometry(H, L, bc), + slit, ) - 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 - - if boundary is not None: - self.boundary = boundary - - 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) + super()._attach_hook() From 80cbd99b4d72c4dedfa897760ff4d5d41b88b6ac Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Tue, 17 Oct 2023 10:26:04 -0500 Subject: [PATCH 03/69] Refactor Integrator --- hoomd/md/integrate.py | 39 +++-- hoomd/mpcd/Integrator.cc | 138 ++++++--------- hoomd/mpcd/Integrator.h | 47 ++---- hoomd/mpcd/__init__.py | 157 ----------------- hoomd/mpcd/integrate.py | 351 ++++++++++++++------------------------- 5 files changed, 205 insertions(+), 527 deletions(-) diff --git a/hoomd/md/integrate.py b/hoomd/md/integrate.py index 567858cf5f..9c4ef4d698 100644 --- a/hoomd/md/integrate.py +++ b/hoomd/md/integrate.py @@ -28,15 +28,16 @@ def __init__(self, forces, constraints, methods, rigid): constraints = [] if constraints is None else constraints methods = [] if methods is None else methods self._forces = syncedlist.SyncedList( - Force, syncedlist._PartialGetAttr('_cpp_obj'), iterable=forces) + Force, syncedlist._PartialGetAttr("_cpp_obj"), iterable=forces) self._constraints = syncedlist.SyncedList( OnlyTypes(Constraint, disallow_types=(Rigid,)), - syncedlist._PartialGetAttr('_cpp_obj'), - iterable=constraints) + syncedlist._PartialGetAttr("_cpp_obj"), + iterable=constraints, + ) self._methods = syncedlist.SyncedList( - Method, syncedlist._PartialGetAttr('_cpp_obj'), iterable=methods) + Method, syncedlist._PartialGetAttr("_cpp_obj"), iterable=methods) param_dict = ParameterDict(rigid=OnlyTypes(Rigid, allow_none=True)) if rigid is not None and rigid._attached: @@ -278,15 +279,16 @@ class Integrator(_DynamicIntegrator): perform computations during the half-step of the integration. """ - def __init__(self, - dt, - integrate_rotational_dof=False, - forces=None, - constraints=None, - methods=None, - rigid=None, - half_step_hook=None): - + def __init__( + self, + dt, + integrate_rotational_dof=False, + forces=None, + constraints=None, + methods=None, + rigid=None, + half_step_hook=None, + ): super().__init__(forces, constraints, methods, rigid) self._param_dict.update( @@ -294,14 +296,17 @@ def __init__(self, dt=float(dt), integrate_rotational_dof=bool(integrate_rotational_dof), half_step_hook=OnlyTypes(hoomd.md.HalfStepHook, - allow_none=True))) + allow_none=True), + )) self.half_step_hook = half_step_hook def _attach_hook(self): # initialize the reflected c++ class - self._cpp_obj = _md.IntegratorTwoStep( - self._simulation.state._cpp_sys_def, self.dt) + # checking for None allows deriving classes to construct this first + if self._cpp_obj is None: + self._cpp_obj = _md.IntegratorTwoStep( + self._simulation.state._cpp_sys_def, self.dt) # Call attach from DynamicIntegrator which attaches forces, # constraint_forces, and methods, and calls super()._attach() itself. super()._attach_hook() @@ -309,7 +314,7 @@ def _attach_hook(self): def __setattr__(self, attr, value): """Hande group DOF update when setting integrate_rotational_dof.""" super().__setattr__(attr, value) - if (attr == 'integrate_rotational_dof' and self._simulation is not None + if (attr == "integrate_rotational_dof" and self._simulation is not None and self._simulation.state is not None): self._simulation.state.update_group_dof() diff --git a/hoomd/mpcd/Integrator.cc b/hoomd/mpcd/Integrator.cc index ef7eead2a0..5186931f37 100644 --- a/hoomd/mpcd/Integrator.cc +++ b/hoomd/mpcd/Integrator.cc @@ -9,7 +9,10 @@ #include "Integrator.h" #ifdef ENABLE_MPI -#include "hoomd/Communicator.h" +#include "Communicator.h" +#ifdef ENABLE_HIP +#include "CommunicatorGPU.h" +#endif #endif namespace hoomd @@ -22,6 +25,23 @@ 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; + if (m_exec_conf->isCUDAEnabled()) + { + mpcd_comm = std::make_shared(sysdef); + } + else + { + mpcd_comm = std::make_shared(sysdef); + } + setMPCDCommunicator(mpcd_comm); + } +#endif // ENABLE_MPI } mpcd::Integrator::~Integrator() @@ -42,16 +62,19 @@ mpcd::Integrator::~Integrator() */ void mpcd::Integrator::update(uint64_t timestep) { - IntegratorTwoStep::update(timestep); - // issue a warning if no integration methods are set - if (!m_gave_warning && m_methods.size() == 0 && !m_stream) + // only issue a warning if no integration AND streaming method is set + if (!m_gave_warning && m_methods.size() == 0) { - m_exec_conf->msg->warning() - << "mpcd.integrate: No integration methods are set." << std::endl; + if (!m_stream) + { + m_exec_conf->msg->warning() + << "mpcd.integrate: No integration methods are set." << std::endl; + } + // setting this to true will shut up subsequent warnings if there is only a streaming method m_gave_warning = true; } - // remove any leftover virtual particles + // remove leftover virtual particles, communicate MPCD particles, and refill if (checkCollide(timestep)) { m_sysdef->getMPCDParticleData()->removeVirtualParticles(); @@ -63,66 +86,23 @@ void mpcd::Integrator::update(uint64_t timestep) #endif // ENABLE_MPI // fill in any virtual particles - if (checkCollide(timestep) && !m_fillers.empty()) + if (checkCollide(timestep) && m_filler) { - for (auto filler = m_fillers.begin(); filler != m_fillers.end(); ++filler) - { - (*filler)->fill(timestep); - } + m_filler->fill(timestep); } - // optionally sort + // optionally sort for performance if (m_sorter) 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); } /*! @@ -161,26 +141,6 @@ 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) - { - auto it = std::find(m_fillers.begin(), m_fillers.end(), filler); - if (it != m_fillers.end()) - { - 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_fillers.push_back(filler); - } - void mpcd::Integrator::syncCellList() { if (m_collide) @@ -201,10 +161,9 @@ void mpcd::Integrator::syncCellList() m_mpcd_comm->setCellList(m_cl); } #endif - - for (auto& filler : m_fillers) + if (m_filler) { - filler->setCellList(m_cl); + m_filler->setCellList(m_cl); } } @@ -217,17 +176,14 @@ void mpcd::detail::export_Integrator(pybind11::module& m) hoomd::md::IntegratorTwoStep, std::shared_ptr>(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("sorter", &mpcd::Integrator::getSorter, &mpcd::Integrator::setSorter) + .def_property("filler", &mpcd::Integrator::getFiller, &mpcd::Integrator::setFiller); } } // end namespace hoomd diff --git a/hoomd/mpcd/Integrator.h b/hoomd/mpcd/Integrator.h index 61f794cb08..39980d49d0 100644 --- a/hoomd/mpcd/Integrator.h +++ b/hoomd/mpcd/Integrator.h @@ -92,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 { @@ -117,13 +108,10 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep 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 @@ -135,28 +123,15 @@ 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() + std::shared_ptr getFiller() const { - m_sorter.reset(); + return m_filler; } - //! Add a virtual particle filling method - void addFiller(std::shared_ptr filler); - - //! Remove all virtual particle fillers - void removeAllFillers() + //! Set the virtual particle filling method + void setFiller(std::shared_ptr filler) { - m_fillers.clear(); + m_filler = filler; } protected: @@ -164,13 +139,11 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep 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::shared_ptr m_filler; //!< MPCD virtual particle fillers - std::vector> - m_fillers; //!< MPCD virtual particle fillers private: //! Check if a collision will occur at the current timestep bool checkCollide(uint64_t timestep) diff --git a/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index e39225d1cf..9430085fe3 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -82,12 +82,6 @@ hoomd.run(2000) -.. rubric:: Stability - -: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. - """ @@ -101,154 +95,3 @@ from hoomd.mpcd import integrate 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() diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index aaa1330872..85441613c5 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -1,241 +1,142 @@ # Copyright (c) 2009-2023 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. - -""" - import hoomd -from hoomd import _hoomd - -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" +from hoomd.data.parameterdicts import ParameterDict +from hoomd.data.typeconverter import OnlyTypes +from hoomd.md.integrate import Integrator as _MDIntegrator +from hoomd.mpcd import _mpcd +from hoomd.mpcd.collide import CellList, CollisionMethod +from hoomd.mpcd.stream import StreamingMethod - 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. +@hoomd.logging.modify_namespace(("mpcd", "Integrator")) +class Integrator(_MDIntegrator): + """MPCD integrator. - """ - 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. + The MPCD `Integrator` enables the MPCD algorithm concurrently with standard + MD methods. - 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') + 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:: - 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. + 0 1 2 3 4 5 + MD: |---->|---->|---->|---->|---->| + MPCD: |---------------------------->| - Examples:: - - all = group.all() - slit = mpcd.integrate.slit(group=all, H=5.0) - slit = mpcd.integrate.slit(group=all, H=10.0, V=1.0) - - .. versionadded:: 2.7 + 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. """ - 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, + ): + super().__init__( + dt, + integrate_rotational_dof, + forces, + constraints, + methods, + rigid, + half_step_hook, + ) + + param_dict = ParameterDict( + streaming_method=OnlyTypes(StreamingMethod, allow_none=True), + collision_method=OnlyTypes(CollisionMethod, allow_none=True), + ) + param_dict.update( + dict(streaming_method=streaming_method, + collision_method=collision_method)) + self._param_dict.update(param_dict) + + self._cell_list = CellList(cell_size=1.0, shift=True) + + @property + def cell_list(self): + """hoomd.mpcd.CellList: Collision cell list. + + A `~hoomd.mpcd.CellList` is automatically created with each `Integrator` + using typical defaults of cell size 1 and random grid shifting enabled. + You can change this 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 + + def _attach_hook(self): + self._cell_list._attach() + if self.streaming_method is not None: + self.streaming_method._attach() + if self.collision_method is not None: + self.collision_method._attach() + + self._cpp_obj = _mpcd.Integrator(self._simulation.state._cpp_sys_def, + self.dt) + self._cpp_obj.setCellList(self._cell_list._cpp_obj) + + super()._attach_hook() + + def _detach_hook(self): + self._cell_list._detach() + if self.streaming_method is not None: + self.streaming_method._detach() + if self.collision_method is not None: + self.collision_method._detach() + + super()._detach_hook() + + def _setattr_param(self, attr, value): + if attr == "streaming_method": + self._set_streaming_method(value) + elif attr == "collision_method": + self._set_collision_method(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) + + def _set_streaming_method(self, streaming_method): + if streaming_method is self.streaming_method: + return + + if streaming_method is not None and streaming_method._attached: + raise ValueError( + "Cannot attach streaming method to multiple integrators.") + + # if already attached, change out which is attached, then set parameter + if self._attached: + if self.streaming_method is not None: + self.streaming_method._detach() + if streaming_method is not None: + streaming_method._attach(self._simulation) + self._param_dict["streaming_method"] = streaming_method + + def _set_collision_method(self, collision_method): + if collision_method is self.collision_method: + return + + if collision_method is not None and collision_method._attached: + raise ValueError( + "Cannot attach collision method to multiple integrators.") + + # if already attached, change out which is attached, then set parameter + if self._attached: + if self.collision_method is not None: + self.collision_method._detach() + if collision_method is not None: + collision_method._attach(self._simulation) + self._param_dict["collision_method"] = collision_method From 8eb620218ec8c0a206e4b0d02d1aef92971d4cd0 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Tue, 17 Oct 2023 10:31:55 -0500 Subject: [PATCH 04/69] Remove remaining unused remove* methods --- hoomd/mpcd/CellList.h | 6 ------ hoomd/mpcd/StreamingMethod.cc | 3 +-- hoomd/mpcd/StreamingMethod.h | 11 +++++------ 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/hoomd/mpcd/CellList.h b/hoomd/mpcd/CellList.h index 45668a2f9e..40cf7fb505 100644 --- a/hoomd/mpcd/CellList.h +++ b/hoomd/mpcd/CellList.h @@ -239,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 { diff --git a/hoomd/mpcd/StreamingMethod.cc b/hoomd/mpcd/StreamingMethod.cc index d12479cf2a..4c23392996 100644 --- a/hoomd/mpcd/StreamingMethod.cc +++ b/hoomd/mpcd/StreamingMethod.cc @@ -121,8 +121,7 @@ void mpcd::detail::export_StreamingMethod(pybind11::module& m) "StreamingMethod") .def(pybind11::init, unsigned int, unsigned int, int>()) .def_property_readonly("every", &mpcd::StreamingMethod::getPeriod) - .def("setField", &mpcd::StreamingMethod::setField) - .def("removeField", &mpcd::StreamingMethod::removeField); + .def_property("field", &mpcd::StreamingMethod::getField, &mpcd::StreamingMethod::setField); } } // end namespace hoomd diff --git a/hoomd/mpcd/StreamingMethod.h b/hoomd/mpcd/StreamingMethod.h index 6d5aa22d9d..29487f009d 100644 --- a/hoomd/mpcd/StreamingMethod.h +++ b/hoomd/mpcd/StreamingMethod.h @@ -66,16 +66,15 @@ class PYBIND11_EXPORT StreamingMethod : public Autotuned return m_mpcd_dt; } - //! Set the external field - void setField(std::shared_ptr> field) + std::shared_ptr> getField() const { - m_field = field; + return m_field; } - //! Remove the external field - void removeField() + //! Set the external field + void setField(std::shared_ptr> field) { - m_field.reset(); + m_field = field; } //! Get the streaming period From 5addc661e51994ac27cebd66982435893bfa5a34 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 19 Oct 2023 11:46:07 -0500 Subject: [PATCH 05/69] Test collision method and fix related bugs --- hoomd/mpcd/CollisionMethod.cc | 6 +- hoomd/mpcd/Integrator.h | 5 +- hoomd/mpcd/__init__.py | 1 + hoomd/mpcd/collide.py | 11 ++- hoomd/mpcd/integrate.py | 8 +-- hoomd/mpcd/pytest/CMakeLists.txt | 2 + hoomd/mpcd/pytest/test_collide.py | 102 +++++++++++++++++++++++++++ hoomd/mpcd/pytest/test_integrator.py | 29 ++++++++ hoomd/mpcd/pytest/test_snapshot.py | 1 - 9 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 hoomd/mpcd/pytest/test_collide.py create mode 100644 hoomd/mpcd/pytest/test_integrator.py diff --git a/hoomd/mpcd/CollisionMethod.cc b/hoomd/mpcd/CollisionMethod.cc index 3f8674384e..d0d07eed65 100644 --- a/hoomd/mpcd/CollisionMethod.cc +++ b/hoomd/mpcd/CollisionMethod.cc @@ -139,9 +139,11 @@ void mpcd::detail::export_CollisionMethod(pybind11::module& m) .def_property_readonly("embed", [](const std::shared_ptr method) { - return (method) ? method->getEmbeddedGroup()->getFilter() - : std::shared_ptr(); + auto group = method->getEmbeddedGroup(); + return (group) ? group->getFilter() + : std::shared_ptr(); }) + .def("setEmbeddedGroup", &mpcd::CollisionMethod::setEmbeddedGroup) .def_property_readonly("every", &mpcd::CollisionMethod::getPeriod); } diff --git a/hoomd/mpcd/Integrator.h b/hoomd/mpcd/Integrator.h index 39980d49d0..7c5de78196 100644 --- a/hoomd/mpcd/Integrator.h +++ b/hoomd/mpcd/Integrator.h @@ -105,7 +105,10 @@ 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); + } } //! Get the current sorting method diff --git a/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index 9430085fe3..1049aa56f5 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -93,5 +93,6 @@ from hoomd.mpcd import collide from hoomd.mpcd import force from hoomd.mpcd import integrate +from hoomd.mpcd.integrate import Integrator from hoomd.mpcd import stream from hoomd.mpcd import update diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index baa3abd89e..7d3a6deea0 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -15,7 +15,7 @@ import hoomd from hoomd.data.parameterdicts import ParameterDict -from hoomd.data.typeconverter import OnlyTypes +from hoomd.data.typeconverter import OnlyTypes, variant_preprocessing from hoomd.mpcd import _mpcd from hoomd.operation import AutotunedObject @@ -110,7 +110,7 @@ class AndersenThermostat(CollisionMethod): Args: every (int): Number of integration steps between collisions. kT - (hoomd.variant.variant_like): Temperature of the solvent + kT (hoomd.variant.variant_like): Temperature of the solvent :math:`[\mathrm{energy}]`. embed (hoomd.filter.ParticleFilter): HOOMD particles to include in collision. @@ -202,7 +202,9 @@ def __init__(self, every, angle, kT=None, embed=None): param_dict = ParameterDict( angle=float(angle), - kT=OnlyTypes(hoomd.variant.Variant, allow_none=True), + kT=OnlyTypes(hoomd.variant.Variant, + allow_none=True, + preprocess=variant_preprocessing), ) param_dict["kT"] = kT self._param_dict.update(param_dict) @@ -220,7 +222,4 @@ def _attach_hook(self): if self.embed is not None: self._cpp_obj.setEmbeddedGroup(sim.state._get_group(self.embed)) - if self.kT is not None: - self._cpp_obj.setTemperature(self.kT) - super()._attach_hook() diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index 85441613c5..1bd789b5d8 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -80,15 +80,15 @@ def cell_list(self): return self._cell_list def _attach_hook(self): - self._cell_list._attach() + self._cell_list._attach(self._simulation) if self.streaming_method is not None: - self.streaming_method._attach() + self.streaming_method._attach(self._simulation) if self.collision_method is not None: - self.collision_method._attach() + self.collision_method._attach(self._simulation) self._cpp_obj = _mpcd.Integrator(self._simulation.state._cpp_sys_def, self.dt) - self._cpp_obj.setCellList(self._cell_list._cpp_obj) + self._cpp_obj.cell_list = self._cell_list._cpp_obj super()._attach_hook() diff --git a/hoomd/mpcd/pytest/CMakeLists.txt b/hoomd/mpcd/pytest/CMakeLists.txt index b3fac64ba7..30f5af44d9 100644 --- a/hoomd/mpcd/pytest/CMakeLists.txt +++ b/hoomd/mpcd/pytest/CMakeLists.txt @@ -1,5 +1,7 @@ # copy python modules to the build directory to make it a working python package set(files __init__.py + test_collide.py + test_integrator.py test_snapshot.py ) diff --git a/hoomd/mpcd/pytest/test_collide.py b/hoomd/mpcd/pytest/test_collide.py new file mode 100644 index 0000000000..57465987f4 --- /dev/null +++ b/hoomd/mpcd/pytest/test_collide.py @@ -0,0 +1,102 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import hoomd +from hoomd.conftest import ClassDefinition +import pytest + + +@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, + { + "every": 5, + "kT": 1.0, + }, + ), + ( + hoomd.mpcd.collide.StochasticRotationDynamics, + { + "every": 5, + "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(**init_args) + ig = hoomd.mpcd.Integrator(dt=0.02, collision_method=cm) + sim.operations.integrator = ig + + sim.run(0) + assert ig.collision_method is cm + assert cm.every == 5 + + ig.collision_method = None + sim.run(0) + assert ig.collision_method is None + + ig.collision_method = cm + sim.run(0) + assert ig.collision_method is cm + + def test_embed(self, small_snap, simulation_factory, cls, init_args): + sim = simulation_factory(small_snap) + cm = cls(**init_args, embed=hoomd.filter.All()) + sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, + collision_method=cm) + + sim.run(0) + assert isinstance(cm.embed, 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(**init_args) + sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, + collision_method=cm) + + sim.run(0) + + cm.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 10) + sim.run(0) + + if not kT_required: + cm.kT = None + sim.run(0) + + def test_run(self, small_snap, simulation_factory, cls, init_args): + sim = simulation_factory(small_snap) + cm = cls(**init_args) + sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, + collision_method=cm) + + sim.run(1) + + if "kT" not in init_args: + init_args["kT"] = 1.0 + sim.operations.integrator.collision_method = cls( + **init_args, embed=hoomd.filter.All()) + sim.run(1) diff --git a/hoomd/mpcd/pytest/test_integrator.py b/hoomd/mpcd/pytest/test_integrator.py new file mode 100644 index 0000000000..e5413baceb --- /dev/null +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -0,0 +1,29 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import hoomd +import numpy as np +import pytest + + +@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 + + +def test_create(small_snap, simulation_factory): + sim = simulation_factory(small_snap) + ig = hoomd.mpcd.Integrator(dt=0.02) + sim.operations.integrator = ig + sim.run(0) + + assert ig.cell_list is not None + assert ig.streaming_method is None + assert ig.collision_method is None diff --git a/hoomd/mpcd/pytest/test_snapshot.py b/hoomd/mpcd/pytest/test_snapshot.py index 2169c9273f..cd727f6d20 100644 --- a/hoomd/mpcd/pytest/test_snapshot.py +++ b/hoomd/mpcd/pytest/test_snapshot.py @@ -2,7 +2,6 @@ # Part of HOOMD-blue, released under the BSD 3-Clause License. import hoomd -from hoomd import Simulation import numpy as np import pytest From 7a574e48e4d7016c38ce3bbea238411e79950c67 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 19 Oct 2023 16:21:44 -0500 Subject: [PATCH 06/69] Test streaming method --- hoomd/mpcd/pytest/CMakeLists.txt | 1 + hoomd/mpcd/pytest/test_collide.py | 1 - hoomd/mpcd/pytest/test_stream.py | 439 ++++++++++++++++++++++++++++++ hoomd/mpcd/stream.py | 4 +- 4 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 hoomd/mpcd/pytest/test_stream.py diff --git a/hoomd/mpcd/pytest/CMakeLists.txt b/hoomd/mpcd/pytest/CMakeLists.txt index 30f5af44d9..80ebf35269 100644 --- a/hoomd/mpcd/pytest/CMakeLists.txt +++ b/hoomd/mpcd/pytest/CMakeLists.txt @@ -3,6 +3,7 @@ set(files __init__.py test_collide.py test_integrator.py test_snapshot.py + test_stream.py ) install(FILES ${files} diff --git a/hoomd/mpcd/pytest/test_collide.py b/hoomd/mpcd/pytest/test_collide.py index 57465987f4..8bd66cbba3 100644 --- a/hoomd/mpcd/pytest/test_collide.py +++ b/hoomd/mpcd/pytest/test_collide.py @@ -2,7 +2,6 @@ # Part of HOOMD-blue, released under the BSD 3-Clause License. import hoomd -from hoomd.conftest import ClassDefinition import pytest diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py new file mode 100644 index 0000000000..b32c992df3 --- /dev/null +++ b/hoomd/mpcd/pytest/test_stream.py @@ -0,0 +1,439 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import hoomd +import numpy as np +import pytest + + +@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.ParallelPlates, + { + "H": 4.0, + "V": 0.0, + "no_slip": True, + }, + ), + ( + hoomd.mpcd.stream.PlanarPore, + { + "H": 4.0, + "L": 3.0, + "no_slip": True, + }, + ), + ], + ids=["Bulk", "ParallelPlates", "PlanarPore"], +) +def test_create(simulation_factory, snap, cls, init_args): + sim = simulation_factory(snap) + sm = cls(every=5, **init_args) + ig = hoomd.mpcd.Integrator(dt=0.02, streaming_method=sm) + sim.operations.integrator = ig + + sim.run(0) + assert ig.streaming_method is sm + assert sm.every == 5 + + ig.streaming_method = None + sim.run(0) + assert ig.streaming_method is None + + ig.streaming_method = sm + sim.run(0) + assert ig.streaming_method is sm + + +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(every=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(every=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, -4.95, 3.85], [0.0, 0.0, -3.8]] + snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]] + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.ParallelPlates(every=1, H=4) + 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, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + 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, 4.95, 3.95], [-0.2, -0.2, -4.0]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[-1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]]) + + # 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, [[4.95, -4.95, 3.85], [-0.1, -0.1, -3.9]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[-1.0, 1.0, -1.0], [1.0, 1.0, 1.0]]) + + def test_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, -4.95, 3.85], [0.0, 0.0, -3.8]] + snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]] + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.ParallelPlates(every=1, H=4, 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, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + 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, 4.85, 3.95], [-0.2, -0.2, -4.0]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]]) + + # 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, [[-4.75, 4.75, 3.85], [-0.3, -0.3, -3.9]]) + 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 3 steps. 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, -4.95, 3.85], [0.0, 0.0, -3.8]] + snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-2.0, -1.0, -1.0]] + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.ParallelPlates(every=3, H=4, V=1, no_slip=True) + ig = hoomd.mpcd.Integrator(dt=0.1, 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, -4.95, 3.85], [-0.4, -0.1, -3.9]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[1.0, 1.0, -1.0], [0.0, 1.0, 1.0]]) + + def test_validate_box(self, simulation_factory, snap): + """Test box validation raises an error on run.""" + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.ParallelPlates(every=1, H=10) + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sim.operations.integrator = ig + + with pytest.raises(RuntimeError): + sim.run(1) + + def test_test_of_bounds(self, simulation_factory, snap): + """Test box validation raises an error on run.""" + if snap.communicator.rank == 0: + snap.mpcd.position[:] = [[4.95, -4.95, 3.85]] + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.ParallelPlates(every=1, H=3.8) + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sim.operations.integrator = ig + + with pytest.raises(RuntimeError): + sim.run(1) + + +class TestPlanarPore: + + def _make_particles(self, snap): + if snap.communicator.rank == 0: + snap.mpcd.N = 8 + snap.mpcd.position[:] = [ + [-3.05, -4, -4.11], + [3.05, 4, 4.11], + [-3.05, -2, 4.11], + [3.05, 2, -4.11], + [0, 0, 3.95], + [0, 0, -3.95], + [3.03, 0, -3.98], + [3.02, 0, -3.97], + ] + snap.mpcd.velocity[:] = [ + [1.0, -1.0, 1.0], + [-1.0, 1.0, -1.0], + [1.0, 0.0, -1.0], + [-1.0, 0.0, 1.0], + [0.0, 0.0, 1.0], + [0.0, 0.0, -1.0], + [-1.0, 0.0, -1.0], + [-1.0, 0.0, -1.0], + ] + return snap + + def test_step_noslip(self, simulation_factory, snap): + snap = self._make_particles(snap) + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.PlanarPore(every=1, H=4, L=3, 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, -4.11]) + 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, 4.11]) + 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, -2, 4.11]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[2], + [-1.0, 0.0, 1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[3], + [3.05, 2, -4.11]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[3], + [1.0, 0.0, -1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[4], + [0, 0, 3.95]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[4], + [0, 0, -1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[5], + [0, 0, -3.95]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[5], + [0, 0, 1.0]) + # hits z = -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, 0, -3.92]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[6], + [1, 0, 1]) + # hits x = 3 after 0.02, then reverses. z is -3.99, so reverses to -3.91 + np.testing.assert_array_almost_equal(snap.mpcd.position[7], + [3.08, 0, -3.91]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[7], + [1, 0, 1]) + + # 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.9, -4.21]) + np.testing.assert_array_almost_equal(snap.mpcd.position[1], + [3.15, 3.9, 4.21]) + np.testing.assert_array_almost_equal(snap.mpcd.position[2], + [-3.15, -2, 4.21]) + np.testing.assert_array_almost_equal(snap.mpcd.position[3], + [3.15, 2, -4.21]) + np.testing.assert_array_almost_equal(snap.mpcd.position[4], + [0, 0, 3.85]) + np.testing.assert_array_almost_equal(snap.mpcd.position[5], + [0, 0, -3.85]) + np.testing.assert_array_almost_equal(snap.mpcd.position[6], + [3.19, 0, -3.82]) + np.testing.assert_array_almost_equal(snap.mpcd.position[7], + [3.18, 0, -3.81]) + + def test_step_slip(self, simulation_factory, snap): + snap = self._make_particles(snap) + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.PlanarPore(every=1, H=4, L=3, 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.1, -4.01]) + 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.1, 4.01]) + 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, -2, 4.01]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[2], + [-1.0, 0.0, -1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[3], + [3.05, 2, -4.01]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[3], + [1.0, 0.0, 1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[4], + [0, 0, 3.95]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[4], + [0, 0, -1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[5], + [0, 0, -3.95]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[5], + [0, 0, 1.0]) + # hits z = -4 after 0.02, then reverses. x is not touched because slip + np.testing.assert_array_almost_equal(snap.mpcd.position[6], + [2.93, 0, -3.92]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[6], + [-1, 0, 1]) + # hits x = 3 after 0.02, then reverses. z is not touched because slip + np.testing.assert_array_almost_equal(snap.mpcd.position[7], + [3.08, 0, -4.07]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[7], + [1, 0, -1]) + + # 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.2, -3.91]) + np.testing.assert_array_almost_equal(snap.mpcd.position[1], + [3.15, 4.2, 3.91]) + np.testing.assert_array_almost_equal(snap.mpcd.position[2], + [-3.15, -2, 3.91]) + np.testing.assert_array_almost_equal(snap.mpcd.position[3], + [3.15, 2, -3.91]) + np.testing.assert_array_almost_equal(snap.mpcd.position[4], + [0, 0, 3.85]) + np.testing.assert_array_almost_equal(snap.mpcd.position[5], + [0, 0, -3.85]) + np.testing.assert_array_almost_equal(snap.mpcd.position[6], + [2.83, 0, -3.82]) + np.testing.assert_array_almost_equal(snap.mpcd.position[7], + [3.18, 0, -4.17]) + + def test_validate_box(self, simulation_factory, snap): + """Test box validation raises an error on run.""" + sim = simulation_factory(snap) + ig = hoomd.mpcd.Integrator(dt=0.1) + sim.operations.integrator = ig + + ig.streaming_method = hoomd.mpcd.stream.PlanarPore(every=1, H=10, L=2) + with pytest.raises(RuntimeError): + sim.run(1) + + ig.streaming_method = hoomd.mpcd.stream.PlanarPore(every=1, H=4, L=10) + with pytest.raises(RuntimeError): + sim.run(1) + + def test_test_of_bounds(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.PlanarPore(every=1, H=3.8, L=3) + with pytest.raises(RuntimeError): + sim.run(1) + + ig.streaming_method = hoomd.mpcd.stream.PlanarPore(every=1, H=4, L=3.5) + with pytest.raises(RuntimeError): + sim.run(1) diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index fc37a0f72c..b5bdf7d808 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -199,8 +199,8 @@ class PlanarPore(StreamingMethod): """ - def __init__(self, trigger, H, L, no_slip=True): - super().__init__(trigger) + def __init__(self, every, H, L, no_slip=True): + super().__init__(every) param_dict = ParameterDict( H=float(H), From ed3631af4a0c9baad5ad6ebdb7bae5ca87871862 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 16 Nov 2023 10:48:22 -0600 Subject: [PATCH 07/69] Use Scalar instead of double for collision angle --- hoomd/mpcd/CellList.cc | 2 +- hoomd/mpcd/CellListGPU.cc | 2 +- hoomd/mpcd/SRDCollisionMethod.cc | 4 ++-- hoomd/mpcd/SRDCollisionMethod.h | 8 ++++---- hoomd/mpcd/SRDCollisionMethodGPU.cc | 4 ++-- hoomd/mpcd/SRDCollisionMethodGPU.h | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/hoomd/mpcd/CellList.cc b/hoomd/mpcd/CellList.cc index 00ee49c427..87abce9f8c 100644 --- a/hoomd/mpcd/CellList.cc +++ b/hoomd/mpcd/CellList.cc @@ -966,7 +966,7 @@ const int3 mpcd::CellList::wrapGlobalCell(const int3& cell) const void mpcd::detail::export_CellList(pybind11::module& m) { pybind11::class_>(m, "CellList") - .def(pybind11::init, double, bool>()) + .def(pybind11::init, Scalar, bool>()) .def_property("cell_size", &mpcd::CellList::getCellSize, &mpcd::CellList::setCellSize) .def_property("shift", &mpcd::CellList::isGridShifting, diff --git a/hoomd/mpcd/CellListGPU.cc b/hoomd/mpcd/CellListGPU.cc index 63d495eed0..971344cedb 100644 --- a/hoomd/mpcd/CellListGPU.cc +++ b/hoomd/mpcd/CellListGPU.cc @@ -213,7 +213,7 @@ void mpcd::detail::export_CellListGPU(pybind11::module& m) pybind11::class_>( m, "CellListGPU") - .def(pybind11::init, double, bool>()); + .def(pybind11::init, Scalar, bool>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/SRDCollisionMethod.cc b/hoomd/mpcd/SRDCollisionMethod.cc index 0c801d8c7d..7942e8bed7 100644 --- a/hoomd/mpcd/SRDCollisionMethod.cc +++ b/hoomd/mpcd/SRDCollisionMethod.cc @@ -16,7 +16,7 @@ mpcd::SRDCollisionMethod::SRDCollisionMethod(std::shared_ptr s unsigned int cur_timestep, unsigned int period, int phase, - double angle) + Scalar angle) : mpcd::CollisionMethod(sysdef, cur_timestep, period, phase), m_rotvec(m_exec_conf), m_angle(angle), m_factors(m_exec_conf) { @@ -286,7 +286,7 @@ void mpcd::detail::export_SRDCollisionMethod(pybind11::module& m) mpcd::CollisionMethod, std::shared_ptr>(m, "SRDCollisionMethod") .def(pybind11:: - init, unsigned int, unsigned int, int, double>()) + init, unsigned int, unsigned int, int, Scalar>()) .def_property("angle", &mpcd::SRDCollisionMethod::getRotationAngle, &mpcd::SRDCollisionMethod::setRotationAngle) diff --git a/hoomd/mpcd/SRDCollisionMethod.h b/hoomd/mpcd/SRDCollisionMethod.h index 6f2f6176b3..7871c7744a 100644 --- a/hoomd/mpcd/SRDCollisionMethod.h +++ b/hoomd/mpcd/SRDCollisionMethod.h @@ -30,7 +30,7 @@ class PYBIND11_EXPORT SRDCollisionMethod : public mpcd::CollisionMethod unsigned int cur_timestep, unsigned int period, int phase, - double angle); + Scalar angle); //! Destructor virtual ~SRDCollisionMethod(); @@ -38,7 +38,7 @@ class PYBIND11_EXPORT SRDCollisionMethod : public mpcd::CollisionMethod void setCellList(std::shared_ptr cl); //! Get the MPCD rotation angles - double getRotationAngle() const + Scalar getRotationAngle() const { return m_angle; } @@ -47,7 +47,7 @@ class PYBIND11_EXPORT SRDCollisionMethod : public mpcd::CollisionMethod /*! * \param angle MPCD rotation angle in degrees */ - void setRotationAngle(double angle) + void setRotationAngle(Scalar angle) { m_angle = angle; } @@ -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 63573e1fdd..7892667eb8 100644 --- a/hoomd/mpcd/SRDCollisionMethodGPU.cc +++ b/hoomd/mpcd/SRDCollisionMethodGPU.cc @@ -16,7 +16,7 @@ mpcd::SRDCollisionMethodGPU::SRDCollisionMethodGPU(std::shared_ptr({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, @@ -182,7 +182,7 @@ void mpcd::detail::export_SRDCollisionMethodGPU(pybind11::module& m) std::shared_ptr>(m, "SRDCollisionMethodGPU") .def( pybind11:: - init, unsigned int, unsigned int, int, double>()); + init, unsigned int, unsigned int, int, Scalar>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/SRDCollisionMethodGPU.h b/hoomd/mpcd/SRDCollisionMethodGPU.h index c4580941ed..5383ff39a1 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, - double angle); + Scalar angle); void setCellList(std::shared_ptr cl); From 4c7a10c651f72efcd4526b9ff922a337a2a83276 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 16 Nov 2023 13:32:38 -0600 Subject: [PATCH 08/69] Add missing include guard for GPU class --- hoomd/mpcd/Integrator.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hoomd/mpcd/Integrator.cc b/hoomd/mpcd/Integrator.cc index 5186931f37..0d9af0533d 100644 --- a/hoomd/mpcd/Integrator.cc +++ b/hoomd/mpcd/Integrator.cc @@ -31,11 +31,13 @@ mpcd::Integrator::Integrator(std::shared_ptr sysdef, Scalar de 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); } From d675d37ea69d071774987ca55141ce18e0c0a588 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 20 Nov 2023 11:08:31 -0600 Subject: [PATCH 09/69] Revert changes to md integrator and use grandparent attach --- hoomd/md/integrate.py | 39 +++++++++++++++++---------------------- hoomd/mpcd/integrate.py | 2 +- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/hoomd/md/integrate.py b/hoomd/md/integrate.py index 9c4ef4d698..567858cf5f 100644 --- a/hoomd/md/integrate.py +++ b/hoomd/md/integrate.py @@ -28,16 +28,15 @@ def __init__(self, forces, constraints, methods, rigid): constraints = [] if constraints is None else constraints methods = [] if methods is None else methods self._forces = syncedlist.SyncedList( - Force, syncedlist._PartialGetAttr("_cpp_obj"), iterable=forces) + Force, syncedlist._PartialGetAttr('_cpp_obj'), iterable=forces) self._constraints = syncedlist.SyncedList( OnlyTypes(Constraint, disallow_types=(Rigid,)), - syncedlist._PartialGetAttr("_cpp_obj"), - iterable=constraints, - ) + syncedlist._PartialGetAttr('_cpp_obj'), + iterable=constraints) self._methods = syncedlist.SyncedList( - Method, syncedlist._PartialGetAttr("_cpp_obj"), iterable=methods) + Method, syncedlist._PartialGetAttr('_cpp_obj'), iterable=methods) param_dict = ParameterDict(rigid=OnlyTypes(Rigid, allow_none=True)) if rigid is not None and rigid._attached: @@ -279,16 +278,15 @@ class Integrator(_DynamicIntegrator): perform computations during the half-step of the integration. """ - def __init__( - self, - dt, - integrate_rotational_dof=False, - forces=None, - constraints=None, - methods=None, - rigid=None, - half_step_hook=None, - ): + def __init__(self, + dt, + integrate_rotational_dof=False, + forces=None, + constraints=None, + methods=None, + rigid=None, + half_step_hook=None): + super().__init__(forces, constraints, methods, rigid) self._param_dict.update( @@ -296,17 +294,14 @@ def __init__( dt=float(dt), integrate_rotational_dof=bool(integrate_rotational_dof), half_step_hook=OnlyTypes(hoomd.md.HalfStepHook, - allow_none=True), - )) + allow_none=True))) self.half_step_hook = half_step_hook def _attach_hook(self): # initialize the reflected c++ class - # checking for None allows deriving classes to construct this first - if self._cpp_obj is None: - self._cpp_obj = _md.IntegratorTwoStep( - self._simulation.state._cpp_sys_def, self.dt) + self._cpp_obj = _md.IntegratorTwoStep( + self._simulation.state._cpp_sys_def, self.dt) # Call attach from DynamicIntegrator which attaches forces, # constraint_forces, and methods, and calls super()._attach() itself. super()._attach_hook() @@ -314,7 +309,7 @@ def _attach_hook(self): def __setattr__(self, attr, value): """Hande group DOF update when setting integrate_rotational_dof.""" super().__setattr__(attr, value) - if (attr == "integrate_rotational_dof" and self._simulation is not None + if (attr == 'integrate_rotational_dof' and self._simulation is not None and self._simulation.state is not None): self._simulation.state.update_group_dof() diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index 1bd789b5d8..9f9c4f071f 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -90,7 +90,7 @@ def _attach_hook(self): self.dt) self._cpp_obj.cell_list = self._cell_list._cpp_obj - super()._attach_hook() + super(_MDIntegrator, self)._attach_hook() def _detach_hook(self): self._cell_list._detach() From 9f3035fc366b22cf02617123059a8e8bccce097b Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 20 Nov 2023 11:34:28 -0600 Subject: [PATCH 10/69] Rename Python attributes of streaming & collision methods --- hoomd/mpcd/CollisionMethod.cc | 4 +-- hoomd/mpcd/StreamingMethod.cc | 2 +- hoomd/mpcd/collide.py | 53 ++++++++++++++++--------------- hoomd/mpcd/pytest/test_collide.py | 12 +++---- hoomd/mpcd/pytest/test_stream.py | 30 ++++++++--------- hoomd/mpcd/stream.py | 36 ++++++++++----------- 6 files changed, 70 insertions(+), 67 deletions(-) diff --git a/hoomd/mpcd/CollisionMethod.cc b/hoomd/mpcd/CollisionMethod.cc index d0d07eed65..a7574d4e9c 100644 --- a/hoomd/mpcd/CollisionMethod.cc +++ b/hoomd/mpcd/CollisionMethod.cc @@ -136,7 +136,7 @@ void mpcd::detail::export_CollisionMethod(pybind11::module& m) m, "CollisionMethod") .def(pybind11::init, uint64_t, uint64_t, int>()) - .def_property_readonly("embed", + .def_property_readonly("embedded_particles", [](const std::shared_ptr method) { auto group = method->getEmbeddedGroup(); @@ -144,7 +144,7 @@ void mpcd::detail::export_CollisionMethod(pybind11::module& m) : std::shared_ptr(); }) .def("setEmbeddedGroup", &mpcd::CollisionMethod::setEmbeddedGroup) - .def_property_readonly("every", &mpcd::CollisionMethod::getPeriod); + .def_property_readonly("period", &mpcd::CollisionMethod::getPeriod); } } // end namespace hoomd diff --git a/hoomd/mpcd/StreamingMethod.cc b/hoomd/mpcd/StreamingMethod.cc index 4c23392996..8cfedc6daa 100644 --- a/hoomd/mpcd/StreamingMethod.cc +++ b/hoomd/mpcd/StreamingMethod.cc @@ -120,7 +120,7 @@ void mpcd::detail::export_StreamingMethod(pybind11::module& m) m, "StreamingMethod") .def(pybind11::init, unsigned int, unsigned int, int>()) - .def_property_readonly("every", &mpcd::StreamingMethod::getPeriod) + .def_property_readonly("period", &mpcd::StreamingMethod::getPeriod) .def_property("field", &mpcd::StreamingMethod::getField, &mpcd::StreamingMethod::setField); } diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 7d3a6deea0..8461d272ae 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -69,11 +69,11 @@ class CollisionMethod(AutotunedObject): """Base collision method. Args: - every (int): Number of integration steps between collisions. - embed (hoomd.filter.filter_like): HOOMD particles to include in collision. + period (int): Number of integration steps between collisions. + embedded_particles (hoomd.filter.filter_like): HOOMD particles to include in collision. Attributes: - embed (hoomd.filter.filter_like): HOOMD particles to include in collision. + embedded_particles (hoomd.filter.filter_like): HOOMD particles to include in collision. These particles are included in per-cell quantities and have their velocities updated along with the MPCD particles. @@ -85,23 +85,24 @@ class CollisionMethod(AutotunedObject): particles themselves already act as a heat bath for the embedded particles. - every (int): Number of integration steps between collisions. + period (int): Number of integration steps between collisions. A collision is executed each time the `~hoomd.Simulation.timestep` - is a multiple of `every`. It must be a multiple of `every` for the + is a multiple of `period`. It must be a multiple of `period` for the `~hoomd.mpcd.stream.StreamingMethod` if one is attached to the `~hoomd.mpcd.Integrator`. """ - def __init__(self, every, embed=None): + def __init__(self, period, embedded_particles=None): super().__init__() param_dict = ParameterDict( - every=int(every), - embed=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=True), + period=int(period), + embedded_particles=OnlyTypes(hoomd.filter.ParticleFilter, + allow_none=True), ) - param_dict["embed"] = embed + param_dict["embedded_particles"] = embedded_particles self._param_dict.update(param_dict) @@ -109,16 +110,16 @@ class AndersenThermostat(CollisionMethod): r"""Andersen thermostat method. Args: - every (int): Number of integration steps between collisions. kT + period (int): Number of integration steps between collisions. kT (hoomd.variant.variant_like): Temperature of the solvent :math:`[\mathrm{energy}]`. - embed (hoomd.filter.ParticleFilter): HOOMD particles to include in + embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to include in collision. This class implements the Andersen thermostat collision rule for MPCD, as described by `Allahyarov and Gompper - `_. Every `every` steps, the + `_. Every `period` steps, the particles are binned into cells. New particle velocities are then randomly drawn from a Gaussian distribution relative to the center-of-mass velocity for the cell. The random velocities are given zero-mean so that the cell @@ -134,8 +135,8 @@ class AndersenThermostat(CollisionMethod): """ - def __init__(self, every, kT, embed=None): - super().__init__(every, embed) + def __init__(self, period, kT, embedded_particles=None): + super().__init__(period, embedded_particles) param_dict = ParameterDict(kT=hoomd.variant.Variant) param_dict["kT"] = kT @@ -149,10 +150,11 @@ def _attach_hook(self): cpp_class = _mpcd.ATCollisionMethod self._cpp_obj = cpp_class(sim.state._cpp_sys_def, sim.timestep, - self.every, 0, self.kT) + self.period, 0, self.kT) - if self.embed is not None: - self._cpp_obj.setEmbeddedGroup(sim.state._get_group(self.embed)) + if self.embedded_particles is not None: + self._cpp_obj.setEmbeddedGroup( + sim.state._get_group(self.embedded_particles)) super()._attach_hook() @@ -161,16 +163,16 @@ class StochasticRotationDynamics(CollisionMethod): r"""Stochastic rotation dynamics method. Args: - every (int): Number of integration steps between collisions. + period (int): Number of integration steps between collisions. angle (float): Rotation angle (in degrees). kT (hoomd.variant.variant_like): Temperature for the collision thermostat :`[\mathrm{energy}]`. If None, no thermostat is used. - embed (hoomd.filter.ParticleFilter): HOOMD particles to include in + embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to include in collision. This class implements the classic stochastic rotation dynamics collision rule for MPCD as first proposed by `Malevanets and Kapral - `_. Every `every` steps, the particles are + `_. Every `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 @@ -197,8 +199,8 @@ class StochasticRotationDynamics(CollisionMethod): """ - def __init__(self, every, angle, kT=None, embed=None): - super().__init__(every, embed) + def __init__(self, period, angle, kT=None, embedded_particles=None): + super().__init__(period, embedded_particles) param_dict = ParameterDict( angle=float(angle), @@ -217,9 +219,10 @@ def _attach_hook(self): cpp_class = _mpcd.SRDCollisionMethod self._cpp_obj = cpp_class(sim.state._cpp_sys_def, sim.timestep, - self.every, 0, self.angle) + self.period, 0, self.angle) - if self.embed is not None: - self._cpp_obj.setEmbeddedGroup(sim.state._get_group(self.embed)) + 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/pytest/test_collide.py b/hoomd/mpcd/pytest/test_collide.py index 8bd66cbba3..e10d8a70a3 100644 --- a/hoomd/mpcd/pytest/test_collide.py +++ b/hoomd/mpcd/pytest/test_collide.py @@ -23,14 +23,14 @@ def small_snap(): ( hoomd.mpcd.collide.AndersenThermostat, { - "every": 5, + "period": 5, "kT": 1.0, }, ), ( hoomd.mpcd.collide.StochasticRotationDynamics, { - "every": 5, + "period": 5, "angle": 90, }, ), @@ -47,7 +47,7 @@ def test_create(self, small_snap, simulation_factory, cls, init_args): sim.run(0) assert ig.collision_method is cm - assert cm.every == 5 + assert cm.period == 5 ig.collision_method = None sim.run(0) @@ -59,12 +59,12 @@ def test_create(self, small_snap, simulation_factory, cls, init_args): def test_embed(self, small_snap, simulation_factory, cls, init_args): sim = simulation_factory(small_snap) - cm = cls(**init_args, embed=hoomd.filter.All()) + cm = cls(**init_args, embedded_particles=hoomd.filter.All()) sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, collision_method=cm) sim.run(0) - assert isinstance(cm.embed, hoomd.filter.All) + assert isinstance(cm.embedded_particles, hoomd.filter.All) def test_temperature(self, small_snap, simulation_factory, cls, init_args): sim = simulation_factory(small_snap) @@ -97,5 +97,5 @@ def test_run(self, small_snap, simulation_factory, cls, init_args): if "kT" not in init_args: init_args["kT"] = 1.0 sim.operations.integrator.collision_method = cls( - **init_args, embed=hoomd.filter.All()) + **init_args, embedded_particles=hoomd.filter.All()) sim.run(1) diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index b32c992df3..56c4621448 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -43,13 +43,13 @@ def snap(): ) def test_create(simulation_factory, snap, cls, init_args): sim = simulation_factory(snap) - sm = cls(every=5, **init_args) + sm = cls(period=5, **init_args) ig = hoomd.mpcd.Integrator(dt=0.02, streaming_method=sm) sim.operations.integrator = ig sim.run(0) assert ig.streaming_method is sm - assert sm.every == 5 + assert sm.period == 5 ig.streaming_method = None sim.run(0) @@ -69,7 +69,7 @@ def test_bulk_step(self, simulation_factory, snap): snap.mpcd.velocity[:] = [[1.0, 1.0, 1.0], [-1.0, -1.0, -1.0]] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.Bulk(every=1) + sm = hoomd.mpcd.stream.Bulk(period=1) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -97,7 +97,7 @@ def test_bulk_step(self, simulation_factory, snap): # 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(every=4) + ig.streaming_method = hoomd.mpcd.stream.Bulk(period=4) sim.run(1) snap = sim.state.get_snapshot() if snap.communicator.rank == 0: @@ -128,7 +128,7 @@ def test_step_noslip(self, simulation_factory, snap): snap.mpcd.position[:] = [[4.95, -4.95, 3.85], [0.0, 0.0, -3.8]] snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(every=1, H=4) + sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=4) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -166,7 +166,7 @@ def test_slip(self, simulation_factory, snap): snap.mpcd.position[:] = [[4.95, -4.95, 3.85], [0.0, 0.0, -3.8]] snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(every=1, H=4, no_slip=False) + sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=4, no_slip=False) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -212,7 +212,7 @@ def test_step_moving_wall(self, simulation_factory, snap): snap.mpcd.position[:] = [[4.95, -4.95, 3.85], [0.0, 0.0, -3.8]] snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-2.0, -1.0, -1.0]] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(every=3, H=4, V=1, no_slip=True) + sm = hoomd.mpcd.stream.ParallelPlates(period=3, H=4, V=1, no_slip=True) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -228,7 +228,7 @@ def test_step_moving_wall(self, simulation_factory, snap): def test_validate_box(self, simulation_factory, snap): """Test box validation raises an error on run.""" sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(every=1, H=10) + sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=10) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -240,7 +240,7 @@ def test_test_of_bounds(self, simulation_factory, snap): if snap.communicator.rank == 0: snap.mpcd.position[:] = [[4.95, -4.95, 3.85]] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(every=1, H=3.8) + sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=3.8) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -278,7 +278,7 @@ def _make_particles(self, snap): def test_step_noslip(self, simulation_factory, snap): snap = self._make_particles(snap) sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.PlanarPore(every=1, H=4, L=3, no_slip=True) + sm = hoomd.mpcd.stream.PlanarPore(period=1, H=4, L=3, no_slip=True) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -345,7 +345,7 @@ def test_step_noslip(self, simulation_factory, snap): def test_step_slip(self, simulation_factory, snap): snap = self._make_particles(snap) sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.PlanarPore(every=1, H=4, L=3, no_slip=False) + sm = hoomd.mpcd.stream.PlanarPore(period=1, H=4, L=3, no_slip=False) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -415,11 +415,11 @@ def test_validate_box(self, simulation_factory, snap): ig = hoomd.mpcd.Integrator(dt=0.1) sim.operations.integrator = ig - ig.streaming_method = hoomd.mpcd.stream.PlanarPore(every=1, H=10, L=2) + ig.streaming_method = hoomd.mpcd.stream.PlanarPore(period=1, H=10, L=2) with pytest.raises(RuntimeError): sim.run(1) - ig.streaming_method = hoomd.mpcd.stream.PlanarPore(every=1, H=4, L=10) + ig.streaming_method = hoomd.mpcd.stream.PlanarPore(period=1, H=4, L=10) with pytest.raises(RuntimeError): sim.run(1) @@ -430,10 +430,10 @@ def test_test_of_bounds(self, simulation_factory, snap): ig = hoomd.mpcd.Integrator(dt=0.1) sim.operations.integrator = ig - ig.streaming_method = hoomd.mpcd.stream.PlanarPore(every=1, H=3.8, L=3) + ig.streaming_method = hoomd.mpcd.stream.PlanarPore(period=1, H=3.8, L=3) with pytest.raises(RuntimeError): sim.run(1) - ig.streaming_method = hoomd.mpcd.stream.PlanarPore(every=1, H=4, L=3.5) + ig.streaming_method = hoomd.mpcd.stream.PlanarPore(period=1, H=4, L=3.5) with pytest.raises(RuntimeError): sim.run(1) diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index b5bdf7d808..591cb1f680 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -57,25 +57,25 @@ class StreamingMethod(AutotunedObject): """Base streaming method. Args: - every (int): Number of integration steps covered by streaming step. + period (int): Number of integration steps covered by streaming step. Attributes: - every (int): Number of integration steps covered by streaming step. + period (int): Number of integration steps covered by streaming step. - The MPCD particles with be streamed every time the `~hoomd.Simulation` - timestep is a multiple of `every`. The streaming time is hence equal - to `every` steps of the `~hoomd.mpcd.Integrator`. Typically `every` - should be equal to `~hoomd.mpcd.CollisionMethod.every` for the + The MPCD particles will be streamed every time the `~hoomd.Simulation` + timestep is a multiple of `period`. The streaming time is hence equal + to `period` steps of the `~hoomd.mpcd.Integrator`. Typically `period` + should be equal to `~hoomd.mpcd.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. """ - def __init__(self, every): + def __init__(self, period): super().__init__() - param_dict = ParameterDict(every=int(every),) + param_dict = ParameterDict(period=int(period),) self._param_dict.update(param_dict) @@ -83,7 +83,7 @@ class Bulk(StreamingMethod): """Bulk fluid. Args: - every (int): Number of integration steps covered by streaming step. + period (int): Number of integration steps covered by streaming step. `Bulk` streams the MPCD particles in a fully periodic geometry (2D or 3D). This geometry is appropriate for modeling bulk fluids. @@ -100,7 +100,7 @@ def _attach_hook(self): self._cpp_obj = class_( sim.state._cpp_sys_def, sim.timestep, - self.every, + self.period, 0, _mpcd.BulkGeometry(), ) @@ -112,7 +112,7 @@ class ParallelPlates(StreamingMethod): r"""Parallel-plate channel. Args: - every (int): Number of integration steps covered by streaming step. + period (int): Number of integration steps covered by streaming step. H (float): Channel half-width. V (float): Wall speed. no_slip (bool): If True, plates have no-slip boundary condition. @@ -139,8 +139,8 @@ class ParallelPlates(StreamingMethod): """ - def __init__(self, every, H, V=0.0, no_slip=True): - super().__init__(every) + def __init__(self, period, H, V=0.0, no_slip=True): + super().__init__(period) param_dict = ParameterDict( H=float(H), @@ -161,7 +161,7 @@ def _attach_hook(self): self._cpp_obj = class_( sim.state._cpp_sys_def, sim.timestep, - self.every, + self.period, 0, slit, ) @@ -173,7 +173,7 @@ class PlanarPore(StreamingMethod): r"""Parallel plate pore. Args: - every (int): Number of integration steps covered by streaming step. + period (int): Number of integration steps covered by streaming step. H (float): Channel half-width. L (float): Pore half-length. no_slip (bool): If True, plates have no-slip boundary condition. @@ -199,8 +199,8 @@ class PlanarPore(StreamingMethod): """ - def __init__(self, every, H, L, no_slip=True): - super().__init__(every) + def __init__(self, period, H, L, no_slip=True): + super().__init__(period) param_dict = ParameterDict( H=float(H), @@ -221,7 +221,7 @@ def _attach_hook(self): self._cpp_obj = class_( sim.state._cpp_sys_def, sim.timestep, - self.every, + self.period, 0, slit, ) From b2ac29a21c0bb42d8b2fd4bf5fb9f16c584fb592 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 20 Nov 2023 13:07:16 -0600 Subject: [PATCH 11/69] Remove warning for running without methods --- hoomd/mpcd/Integrator.cc | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/hoomd/mpcd/Integrator.cc b/hoomd/mpcd/Integrator.cc index 0d9af0533d..742d7b0305 100644 --- a/hoomd/mpcd/Integrator.cc +++ b/hoomd/mpcd/Integrator.cc @@ -64,18 +64,6 @@ mpcd::Integrator::~Integrator() */ void mpcd::Integrator::update(uint64_t timestep) { - // only issue a warning if no integration AND streaming method is set - if (!m_gave_warning && m_methods.size() == 0) - { - if (!m_stream) - { - m_exec_conf->msg->warning() - << "mpcd.integrate: No integration methods are set." << std::endl; - } - // setting this to true will shut up subsequent warnings if there is only a streaming method - m_gave_warning = true; - } - // remove leftover virtual particles, communicate MPCD particles, and refill if (checkCollide(timestep)) { From 8a0548adb57fcc92c386c8d7fc7a5b2a565c6fa3 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 20 Nov 2023 13:10:20 -0600 Subject: [PATCH 12/69] Reenable MPCD import --- hoomd/__init__.py | 4 ++-- hoomd/mpcd/__init__.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/hoomd/__init__.py b/hoomd/__init__.py index 6513b84665..66bc76ce06 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/__init__.py b/hoomd/mpcd/__init__.py index 1049aa56f5..50f492fd56 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -91,8 +91,6 @@ from hoomd.md import _md from hoomd.mpcd import collide -from hoomd.mpcd import force from hoomd.mpcd import integrate from hoomd.mpcd.integrate import Integrator from hoomd.mpcd import stream -from hoomd.mpcd import update From 7ca3408d733a14c84a733a572b8247be8d88e375 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 20 Nov 2023 13:10:37 -0600 Subject: [PATCH 13/69] Fix language in docstring per review --- hoomd/mpcd/collide.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 8461d272ae..6c972e1096 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -188,8 +188,7 @@ class StochasticRotationDynamics(CollisionMethod): 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 linear momentum, but kinetic energy is of course no longer - conserved. + still conserves linear momentum, but kinetic energy is no longer conserved. Attributes: angle (float): Rotation angle (in degrees) From 71450349194bf776f290af8ea44bba36746a07c3 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 20 Nov 2023 14:53:14 -0600 Subject: [PATCH 14/69] Apply suggestions from code review to unit tests --- hoomd/mpcd/pytest/test_collide.py | 49 ++++++++--- hoomd/mpcd/pytest/test_integrator.py | 123 +++++++++++++++++++++++---- hoomd/mpcd/pytest/test_stream.py | 47 +++++----- 3 files changed, 171 insertions(+), 48 deletions(-) diff --git a/hoomd/mpcd/pytest/test_collide.py b/hoomd/mpcd/pytest/test_collide.py index e10d8a70a3..49029aa51f 100644 --- a/hoomd/mpcd/pytest/test_collide.py +++ b/hoomd/mpcd/pytest/test_collide.py @@ -2,6 +2,7 @@ # Part of HOOMD-blue, released under the BSD 3-Clause License. import hoomd +from hoomd.conftest import pickling_check import pytest @@ -23,14 +24,12 @@ def small_snap(): ( hoomd.mpcd.collide.AndersenThermostat, { - "period": 5, "kT": 1.0, }, ), ( hoomd.mpcd.collide.StochasticRotationDynamics, { - "period": 5, "angle": 90, }, ), @@ -41,28 +40,42 @@ class TestCollisionMethod: def test_create(self, small_snap, simulation_factory, cls, init_args): sim = simulation_factory(small_snap) - cm = cls(**init_args) + cm = cls(period=5, **init_args) ig = hoomd.mpcd.Integrator(dt=0.02, collision_method=cm) sim.operations.integrator = ig - 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"] - ig.collision_method = None sim.run(0) - assert ig.collision_method is None + 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"] - ig.collision_method = cm + 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) - assert ig.collision_method is cm + pickling_check(cm) def test_embed(self, small_snap, simulation_factory, cls, init_args): sim = simulation_factory(small_snap) - cm = cls(**init_args, embedded_particles=hoomd.filter.All()) + 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) @@ -73,29 +86,39 @@ def test_temperature(self, small_snap, simulation_factory, cls, init_args): kT_required = False else: kT_required = True - cm = cls(**init_args) + 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) - cm.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 10) + 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(**init_args) + 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( - **init_args, embedded_particles=hoomd.filter.All()) + period=1, embedded_particles=hoomd.filter.All(), **init_args) sim.run(1) diff --git a/hoomd/mpcd/pytest/test_integrator.py b/hoomd/mpcd/pytest/test_integrator.py index e5413baceb..5d27a99e38 100644 --- a/hoomd/mpcd/pytest/test_integrator.py +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -2,28 +2,121 @@ # Part of HOOMD-blue, released under the BSD 3-Clause License. import hoomd +from hoomd.conftest import pickling_check + import numpy as np import pytest @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 - - -def test_create(small_snap, simulation_factory): - sim = simulation_factory(small_snap) - ig = hoomd.mpcd.Integrator(dt=0.02) +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) + 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_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 + + # attach with both methods + ig.streaming_method = hoomd.mpcd.stream.Bulk(period=1) + ig.collision_method = hoomd.mpcd.collide.StochasticRotationDynamics( + period=1, angle=130) + sim.run(0) + assert ig.streaming_method._attached + assert ig.collision_method._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 + + +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_stream.py b/hoomd/mpcd/pytest/test_stream.py index 56c4621448..ecf66eae96 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -2,6 +2,7 @@ # Part of HOOMD-blue, released under the BSD 3-Clause License. import hoomd +from hoomd.conftest import pickling_check import numpy as np import pytest @@ -41,23 +42,29 @@ def snap(): ], ids=["Bulk", "ParallelPlates", "PlanarPore"], ) -def test_create(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 +class TestStreamingMethod: - sim.run(0) - assert ig.streaming_method is sm - assert sm.period == 5 + 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 - ig.streaming_method = None - sim.run(0) - assert ig.streaming_method is None + assert ig.streaming_method is sm + assert sm.period == 5 + sim.run(0) + assert ig.streaming_method is sm + assert sm.period == 5 - ig.streaming_method = sm - sim.run(0) - assert ig.streaming_method is sm + 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) class TestBulk: @@ -150,7 +157,7 @@ def test_step_noslip(self, simulation_factory, snap): np.testing.assert_array_almost_equal( snap.mpcd.velocity, [[-1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]]) - # take another step, wrapping the second particle through the boundary + # take another step, reflecting the second particle sim.run(1) snap = sim.state.get_snapshot() if snap.communicator.rank == 0: @@ -188,7 +195,7 @@ def test_slip(self, simulation_factory, snap): np.testing.assert_array_almost_equal( snap.mpcd.velocity, [[1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]]) - # take another step, wrapping the second particle through the boundary + # take another step, reflecting the perpendicular motion of second particle sim.run(1) snap = sim.state.get_snapshot() if snap.communicator.rank == 0: @@ -201,7 +208,7 @@ 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 3 steps. It will bounce back in + 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 @@ -212,8 +219,8 @@ def test_step_moving_wall(self, simulation_factory, snap): snap.mpcd.position[:] = [[4.95, -4.95, 3.85], [0.0, 0.0, -3.8]] snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-2.0, -1.0, -1.0]] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(period=3, H=4, V=1, no_slip=True) - ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=4, V=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 @@ -238,7 +245,7 @@ def test_validate_box(self, simulation_factory, snap): def test_test_of_bounds(self, simulation_factory, snap): """Test box validation raises an error on run.""" if snap.communicator.rank == 0: - snap.mpcd.position[:] = [[4.95, -4.95, 3.85]] + snap.mpcd.position[0] = [4.95, -4.95, 3.85] sim = simulation_factory(snap) sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=3.8) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) From 0294b8c052116c2319df43a1c7dd2e09489fccdf Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 20 Nov 2023 15:58:21 -0600 Subject: [PATCH 15/69] Link in sphinx documentation and revise --- hoomd/mpcd/__init__.py | 63 +++++++-------------- hoomd/mpcd/collide.py | 63 +++++++++++---------- hoomd/mpcd/integrate.py | 49 ++++++++++++++-- hoomd/mpcd/stream.py | 90 +++++++++++++++--------------- sphinx-doc/index.rst | 1 + sphinx-doc/module-mpcd-collide.rst | 27 +++++++++ sphinx-doc/module-mpcd-stream.rst | 27 +++++++++ sphinx-doc/package-mpcd.rst | 20 +++++++ 8 files changed, 219 insertions(+), 121 deletions(-) create mode 100644 sphinx-doc/module-mpcd-collide.rst create mode 100644 sphinx-doc/module-mpcd-stream.rst create mode 100644 sphinx-doc/package-mpcd.rst diff --git a/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index 50f492fd56..1204ba2718 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -1,10 +1,10 @@ # Copyright (c) 2009-2023 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 +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, and deformable materials like cells. One way to overcome this challenge is to simplify the model @@ -30,57 +30,36 @@ 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 + * :class:`~hoomd.mpcd.collide.StochasticRotationDynamics` + * :class:`~hoomd.mpcd.collide.AndersenThermostat` 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. +a thermostat. -Details of this implementation of the MPCD algorithm for HOOMD-blue can be found +The solvent 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) +`hoomd.md`. Getting started can look like: + + 1. Initialize the solvent through `Snapshot.mpcd`. You can include any solute + particles in the snapshot as well. + 2. Create the MPCD `Integrator`. + 3. Choose the streaming method from :mod:`.mpcd.stream`. + 4. Choose the collision rule from :mod:`.mpcd.collide`. + 5. Setup solute particle integration methods and interactions as you normally + would to use `hoomd.md`. Note that there are some restrictions on which + features are compatible with MPCD. + 6. Run your simulation! """ diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 6c972e1096..b4a91d6595 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -27,20 +27,22 @@ class CellList(AutotunedObject): cell_size (float): Size of a collision cell. shift (bool): When True, randomly shift underlying collision cells. + 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`. + + 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. + Attributes: cell_size (float): Edge length of a collision cell. - Each collision cell is a cube. The box must be orthorhombic with - each edge length a multiple of `cell_size`. - shift (bool): When True, randomly shift underlying collision cells. - When the total mean-free path of the MPCD particles is small, the - underlying MPCD cell list should be randomly shifted in order to - ensure Galilean invariance. The performance penalty from grid - shifting is small, so it is recommended to enable it in all - simulations. - """ def __init__(self, cell_size, shift=True): @@ -80,17 +82,17 @@ class CollisionMethod(AutotunedObject): You will need to create an appropriate method to integrate the positions of these particles. The recommended integrator is - `~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. + :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. period (int): Number of integration steps between collisions. - A collision is executed each time the `~hoomd.Simulation.timestep` + 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 - `~hoomd.mpcd.stream.StreamingMethod` if one is attached to the - `~hoomd.mpcd.Integrator`. + :class:`~hoomd.mpcd.stream.StreamingMethod` if one is attached to + the :class:`~hoomd.mpcd.Integrator`. """ @@ -113,13 +115,13 @@ class AndersenThermostat(CollisionMethod): period (int): Number of integration steps between collisions. kT (hoomd.variant.variant_like): Temperature of the solvent :math:`[\mathrm{energy}]`. - embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to include in - collision. + embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to + include in collision. This class implements the Andersen thermostat collision rule for MPCD, as described by `Allahyarov and Gompper - `_. Every `period` steps, the + `_. Every ``period`` steps, the particles are binned into cells. New particle velocities are then randomly drawn from a Gaussian distribution relative to the center-of-mass velocity for the cell. The random velocities are given zero-mean so that the cell @@ -166,24 +168,25 @@ class StochasticRotationDynamics(CollisionMethod): period (int): Number of integration steps between collisions. angle (float): Rotation angle (in degrees). kT (hoomd.variant.variant_like): Temperature for the collision - thermostat :`[\mathrm{energy}]`. If None, no thermostat is used. - embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to include in - 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 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 particle velocities are then rotated by `angle` + rule for MPCD proposed by `Malevanets and Kapral + `_. Every ``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 + the MPCD particles and the `embedded_particles`. 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 diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index 9f9c4f071f..bbf3aa20f3 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -14,13 +14,44 @@ class Integrator(_MDIntegrator): """MPCD integrator. + Args: + dt (float): Integrator time step size :math:`[\mathrm{time}]`. + + methods (Sequence[hoomd.md.methods.Method]): Sequence of integration + methods. The default value of ``None`` initializes an empty list. + + 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. + + integrate_rotational_dof (bool): When True, integrate rotational degrees + of freedom. + + 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 solvent. + + collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method + for the MPCD solvent and any embedded particles. + The MPCD `Integrator` enables the MPCD algorithm concurrently with standard MD methods. - In MPCD simulations, `dt` defines the amount of time that the system is + 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 + 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:: @@ -33,6 +64,14 @@ class Integrator(_MDIntegrator): The MD particles can be read at any time step because their positions are updated every step. + + Attributes: + streaming_method (hoomd.mpcd.stream.StreamingMethod): Streaming method + for the MPCD solvent. + + collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method + for the MPCD solvent and any embedded particles. + """ def __init__( @@ -70,9 +109,9 @@ def __init__( @property def cell_list(self): - """hoomd.mpcd.CellList: Collision cell list. + """hoomd.mpcd.collide.CellList: Collision cell list. - A `~hoomd.mpcd.CellList` is automatically created with each `Integrator` + A `CellList` is automatically created with each `Integrator` using typical defaults of cell size 1 and random grid shifting enabled. You can change this configuration if desired. diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index 591cb1f680..5285903adc 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -4,10 +4,10 @@ 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 `.mpcd.Integrator` -and `.mpcd.collide.CollisionMethod`. 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,34 +15,35 @@ \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. - 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 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) + +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`. + +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. + 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 to ensure all particles are + "inside" the geometry. An error will be raised otherwise. """ @@ -62,10 +63,11 @@ class StreamingMethod(AutotunedObject): Attributes: period (int): Number of integration steps covered by streaming step. - The MPCD particles will be streamed every time the `~hoomd.Simulation` - timestep is a multiple of `period`. The streaming time is hence equal - to `period` steps of the `~hoomd.mpcd.Integrator`. Typically `period` - should be equal to `~hoomd.mpcd.CollisionMethod.period` for the + 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. @@ -86,7 +88,8 @@ class Bulk(StreamingMethod): period (int): Number of integration steps covered by streaming step. `Bulk` streams the MPCD particles in a fully periodic geometry (2D or 3D). - This geometry is appropriate for modeling bulk fluids. + This geometry is appropriate for modeling bulk fluids, i.e., those that + are not confined by any surfaces. """ @@ -118,13 +121,12 @@ class ParallelPlates(StreamingMethod): no_slip (bool): If True, plates have no-slip boundary condition. Otherwise, they have the slip boundary condition. - `Slit` streams the MPCD particles 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 :math:`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. + `ParallelPlates` streams the MPCD particles between two infinite parallel + plates centered around the origin. The plates are placed at :math:`z=-H` + and :math:`z=+H`, so the total channel width is :math:`2H`. The plates 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. Attributes: H (float): Channel half-width. diff --git a/sphinx-doc/index.rst b/sphinx-doc/index.rst index 2daa36d618..cb6150232b 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/module-mpcd-collide.rst b/sphinx-doc/module-mpcd-collide.rst new file mode 100644 index 0000000000..db4991f2be --- /dev/null +++ b/sphinx-doc/module-mpcd-collide.rst @@ -0,0 +1,27 @@ +.. Copyright (c) 2009-2023 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-stream.rst b/sphinx-doc/module-mpcd-stream.rst new file mode 100644 index 0000000000..782f63357c --- /dev/null +++ b/sphinx-doc/module-mpcd-stream.rst @@ -0,0 +1,27 @@ +.. Copyright (c) 2009-2023 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 + ParallelPlates + PlanarPore + StreamingMethod + +.. rubric:: Details + +.. automodule:: hoomd.mpcd.stream + :synopsis: Streaming methods. + :members: StreamingMethod, + Bulk, + ParallelPlates, + PlanarPore + :show-inheritance: diff --git a/sphinx-doc/package-mpcd.rst b/sphinx-doc/package-mpcd.rst new file mode 100644 index 0000000000..8f71684890 --- /dev/null +++ b/sphinx-doc/package-mpcd.rst @@ -0,0 +1,20 @@ +.. Copyright (c) 2009-2023 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-stream From eba77b1d66711563e1795b91cd31bad0609423ab Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 4 Dec 2023 13:03:43 -0600 Subject: [PATCH 16/69] Fix lists in documentation --- hoomd/mpcd/__init__.py | 21 ++++++++++----------- hoomd/mpcd/collide.py | 5 +++++ hoomd/mpcd/stream.py | 22 +++++++++++----------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index 1204ba2718..ded63b5cc5 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -30,8 +30,8 @@ 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` +* :class:`~hoomd.mpcd.collide.StochasticRotationDynamics` +* :class:`~hoomd.mpcd.collide.AndersenThermostat` Solute particles can be coupled to the solvent during the collision step. This is particularly useful for soft materials like polymers. Standard molecular @@ -51,15 +51,14 @@ MPCD is intended to be used as an add-on to the standard MD methods in `hoomd.md`. Getting started can look like: - 1. Initialize the solvent through `Snapshot.mpcd`. You can include any solute - particles in the snapshot as well. - 2. Create the MPCD `Integrator`. - 3. Choose the streaming method from :mod:`.mpcd.stream`. - 4. Choose the collision rule from :mod:`.mpcd.collide`. - 5. Setup solute particle integration methods and interactions as you normally - would to use `hoomd.md`. Note that there are some restrictions on which - features are compatible with MPCD. - 6. Run your simulation! +1. Initialize the solvent 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 + collision step. +5. Run your simulation! """ diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index b4a91d6595..e6fb1136c2 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -87,6 +87,11 @@ class CollisionMethod(AutotunedObject): MPCD particles themselves already act as a heat bath for the embedded particles. + 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. + period (int): Number of integration steps between collisions. A collision is executed each time the :attr:`~hoomd.Simulation.timestep` diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index 5285903adc..2a9e87afb5 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -33,17 +33,17 @@ 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. - 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 to ensure all particles are - "inside" the geometry. An error will be raised otherwise. +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. +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 to ensure all particles are + "inside" the geometry. An error will be raised otherwise. """ From 5ad7a9de49951ce417dd19b243751ab442fd63c1 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Sat, 16 Dec 2023 22:26:14 -0600 Subject: [PATCH 17/69] Reimplement particle sorter --- hoomd/mpcd/CMakeLists.txt | 2 +- hoomd/mpcd/Integrator.cc | 4 +- hoomd/mpcd/Sorter.cc | 38 +------ hoomd/mpcd/Sorter.h | 29 +---- hoomd/mpcd/SorterGPU.cc | 7 +- hoomd/mpcd/SorterGPU.h | 4 +- hoomd/mpcd/__init__.py | 1 + hoomd/mpcd/integrate.py | 76 ++++++------- hoomd/mpcd/pytest/CMakeLists.txt | 1 + hoomd/mpcd/pytest/test_integrator.py | 27 +++++ hoomd/mpcd/pytest/test_tune.py | 49 ++++++++ hoomd/mpcd/test/sorter_test.cc | 4 +- hoomd/mpcd/tune.py | 57 ++++++++++ hoomd/mpcd/update.py | 163 --------------------------- sphinx-doc/module-mpcd-tune.rst | 21 ++++ sphinx-doc/package-mpcd.rst | 1 + 16 files changed, 210 insertions(+), 274 deletions(-) create mode 100644 hoomd/mpcd/pytest/test_tune.py create mode 100644 hoomd/mpcd/tune.py delete mode 100644 hoomd/mpcd/update.py create mode 100644 sphinx-doc/module-mpcd-tune.rst diff --git a/hoomd/mpcd/CMakeLists.txt b/hoomd/mpcd/CMakeLists.txt index da2b2dcb02..ca1d3c5615 100644 --- a/hoomd/mpcd/CMakeLists.txt +++ b/hoomd/mpcd/CMakeLists.txt @@ -141,7 +141,7 @@ set(files force.py integrate.py stream.py - update.py + tune.py ) install(FILES ${files} diff --git a/hoomd/mpcd/Integrator.cc b/hoomd/mpcd/Integrator.cc index 742d7b0305..2c2366ff7c 100644 --- a/hoomd/mpcd/Integrator.cc +++ b/hoomd/mpcd/Integrator.cc @@ -82,7 +82,7 @@ void mpcd::Integrator::update(uint64_t timestep) } // optionally sort for performance - if (m_sorter) + if (m_sorter && (*m_sorter->getTrigger())(timestep)) m_sorter->update(timestep); // perform the core MPCD steps of collision and streaming @@ -173,7 +173,7 @@ void mpcd::detail::export_Integrator(pybind11::module& m) .def_property("streaming_method", &mpcd::Integrator::getStreamingMethod, &mpcd::Integrator::setStreamingMethod) - .def_property("sorter", &mpcd::Integrator::getSorter, &mpcd::Integrator::setSorter) + .def_property("solvent_sorter", &mpcd::Integrator::getSorter, &mpcd::Integrator::setSorter) .def_property("filler", &mpcd::Integrator::getFiller, &mpcd::Integrator::setFiller); } } // end namespace hoomd diff --git a/hoomd/mpcd/Sorter.cc b/hoomd/mpcd/Sorter.cc index 71d9edd63a..79fe3958e4 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 93ae80d36c..30f009d0b1 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 54f9ee1116..307ec293cc 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 94538a98a2..f7840e1745 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/__init__.py b/hoomd/mpcd/__init__.py index ded63b5cc5..2b7beba69b 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -72,3 +72,4 @@ from hoomd.mpcd import integrate from hoomd.mpcd.integrate import Integrator from hoomd.mpcd import stream +from hoomd.mpcd import tune diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index bbf3aa20f3..d72939527d 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -8,6 +8,7 @@ from hoomd.mpcd import _mpcd from hoomd.mpcd.collide import CellList, CollisionMethod from hoomd.mpcd.stream import StreamingMethod +from hoomd.mpcd.tune import ParticleSorter @hoomd.logging.modify_namespace(("mpcd", "Integrator")) @@ -45,6 +46,9 @@ class Integrator(_MDIntegrator): collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method for the MPCD solvent and any embedded particles. + sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the MPCD + particles. + The MPCD `Integrator` enables the MPCD algorithm concurrently with standard MD methods. @@ -72,6 +76,9 @@ class Integrator(_MDIntegrator): collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method for the MPCD solvent and any embedded particles. + sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the MPCD + particles (recommended). + """ def __init__( @@ -85,6 +92,7 @@ def __init__( half_step_hook=None, streaming_method=None, collision_method=None, + solvent_sorter=None, ): super().__init__( dt, @@ -96,13 +104,16 @@ def __init__( half_step_hook, ) - param_dict = ParameterDict( - streaming_method=OnlyTypes(StreamingMethod, allow_none=True), - collision_method=OnlyTypes(CollisionMethod, allow_none=True), - ) + param_dict = ParameterDict(streaming_method=OnlyTypes(StreamingMethod, + allow_none=True), + collision_method=OnlyTypes(CollisionMethod, + allow_none=True), + solvent_sorter=OnlyTypes(ParticleSorter, + allow_none=True)) param_dict.update( dict(streaming_method=streaming_method, - collision_method=collision_method)) + collision_method=collision_method, + solvent_sorter=solvent_sorter)) self._param_dict.update(param_dict) self._cell_list = CellList(cell_size=1.0, shift=True) @@ -124,6 +135,8 @@ def _attach_hook(self): self.streaming_method._attach(self._simulation) if self.collision_method is not None: self.collision_method._attach(self._simulation) + if self.solvent_sorter is not None: + self.solvent_sorter._attach(self._simulation) self._cpp_obj = _mpcd.Integrator(self._simulation.state._cpp_sys_def, self.dt) @@ -137,45 +150,26 @@ def _detach_hook(self): self.streaming_method._detach() if self.collision_method is not None: self.collision_method._detach() + if self.solvent_sorter is not None: + self.solvent_sorter._detach() super()._detach_hook() def _setattr_param(self, attr, value): - if attr == "streaming_method": - self._set_streaming_method(value) - elif attr == "collision_method": - self._set_collision_method(value) + if attr in ("streaming_method", "collision_method", "solvent_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 which is attached, then 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: super()._setattr_param(attr, value) - - def _set_streaming_method(self, streaming_method): - if streaming_method is self.streaming_method: - return - - if streaming_method is not None and streaming_method._attached: - raise ValueError( - "Cannot attach streaming method to multiple integrators.") - - # if already attached, change out which is attached, then set parameter - if self._attached: - if self.streaming_method is not None: - self.streaming_method._detach() - if streaming_method is not None: - streaming_method._attach(self._simulation) - self._param_dict["streaming_method"] = streaming_method - - def _set_collision_method(self, collision_method): - if collision_method is self.collision_method: - return - - if collision_method is not None and collision_method._attached: - raise ValueError( - "Cannot attach collision method to multiple integrators.") - - # if already attached, change out which is attached, then set parameter - if self._attached: - if self.collision_method is not None: - self.collision_method._detach() - if collision_method is not None: - collision_method._attach(self._simulation) - self._param_dict["collision_method"] = collision_method diff --git a/hoomd/mpcd/pytest/CMakeLists.txt b/hoomd/mpcd/pytest/CMakeLists.txt index 80ebf35269..a8db69e623 100644 --- a/hoomd/mpcd/pytest/CMakeLists.txt +++ b/hoomd/mpcd/pytest/CMakeLists.txt @@ -4,6 +4,7 @@ set(files __init__.py test_integrator.py test_snapshot.py test_stream.py + test_tune.py ) install(FILES ${files} diff --git a/hoomd/mpcd/pytest/test_integrator.py b/hoomd/mpcd/pytest/test_integrator.py index 5d27a99e38..c688202f97 100644 --- a/hoomd/mpcd/pytest/test_integrator.py +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -84,6 +84,30 @@ def test_streaming_method(make_simulation): assert ig.streaming_method is stream +def test_solvent_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, solvent_sorter=sorter) + sim.operations.integrator = ig + assert ig.solvent_sorter is sorter + sim.run(0) + assert ig.solvent_sorter is sorter + + # clear out by setter + ig.solvent_sorter = None + assert ig.solvent_sorter is None + sim.run(0) + assert ig.solvent_sorter is None + + # assign by setter + ig.solvent_sorter = sorter + assert ig.solvent_sorter is sorter + sim.run(0) + assert ig.solvent_sorter is sorter + + def test_attach_and_detach(make_simulation): sim = make_simulation() ig = hoomd.mpcd.Integrator(dt=0.1) @@ -100,9 +124,11 @@ def test_attach_and_detach(make_simulation): ig.streaming_method = hoomd.mpcd.stream.Bulk(period=1) ig.collision_method = hoomd.mpcd.collide.StochasticRotationDynamics( period=1, angle=130) + ig.solvent_sorter = hoomd.mpcd.tune.ParticleSorter(trigger=1) sim.run(0) assert ig.streaming_method._attached assert ig.collision_method._attached + assert ig.solvent_sorter._attached # detach with everything sim.operations._unschedule() @@ -110,6 +136,7 @@ def test_attach_and_detach(make_simulation): assert not ig.cell_list._attached assert not ig.streaming_method._attached assert not ig.collision_method._attached + assert not ig.solvent_sorter._attached def test_pickling(make_simulation): diff --git a/hoomd/mpcd/pytest/test_tune.py b/hoomd/mpcd/pytest/test_tune.py new file mode 100644 index 0000000000..2660893da9 --- /dev/null +++ b/hoomd/mpcd/pytest/test_tune.py @@ -0,0 +1,49 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import hoomd +from hoomd.conftest import pickling_check +import pytest + + +@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, solvent_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, + solvent_sorter=sorter) + sim.run(0) + pickling_check(sorter) diff --git a/hoomd/mpcd/test/sorter_test.cc b/hoomd/mpcd/test/sorter_test.cc index cba2912af1..70b61dea1c 100644 --- a/hoomd/mpcd/test/sorter_test.cc +++ b/hoomd/mpcd/test/sorter_test.cc @@ -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); @@ -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/tune.py b/hoomd/mpcd/tune.py new file mode 100644 index 0000000000..b4a750cb8d --- /dev/null +++ b/hoomd/mpcd/tune.py @@ -0,0 +1,57 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +r"""MPCD tuning operations. + +Tuning methods for the MPCD simulation. These operations will affect performance +but not correctness. + +""" + +import hoomd +from hoomd.mpcd import _mpcd +from hoomd.operation import Tuner + + +class ParticleSorter(Tuner): + r"""MPCD particle sorter. + + Args: + trigger (hoomd.trigger.trigger_like): Number of integration steps + between sorting. + + 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, 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` should be a multiple of :attr:`CollisionMethod.period` to avoid + unnecessary cell list builds. Typically, using a small multiple (tens) of + the collision period works best. + + To avoid further unnecessary cell list builds, this `ParticleSorter` should + **not** be added to `hoomd.Operations.tuners`. Instead, set it in + `hoomd.mpcd.Integrator.sorter`. + + Essentially all MPCD systems benefit from sorting, so it is recommended + to use one for all simulations! + + Attributes: + trigger (hoomd.trigger.Trigger): Number of integration steps + between sorting. + + """ + + 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 e0a6a64c94..0000000000 --- a/hoomd/mpcd/update.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright (c) 2009-2023 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/sphinx-doc/module-mpcd-tune.rst b/sphinx-doc/module-mpcd-tune.rst new file mode 100644 index 0000000000..f90246dc42 --- /dev/null +++ b/sphinx-doc/module-mpcd-tune.rst @@ -0,0 +1,21 @@ +.. Copyright (c) 2009-2023 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/package-mpcd.rst b/sphinx-doc/package-mpcd.rst index 8f71684890..51e3c38db5 100644 --- a/sphinx-doc/package-mpcd.rst +++ b/sphinx-doc/package-mpcd.rst @@ -18,3 +18,4 @@ hoomd.mpcd module-mpcd-collide module-mpcd-stream + module-mpcd-tune From 723d21466b6b454d717df8ff70545aaccee3035c Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Wed, 20 Dec 2023 21:20:59 -0600 Subject: [PATCH 18/69] Improve sorter documentation --- hoomd/mpcd/integrate.py | 14 +++++++------- hoomd/mpcd/tune.py | 29 ++++++++++++++--------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index d72939527d..d72a05a71b 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -46,8 +46,8 @@ class Integrator(_MDIntegrator): collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method for the MPCD solvent and any embedded particles. - sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the MPCD - particles. + solvent_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the + MPCD particles. The MPCD `Integrator` enables the MPCD algorithm concurrently with standard MD methods. @@ -70,14 +70,14 @@ class Integrator(_MDIntegrator): Attributes: - streaming_method (hoomd.mpcd.stream.StreamingMethod): Streaming method - for the MPCD solvent. - collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method for the MPCD solvent and any embedded particles. - sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the MPCD - particles (recommended). + solvent_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the + MPCD particles (recommended). + + streaming_method (hoomd.mpcd.stream.StreamingMethod): Streaming method + for the MPCD solvent. """ diff --git a/hoomd/mpcd/tune.py b/hoomd/mpcd/tune.py index b4a750cb8d..6340540b84 100644 --- a/hoomd/mpcd/tune.py +++ b/hoomd/mpcd/tune.py @@ -3,8 +3,8 @@ r"""MPCD tuning operations. -Tuning methods for the MPCD simulation. These operations will affect performance -but not correctness. +These operations will affect the performance of MPCD simulations but not +their correctness. """ @@ -22,19 +22,18 @@ class ParticleSorter(Tuner): 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, 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` should be a multiple of :attr:`CollisionMethod.period` to avoid - unnecessary cell list builds. Typically, using a small multiple (tens) of - the collision period works best. - - To avoid further unnecessary cell list builds, this `ParticleSorter` should - **not** be added to `hoomd.Operations.tuners`. Instead, set it in - `hoomd.mpcd.Integrator.sorter`. + 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` 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. + + For best performance, the `ParticleSorter` should **not** be added to + `hoomd.Operations.tuners`. Instead, set it in `hoomd.mpcd.Integrator.sorter`. Essentially all MPCD systems benefit from sorting, so it is recommended to use one for all simulations! From 673ebf43c2647cf1d25255995fe727bbba6e3c1f Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 4 Jan 2024 14:00:02 -0600 Subject: [PATCH 19/69] Fix sphinx warning --- hoomd/mpcd/tune.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hoomd/mpcd/tune.py b/hoomd/mpcd/tune.py index 6340540b84..31dfc9682f 100644 --- a/hoomd/mpcd/tune.py +++ b/hoomd/mpcd/tune.py @@ -33,7 +33,7 @@ class ParticleSorter(Tuner): (tens) of the collision period works best. For best performance, the `ParticleSorter` should **not** be added to - `hoomd.Operations.tuners`. Instead, set it in `hoomd.mpcd.Integrator.sorter`. + `hoomd.Operations.tuners`. Instead, set it in `hoomd.mpcd.Integrator.solvent_sorter`. Essentially all MPCD systems benefit from sorting, so it is recommended to use one for all simulations! From a24e30340d662c65d54dcddf85bc95e773c13947 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 4 Jan 2024 13:47:13 -0600 Subject: [PATCH 20/69] Refactor geometries and streaming methods --- hoomd/RNGIdentifiers.h | 4 +- hoomd/mpcd/BounceBackNVEGPU.cu | 8 +- ...ngMethod.h => BounceBackStreamingMethod.h} | 38 +++--- hoomd/mpcd/BounceBackStreamingMethodGPU.cu | 39 ++++++ ...U.cuh => BounceBackStreamingMethodGPU.cuh} | 4 +- ...odGPU.h => BounceBackStreamingMethodGPU.h} | 35 ++--- hoomd/mpcd/BoundaryCondition.h | 34 ----- hoomd/mpcd/BulkGeometry.h | 4 +- hoomd/mpcd/BulkStreamingMethod.cc | 28 ++++ hoomd/mpcd/BulkStreamingMethod.h | 46 +++++++ hoomd/mpcd/BulkStreamingMethodGPU.cc | 28 ++++ hoomd/mpcd/BulkStreamingMethodGPU.cu | 29 ++++ hoomd/mpcd/BulkStreamingMethodGPU.h | 46 +++++++ hoomd/mpcd/CMakeLists.txt | 43 +++--- hoomd/mpcd/ConfinedStreamingMethodGPU.cu | 43 ------ ...SlitGeometry.h => ParallelPlateGeometry.h} | 36 +++-- ...ller.cc => ParallelPlateGeometryFiller.cc} | 41 +++--- ...Filler.h => ParallelPlateGeometryFiller.h} | 36 ++--- ...U.cc => ParallelPlateGeometryFillerGPU.cc} | 28 ++-- ...U.cu => ParallelPlateGeometryFillerGPU.cu} | 12 +- ...cuh => ParallelPlateGeometryFillerGPU.cuh} | 10 +- hoomd/mpcd/ParallelPlateGeometryFillerGPU.h | 50 +++++++ ...litPoreGeometry.h => PlanarPoreGeometry.h} | 39 +++--- ...yFiller.cc => PlanarPoreGeometryFiller.cc} | 38 +++--- ...tryFiller.h => PlanarPoreGeometryFiller.h} | 32 ++--- ...rGPU.cc => PlanarPoreGeometryFillerGPU.cc} | 29 ++-- ...rGPU.cu => PlanarPoreGeometryFillerGPU.cu} | 8 +- ...PU.cuh => PlanarPoreGeometryFillerGPU.cuh} | 8 +- ...lerGPU.h => PlanarPoreGeometryFillerGPU.h} | 26 ++-- hoomd/mpcd/SlitGeometryFillerGPU.h | 50 ------- hoomd/mpcd/StreamingGeometry.cc | 40 +++--- hoomd/mpcd/StreamingGeometry.h | 19 +-- hoomd/mpcd/__init__.py | 1 + hoomd/mpcd/geometry.py | 105 +++++++++++++++ hoomd/mpcd/module.cc | 48 +++---- hoomd/mpcd/pytest/CMakeLists.txt | 1 + hoomd/mpcd/pytest/test_geometry.py | 88 ++++++++++++ hoomd/mpcd/pytest/test_stream.py | 53 +++++--- hoomd/mpcd/stream.py | 127 +++++------------- hoomd/mpcd/test/CMakeLists.txt | 8 +- ...arallel_plate_geometry_filler_mpi_test.cc} | 21 ++- ...=> parallel_plate_geometry_filler_test.cc} | 21 ++- ...> planar_pore_geometry_filler_mpi_test.cc} | 21 ++- ...cc => planar_pore_geometry_filler_test.cc} | 22 ++- hoomd/mpcd/test/streaming_method_test.cc | 14 +- 45 files changed, 865 insertions(+), 596 deletions(-) rename hoomd/mpcd/{ConfinedStreamingMethod.h => BounceBackStreamingMethod.h} (84%) create mode 100644 hoomd/mpcd/BounceBackStreamingMethodGPU.cu rename hoomd/mpcd/{ConfinedStreamingMethodGPU.cuh => BounceBackStreamingMethodGPU.cuh} (97%) rename hoomd/mpcd/{ConfinedStreamingMethodGPU.h => BounceBackStreamingMethodGPU.h} (74%) delete mode 100644 hoomd/mpcd/BoundaryCondition.h create mode 100644 hoomd/mpcd/BulkStreamingMethod.cc create mode 100644 hoomd/mpcd/BulkStreamingMethod.h create mode 100644 hoomd/mpcd/BulkStreamingMethodGPU.cc create mode 100644 hoomd/mpcd/BulkStreamingMethodGPU.cu create mode 100644 hoomd/mpcd/BulkStreamingMethodGPU.h delete mode 100644 hoomd/mpcd/ConfinedStreamingMethodGPU.cu rename hoomd/mpcd/{SlitGeometry.h => ParallelPlateGeometry.h} (89%) rename hoomd/mpcd/{SlitGeometryFiller.cc => ParallelPlateGeometryFiller.cc} (78%) rename hoomd/mpcd/{SlitGeometryFiller.h => ParallelPlateGeometryFiller.h} (50%) rename hoomd/mpcd/{SlitGeometryFillerGPU.cc => ParallelPlateGeometryFillerGPU.cc} (73%) rename hoomd/mpcd/{SlitGeometryFillerGPU.cu => ParallelPlateGeometryFillerGPU.cu} (94%) rename hoomd/mpcd/{SlitGeometryFillerGPU.cuh => ParallelPlateGeometryFillerGPU.cuh} (82%) create mode 100644 hoomd/mpcd/ParallelPlateGeometryFillerGPU.h rename hoomd/mpcd/{SlitPoreGeometry.h => PlanarPoreGeometry.h} (88%) rename hoomd/mpcd/{SlitPoreGeometryFiller.cc => PlanarPoreGeometryFiller.cc} (86%) rename hoomd/mpcd/{SlitPoreGeometryFiller.h => PlanarPoreGeometryFiller.h} (61%) rename hoomd/mpcd/{SlitPoreGeometryFillerGPU.cc => PlanarPoreGeometryFillerGPU.cc} (75%) rename hoomd/mpcd/{SlitPoreGeometryFillerGPU.cu => PlanarPoreGeometryFillerGPU.cu} (96%) rename hoomd/mpcd/{SlitPoreGeometryFillerGPU.cuh => PlanarPoreGeometryFillerGPU.cuh} (87%) rename hoomd/mpcd/{SlitPoreGeometryFillerGPU.h => PlanarPoreGeometryFillerGPU.h} (50%) delete mode 100644 hoomd/mpcd/SlitGeometryFillerGPU.h create mode 100644 hoomd/mpcd/geometry.py create mode 100644 hoomd/mpcd/pytest/test_geometry.py rename hoomd/mpcd/test/{slit_geometry_filler_mpi_test.cc => parallel_plate_geometry_filler_mpi_test.cc} (84%) rename hoomd/mpcd/test/{slit_geometry_filler_test.cc => parallel_plate_geometry_filler_test.cc} (91%) rename hoomd/mpcd/test/{slit_pore_geometry_filler_mpi_test.cc => planar_pore_geometry_filler_mpi_test.cc} (84%) rename hoomd/mpcd/test/{slit_pore_geometry_filler_test.cc => planar_pore_geometry_filler_test.cc} (91%) diff --git a/hoomd/RNGIdentifiers.h b/hoomd/RNGIdentifiers.h index 4961bb16e5..b2ab931bac 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; diff --git a/hoomd/mpcd/BounceBackNVEGPU.cu b/hoomd/mpcd/BounceBackNVEGPU.cu index 1e76c9dd60..63e85f1fab 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/ConfinedStreamingMethod.h b/hoomd/mpcd/BounceBackStreamingMethod.h similarity index 84% rename from hoomd/mpcd/ConfinedStreamingMethod.h rename to hoomd/mpcd/BounceBackStreamingMethod.h index 896953b46a..3948c08cbd 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,7 +25,7 @@ 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. * * 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 @@ -41,7 +41,7 @@ namespace mpcd * */ template -class PYBIND11_EXPORT ConfinedStreamingMethod : public mpcd::StreamingMethod +class PYBIND11_EXPORT BounceBackStreamingMethod : public mpcd::StreamingMethod { public: //! Constructor @@ -52,11 +52,11 @@ class PYBIND11_EXPORT ConfinedStreamingMethod : public mpcd::StreamingMethod * \param phase Phase shift for periodic updates * \param geom Streaming geometry */ - ConfinedStreamingMethod(std::shared_ptr sysdef, - unsigned int cur_timestep, - unsigned int period, - int phase, - std::shared_ptr geom) + BounceBackStreamingMethod(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) { @@ -92,7 +92,7 @@ class PYBIND11_EXPORT ConfinedStreamingMethod : public mpcd::StreamingMethod /*! * \param timestep Current time to stream */ -template void ConfinedStreamingMethod::stream(uint64_t timestep) +template void BounceBackStreamingMethod::stream(uint64_t timestep) { if (!shouldStream(timestep)) return; @@ -162,14 +162,14 @@ template void ConfinedStreamingMethod::stream(uint64_t m_mpcd_pdata->invalidateCellCache(); } -template void ConfinedStreamingMethod::validate() +template void BounceBackStreamingMethod::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 " + m_exec_conf->msg->error() << "BounceBackStreamingMethod: box too small for " << Geometry::getName() << " geometry. Increase box size." << std::endl; throw std::runtime_error("Simulation box too small for confined streaming method"); @@ -194,7 +194,7 @@ template void ConfinedStreamingMethod::validate() * 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::validateParticles() { ArrayHandle h_pos(m_mpcd_pdata->getPositions(), access_location::host, @@ -226,20 +226,18 @@ 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(); + 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); + .def_property_readonly("geometry", &mpcd::BounceBackStreamingMethod::getGeometry); } } // end namespace detail } // end namespace mpcd diff --git a/hoomd/mpcd/BounceBackStreamingMethodGPU.cu b/hoomd/mpcd/BounceBackStreamingMethodGPU.cu new file mode 100644 index 0000000000..0baa68fdae --- /dev/null +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cu @@ -0,0 +1,39 @@ +// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BounceBackStreamingMethodGPU.cu + * \brief Defines GPU functions and kernels used by mpcd::BounceBackStreamingMethodGPU + * + * \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 "BounceBackStreamingMethodGPU.cuh" +#include "StreamingGeometry.h" + +#include "ExternalField.h" +#include "hoomd/GPUPolymorph.cuh" + +namespace hoomd + { +namespace mpcd + { +namespace gpu + { + +//! Template instantiation of parallel plate geometry streaming +template cudaError_t __attribute__((visibility("default"))) +confined_stream(const stream_args_t& args, + const mpcd::ParallelPlateGeometry& geom); + +//! Template instantiation of planar pore geometry streaming +template cudaError_t __attribute__((visibility("default"))) +confined_stream(const stream_args_t& args, + const mpcd::PlanarPoreGeometry& geom); + + } // end namespace gpu + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/ConfinedStreamingMethodGPU.cuh b/hoomd/mpcd/BounceBackStreamingMethodGPU.cuh similarity index 97% rename from hoomd/mpcd/ConfinedStreamingMethodGPU.cuh rename to hoomd/mpcd/BounceBackStreamingMethodGPU.cuh index 2aeec76b72..4aee515faf 100644 --- a/hoomd/mpcd/ConfinedStreamingMethodGPU.cuh +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cuh @@ -5,8 +5,8 @@ #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" diff --git a/hoomd/mpcd/ConfinedStreamingMethodGPU.h b/hoomd/mpcd/BounceBackStreamingMethodGPU.h similarity index 74% rename from hoomd/mpcd/ConfinedStreamingMethodGPU.h rename to hoomd/mpcd/BounceBackStreamingMethodGPU.h index 586b1e10c6..3446a256f8 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 @@ -27,7 +27,8 @@ namespace mpcd * particles in a confined geometry. */ template -class PYBIND11_EXPORT ConfinedStreamingMethodGPU : public mpcd::ConfinedStreamingMethod +class PYBIND11_EXPORT BounceBackStreamingMethodGPU + : public mpcd::BounceBackStreamingMethod { public: //! Constructor @@ -38,12 +39,12 @@ class PYBIND11_EXPORT ConfinedStreamingMethodGPU : public mpcd::ConfinedStreamin * \param phase Phase shift for periodic updates * \param geom Streaming geometry */ - 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) + : mpcd::BounceBackStreamingMethod(sysdef, cur_timestep, period, phase, geom) { m_tuner.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(this->m_exec_conf)}, this->m_exec_conf, @@ -61,7 +62,7 @@ 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; @@ -111,12 +112,12 @@ 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() + "GPU"; + pybind11::class_, + mpcd::BounceBackStreamingMethod, + std::shared_ptr>>(m, name.c_str()) .def(pybind11::init, unsigned int, unsigned int, diff --git a/hoomd/mpcd/BoundaryCondition.h b/hoomd/mpcd/BoundaryCondition.h deleted file mode 100644 index 350346edb9..0000000000 --- a/hoomd/mpcd/BoundaryCondition.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2009-2023 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 61ad12edb7..06b3dfd418 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 { diff --git a/hoomd/mpcd/BulkStreamingMethod.cc b/hoomd/mpcd/BulkStreamingMethod.cc new file mode 100644 index 0000000000..43f46132c4 --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethod.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +#include "BulkStreamingMethod.h" + +namespace hoomd + { +mpcd::BulkStreamingMethod::BulkStreamingMethod(std::shared_ptr sysdef, + unsigned int cur_timestep, + unsigned int period, + int phase) + : mpcd::BounceBackStreamingMethod( + sysdef, + cur_timestep, + period, + phase, + std::make_shared()) + { + } + +void mpcd::detail::export_BulkStreamingMethod(pybind11::module& m) + { + pybind11::class_>(m, "BulkStreamingMethod") + .def(pybind11::init, unsigned int, unsigned int, int>()); + } + } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethod.h b/hoomd/mpcd/BulkStreamingMethod.h new file mode 100644 index 0000000000..30b68ac692 --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethod.h @@ -0,0 +1,46 @@ +// Copyright (c) 2009-2023 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 + { + +class PYBIND11_EXPORT BulkStreamingMethod + : public BounceBackStreamingMethod + { + public: + BulkStreamingMethod(std::shared_ptr sysdef, + unsigned int cur_timestep, + unsigned int period, + int phase); + }; + +namespace detail + { +//! Export mpcd::BulkStreamingMethod to python +/*! + * \param m Python module to export to + */ +void export_BulkStreamingMethod(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd + +#endif // MPCD_BULK_STREAMING_METHOD_H_ diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cc b/hoomd/mpcd/BulkStreamingMethodGPU.cc new file mode 100644 index 0000000000..3df88075a8 --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +#include "BulkStreamingMethodGPU.h" + +namespace hoomd + { +mpcd::BulkStreamingMethodGPU::BulkStreamingMethodGPU(std::shared_ptr sysdef, + unsigned int cur_timestep, + unsigned int period, + int phase) + : mpcd::BounceBackStreamingMethodGPU( + sysdef, + cur_timestep, + period, + phase, + std::make_shared()) + { + } + +void mpcd::detail::export_BulkStreamingMethodGPU(pybind11::module& m) + { + pybind11::class_>(m, "BulkStreamingMethodGPU") + .def(pybind11::init, unsigned int, unsigned int, int>()); + } + } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cu b/hoomd/mpcd/BulkStreamingMethodGPU.cu new file mode 100644 index 0000000000..d63c493794 --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cu @@ -0,0 +1,29 @@ +// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BulkStreamingMethodGPU.cu + * \brief Defines GPU functions and kernels used by mpcd::BulkStreamingMethodGPU + */ + +#include "BounceBackStreamingMethodGPU.cuh" +#include "BulkGeometry.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); + + } // 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..57df4254e0 --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethodGPU.h @@ -0,0 +1,46 @@ +// Copyright (c) 2009-2023 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 + { + +class PYBIND11_EXPORT BulkStreamingMethodGPU + : public BounceBackStreamingMethodGPU + { + public: + BulkStreamingMethodGPU(std::shared_ptr sysdef, + unsigned int cur_timestep, + unsigned int period, + int phase); + }; + +namespace detail + { +//! Export mpcd::BulkStreamingMethodGPU to python +/*! + * \param m Python module to export to + */ +void export_BulkStreamingMethodGPU(pybind11::module& m); + } // 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 ca1d3c5615..0bb7d19060 100644 --- a/hoomd/mpcd/CMakeLists.txt +++ b/hoomd/mpcd/CMakeLists.txt @@ -6,6 +6,7 @@ endif(NOT BUILD_MD) set(_mpcd_sources module.cc ATCollisionMethod.cc + BulkStreamingMethod.cc CellCommunicator.cc CellThermoCompute.cc CellList.cc @@ -13,8 +14,8 @@ set(_mpcd_sources Communicator.cc ExternalField.cc Integrator.cc - SlitGeometryFiller.cc - SlitPoreGeometryFiller.cc + ParallelPlateGeometryFiller.cc + PlanarPoreGeometryFiller.cc Sorter.cc SRDCollisionMethod.cc StreamingGeometry.cc @@ -25,13 +26,13 @@ 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 @@ -39,10 +40,10 @@ set(_mpcd_headers 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 @@ -53,11 +54,12 @@ set(_mpcd_headers if (ENABLE_HIP) list(APPEND _mpcd_sources ATCollisionMethodGPU.cc + BulkStreamingMethodGPU.cc CellThermoComputeGPU.cc CellListGPU.cc CommunicatorGPU.cc - SlitGeometryFillerGPU.cc - SlitPoreGeometryFillerGPU.cc + ParallelPlateGeometryFillerGPU.cc + PlanarPoreGeometryFillerGPU.cc SorterGPU.cc SRDCollisionMethodGPU.cc ) @@ -66,6 +68,7 @@ list(APPEND _mpcd_headers ATCollisionMethodGPU.h BounceBackNVEGPU.cuh BounceBackNVEGPU.h + BulkStreamingMethodGPU.h CellCommunicator.cuh CellThermoComputeGPU.cuh CellThermoComputeGPU.h @@ -73,13 +76,13 @@ list(APPEND _mpcd_headers CellListGPU.h CommunicatorGPU.cuh CommunicatorGPU.h - ConfinedStreamingMethodGPU.cuh - ConfinedStreamingMethodGPU.h + BounceBackStreamingMethodGPU.cuh + BounceBackStreamingMethodGPU.h ParticleData.cuh - SlitGeometryFillerGPU.cuh - SlitGeometryFillerGPU.h - SlitPoreGeometryFillerGPU.cuh - SlitPoreGeometryFillerGPU.h + ParallelPlateGeometryFillerGPU.cuh + ParallelPlateGeometryFillerGPU.h + PlanarPoreGeometryFillerGPU.cuh + PlanarPoreGeometryFillerGPU.h SorterGPU.cuh SorterGPU.h SRDCollisionMethodGPU.cuh @@ -90,14 +93,15 @@ endif() set(_mpcd_cu_sources ATCollisionMethodGPU.cu BounceBackNVEGPU.cu + BulkStreamingMethodGPU.cu CellThermoComputeGPU.cu CellListGPU.cu - ConfinedStreamingMethodGPU.cu + BounceBackStreamingMethodGPU.cu CommunicatorGPU.cu ExternalField.cu ParticleData.cu - SlitGeometryFillerGPU.cu - SlitPoreGeometryFillerGPU.cu + ParallelPlateGeometryFillerGPU.cu + PlanarPoreGeometryFillerGPU.cu SorterGPU.cu SRDCollisionMethodGPU.cu ) @@ -139,6 +143,7 @@ set(files __init__.py collide.py force.py + geometry.py integrate.py stream.py tune.py diff --git a/hoomd/mpcd/ConfinedStreamingMethodGPU.cu b/hoomd/mpcd/ConfinedStreamingMethodGPU.cu deleted file mode 100644 index 0488ae048d..0000000000 --- a/hoomd/mpcd/ConfinedStreamingMethodGPU.cu +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2009-2023 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/SlitGeometry.h b/hoomd/mpcd/ParallelPlateGeometry.h similarity index 89% rename from hoomd/mpcd/SlitGeometry.h rename to hoomd/mpcd/ParallelPlateGeometry.h index 9c404e921c..1464451ff1 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 @@ -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 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 H, Scalar V, bool no_slip) + : m_H(H), m_V(V), m_no_slip(no_slip) + { + } //! Detect collision between the particle and the boundary /*! @@ -114,7 +113,7 @@ class __attribute__((visibility("default"))) SlitGeometry // 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; @@ -173,28 +172,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 78% rename from hoomd/mpcd/SlitGeometryFiller.cc rename to hoomd/mpcd/ParallelPlateGeometryFiller.cc index 25c3e37341..4289b0c6eb 100644 --- a/hoomd/mpcd/SlitGeometryFiller.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.cc @@ -2,32 +2,33 @@ // 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::ParallelPlateGeometryFiller::ParallelPlateGeometryFiller( + 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) { - 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(); @@ -76,7 +77,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, @@ -102,7 +103,7 @@ void mpcd::SlitGeometryFiller::drawParticles(uint64_t timestep) { const unsigned int tag = m_first_tag + i; hoomd::RandomGenerator rng( - hoomd::Seed(hoomd::RNGIdentifier::SlitGeometryFiller, timestep, seed), + hoomd::Seed(hoomd::RNGIdentifier::ParallelPlateGeometryFiller, timestep, seed), hoomd::Counter(tag)); signed char sign = (char)((i >= m_N_lo) - (i < m_N_lo)); if (sign == -1) // bottom @@ -139,17 +140,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, Scalar, unsigned int, std::shared_ptr, - std::shared_ptr>()) - .def("setGeometry", &mpcd::SlitGeometryFiller::setGeometry); + std::shared_ptr>()) + .def("setGeometry", &mpcd::ParallelPlateGeometryFiller::setGeometry); } } // end namespace hoomd diff --git a/hoomd/mpcd/SlitGeometryFiller.h b/hoomd/mpcd/ParallelPlateGeometryFiller.h similarity index 50% rename from hoomd/mpcd/SlitGeometryFiller.h rename to hoomd/mpcd/ParallelPlateGeometryFiller.h index 8b12e0872e..7372e6a9d2 100644 --- a/hoomd/mpcd/SlitGeometryFiller.h +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.h @@ -2,18 +2,18 @@ // 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. + * \file mpcd/ParallelPlateGeometryFiller.h + * \brief Definition of virtual particle filler for mpcd::ParallelPlateGeometry. */ -#ifndef MPCD_SLIT_GEOMETRY_FILLER_H_ -#define MPCD_SLIT_GEOMETRY_FILLER_H_ +#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 "SlitGeometry.h" +#include "ParallelPlateGeometry.h" #include "VirtualParticleFiller.h" #include @@ -22,29 +22,29 @@ namespace hoomd { namespace mpcd { -//! Adds virtual particles to the MPCD particle data for SlitGeometry +//! 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 SlitGeometryFiller : public mpcd::VirtualParticleFiller +class PYBIND11_EXPORT ParallelPlateGeometryFiller : public mpcd::VirtualParticleFiller { public: - SlitGeometryFiller(std::shared_ptr sysdef, - Scalar density, - unsigned int type, - std::shared_ptr T, - std::shared_ptr geom); + ParallelPlateGeometryFiller(std::shared_ptr sysdef, + Scalar density, + unsigned int type, + std::shared_ptr T, + std::shared_ptr geom); - virtual ~SlitGeometryFiller(); + virtual ~ParallelPlateGeometryFiller(); - void setGeometry(std::shared_ptr geom) + void setGeometry(std::shared_ptr geom) { m_geom = geom; } protected: - std::shared_ptr m_geom; + 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 @@ -59,9 +59,9 @@ class PYBIND11_EXPORT SlitGeometryFiller : public mpcd::VirtualParticleFiller namespace detail { -//! Export SlitGeometryFiller to python -void export_SlitGeometryFiller(pybind11::module& m); +//! Export ParallelPlateGeometryFiller to python +void export_ParallelPlateGeometryFiller(pybind11::module& m); } // end namespace detail } // end namespace mpcd } // end namespace hoomd -#endif // MPCD_SLIT_GEOMETRY_FILLER_H_ +#endif // MPCD_PARALLEL_PLATE_GEOMETRY_FILLER_H_ diff --git a/hoomd/mpcd/SlitGeometryFillerGPU.cc b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc similarity index 73% rename from hoomd/mpcd/SlitGeometryFillerGPU.cc rename to hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc index d884512ba6..4d9cd7c22f 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, 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, density, type, 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, @@ -70,16 +70,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, 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 94% rename from hoomd/mpcd/SlitGeometryFillerGPU.cu rename to hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu index eb45ff4ed7..066d595843 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" @@ -45,7 +45,7 @@ namespace kernel __global__ void slit_draw_particles(Scalar4* d_pos, Scalar4* d_vel, unsigned int* d_tag, - const mpcd::detail::SlitGeometry geom, + const mpcd::ParallelPlateGeometry geom, const Scalar z_min, const Scalar z_max, const BoxDim box, @@ -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), @@ -129,7 +129,7 @@ __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 mpcd::ParallelPlateGeometry& geom, const Scalar z_min, const Scalar z_max, const BoxDim& box, diff --git a/hoomd/mpcd/SlitGeometryFillerGPU.cuh b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cuh similarity index 82% rename from hoomd/mpcd/SlitGeometryFillerGPU.cuh rename to hoomd/mpcd/ParallelPlateGeometryFillerGPU.cuh index 46dd85cecf..65ee74ecfe 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,11 +21,11 @@ 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 mpcd::ParallelPlateGeometry& geom, const Scalar z_min, const Scalar z_max, const BoxDim& box, diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h new file mode 100644 index 0000000000..82ea62dcd2 --- /dev/null +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h @@ -0,0 +1,50 @@ +// Copyright (c) 2009-2023 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, + 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 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/SlitPoreGeometry.h b/hoomd/mpcd/PlanarPoreGeometry.h similarity index 88% rename from hoomd/mpcd/SlitPoreGeometry.h rename to hoomd/mpcd/PlanarPoreGeometry.h index fe720b532f..d3331a73c2 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,8 +23,7 @@ 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 @@ -41,18 +38,21 @@ namespace detail * 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 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 H, Scalar L, bool no_slip) + : m_H(H), m_L(L), m_no_slip(no_slip) + { + } //! Detect collision between the particle and the boundary /*! @@ -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; @@ -207,28 +207,27 @@ class __attribute__((visibility("default"))) SlitPoreGeometry /*! * \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 86% rename from hoomd/mpcd/SlitPoreGeometryFiller.cc rename to hoomd/mpcd/PlanarPoreGeometryFiller.cc index de6b36ef42..f3661bf7ab 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,17 @@ namespace hoomd { -mpcd::SlitPoreGeometryFiller::SlitPoreGeometryFiller( +mpcd::PlanarPoreGeometryFiller::PlanarPoreGeometryFiller( std::shared_ptr sysdef, Scalar density, unsigned int type, std::shared_ptr T, uint16_t seed, - std::shared_ptr geom) + std::shared_ptr geom) : mpcd::VirtualParticleFiller(sysdef, density, type, 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 +32,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(); @@ -144,7 +144,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) @@ -180,7 +180,7 @@ void mpcd::SlitPoreGeometryFiller::drawParticles(uint64_t timestep) { const unsigned int tag = m_first_tag + i; hoomd::RandomGenerator rng( - hoomd::Seed(hoomd::RNGIdentifier::SlitPoreGeometryFiller, timestep, seed), + hoomd::Seed(hoomd::RNGIdentifier::PlanarPoreGeometryFiller, timestep, seed), hoomd::Counter(tag)); // advanced past end of this box range, take the next @@ -216,18 +216,18 @@ 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, Scalar, unsigned int, std::shared_ptr, unsigned int, - std::shared_ptr>()) - .def("setGeometry", &mpcd::SlitPoreGeometryFiller::setGeometry); + std::shared_ptr>()) + .def("setGeometry", &mpcd::PlanarPoreGeometryFiller::setGeometry); } } // end namespace hoomd diff --git a/hoomd/mpcd/SlitPoreGeometryFiller.h b/hoomd/mpcd/PlanarPoreGeometryFiller.h similarity index 61% rename from hoomd/mpcd/SlitPoreGeometryFiller.h rename to hoomd/mpcd/PlanarPoreGeometryFiller.h index db15c30aeb..50779e1809 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,7 +13,7 @@ #error This header cannot be compiled by nvcc #endif -#include "SlitPoreGeometry.h" +#include "PlanarPoreGeometry.h" #include "VirtualParticleFiller.h" #include @@ -22,31 +22,31 @@ 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::VirtualParticleFiller { 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, + Scalar density, + unsigned int type, + std::shared_ptr T, + uint16_t seed, + std::shared_ptr geom); - virtual ~SlitPoreGeometryFiller(); + virtual ~PlanarPoreGeometryFiller(); - void setGeometry(std::shared_ptr 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 +70,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 75% rename from hoomd/mpcd/SlitPoreGeometryFillerGPU.cc rename to hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc index e386309946..2c76d5957b 100644 --- a/hoomd/mpcd/SlitPoreGeometryFillerGPU.cc +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc @@ -2,23 +2,23 @@ // 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, 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, density, type, T, seed, geom) { m_tuner.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, @@ -29,7 +29,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, @@ -74,18 +74,19 @@ 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, 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 b2173fd0ce..99c8548cc3 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" @@ -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 260592de97..e30202e28f 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 4707037775..4c7e9ad707 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,17 @@ 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, + Scalar density, + unsigned int type, + std::shared_ptr T, + uint16_t seed, + std::shared_ptr geom); protected: //! Draw particles within the fill volume on the GPU @@ -43,8 +43,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/SlitGeometryFillerGPU.h b/hoomd/mpcd/SlitGeometryFillerGPU.h deleted file mode 100644 index a1fe5b8807..0000000000 --- a/hoomd/mpcd/SlitGeometryFillerGPU.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2009-2023 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/StreamingGeometry.cc b/hoomd/mpcd/StreamingGeometry.cc index 3828e72408..6dbf5f6ff2 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("H", &ParallelPlateGeometry::getH) + .def_property_readonly("V", &ParallelPlateGeometry::getVelocity) + .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("H", &PlanarPoreGeometry::getH) + .def_property_readonly("L", &PlanarPoreGeometry::getL) + .def_property_readonly("no_slip", &PlanarPoreGeometry::getNoSlip); } } // end namespace detail diff --git a/hoomd/mpcd/StreamingGeometry.h b/hoomd/mpcd/StreamingGeometry.h index bfff4e4d25..c9137df4bb 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/__init__.py b/hoomd/mpcd/__init__.py index 2b7beba69b..81b0c7210d 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -69,6 +69,7 @@ from hoomd.md import _md from hoomd.mpcd import collide +from hoomd.mpcd import geometry from hoomd.mpcd import integrate from hoomd.mpcd.integrate import Integrator from hoomd.mpcd import stream diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py new file mode 100644 index 0000000000..a6f4b38235 --- /dev/null +++ b/hoomd/mpcd/geometry.py @@ -0,0 +1,105 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +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 no-slip boundary condition. + Otherwise, they have the slip boundary condition. + + Attributes: + no_slip (bool): If True, plates have no-slip boundary condition. + Otherwise, they have the slip boundary condition. + + `V` will have no effect if `no_slip` is False because the slip + surface cannot generate shear stress. + + """ + + 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: + H (float): Channel half-width. + V (float): Wall speed. + no_slip (bool): If True, surfaces have no-slip boundary condition. + Otherwise, they have the slip boundary condition. + + `ParallelPlates` confines the MPCD particles between two infinite parallel + plates centered around the origin. The plates are placed at :math:`z=-H` + and :math:`z=+H`, so the total channel width is :math:`2H`. The plates 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. + + Attributes: + H (float): Channel half-width. + + V (float): Wall speed. + + """ + + def __init__(self, H, V=0.0, no_slip=True): + super().__init__(no_slip) + param_dict = ParameterDict( + H=float(H), + V=float(V), + ) + self._param_dict.update(param_dict) + + def _attach_hook(self): + self._cpp_obj = _mpcd.ParallelPlates(self.H, self.V, self.no_slip) + super()._attach_hook() + + +class PlanarPore(Geometry): + r"""Pore with parallel plate opening. + + Args: + H (float): Pore half-width. + L (float): Pore half-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 *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 + + Attributes: + H (float): Pore half-width. + + L (float): Pore half-length. + + """ + + def __init__(self, H, L, no_slip=True): + super().__init__(no_slip) + + param_dict = ParameterDict( + H=float(H), + L=float(L), + ) + self._param_dict.update(param_dict) + + def _attach_hook(self): + self._cpp_obj = _mpcd.PlanarPore(self.H, self.L, self.no_slip) + super()._attach_hook() diff --git a/hoomd/mpcd/module.cc b/hoomd/mpcd/module.cc index e9e07eaca5..409f613def 100644 --- a/hoomd/mpcd/module.cc +++ b/hoomd/mpcd/module.cc @@ -28,11 +28,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 +44,12 @@ #endif // virtual particle fillers -#include "SlitGeometryFiller.h" -#include "SlitPoreGeometryFiller.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 @@ -128,35 +130,33 @@ 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); + mpcd::detail::export_BulkStreamingMethod(m); + mpcd::detail::export_BounceBackStreamingMethod(m); + mpcd::detail::export_BounceBackStreamingMethod(m); #ifdef ENABLE_HIP - mpcd::detail::export_ConfinedStreamingMethodGPU(m); - mpcd::detail::export_ConfinedStreamingMethodGPU(m); - mpcd::detail::export_ConfinedStreamingMethodGPU(m); + mpcd::detail::export_BulkStreamingMethodGPU(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_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 a8db69e623..3f64f275d9 100644 --- a/hoomd/mpcd/pytest/CMakeLists.txt +++ b/hoomd/mpcd/pytest/CMakeLists.txt @@ -1,6 +1,7 @@ # copy python modules to the build directory to make it a working python package set(files __init__.py test_collide.py + test_geometry.py test_integrator.py test_snapshot.py test_stream.py diff --git a/hoomd/mpcd/pytest/test_geometry.py b/hoomd/mpcd/pytest/test_geometry.py new file mode 100644 index 0000000000..26dcfa7b42 --- /dev/null +++ b/hoomd/mpcd/pytest/test_geometry.py @@ -0,0 +1,88 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import hoomd +from hoomd.conftest import pickling_check +import pytest + + +@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(H=4.0) + assert geom.H == 4.0 + assert geom.V == 0.0 + assert geom.no_slip + + sim = simulation_factory(snap) + geom._attach(sim) + assert geom.H == 4.0 + assert geom.V == 0.0 + assert geom.no_slip + + def test_nondefault_init(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.ParallelPlates(H=5.0, V=1.0, no_slip=False) + assert geom.H == 5.0 + assert geom.V == 1.0 + assert not geom.no_slip + + sim = simulation_factory(snap) + geom._attach(sim) + assert geom.H == 5.0 + assert geom.V == 1.0 + assert not geom.no_slip + + def test_pickling(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.ParallelPlates(H=4.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(H=4.0, L=5.0) + assert geom.H == 4.0 + assert geom.L == 5.0 + assert geom.no_slip + + sim = simulation_factory(snap) + geom._attach(sim) + assert geom.H == 4.0 + assert geom.L == 5.0 + assert geom.no_slip + + def test_nondefault_init(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.PlanarPore(H=5.0, L=4.0, no_slip=False) + assert geom.H == 5.0 + assert geom.L == 4.0 + assert not geom.no_slip + + sim = simulation_factory(snap) + geom._attach(sim) + assert geom.H == 5.0 + assert geom.L == 4.0 + assert not geom.no_slip + + def test_pickling(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.PlanarPore(H=4.0, L=5.0) + pickling_check(geom) + + sim = simulation_factory(snap) + geom._attach(sim) + pickling_check(geom) diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index ecf66eae96..4f53be9285 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -24,19 +24,18 @@ def snap(): [ (hoomd.mpcd.stream.Bulk, {}), ( - hoomd.mpcd.stream.ParallelPlates, + hoomd.mpcd.stream.BounceBack, { - "H": 4.0, - "V": 0.0, - "no_slip": True, + "geometry": + hoomd.mpcd.geometry.ParallelPlates( + H=4.0, V=0.0, no_slip=True), }, ), ( - hoomd.mpcd.stream.PlanarPore, + hoomd.mpcd.stream.BounceBack, { - "H": 4.0, - "L": 3.0, - "no_slip": True, + "geometry": + hoomd.mpcd.geometry.PlanarPore(H=4.0, L=3.0, no_slip=True) }, ), ], @@ -135,7 +134,8 @@ def test_step_noslip(self, simulation_factory, snap): snap.mpcd.position[:] = [[4.95, -4.95, 3.85], [0.0, 0.0, -3.8]] snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=4) + sm = hoomd.mpcd.stream.BounceBack( + period=1, geometry=hoomd.mpcd.geometry.ParallelPlates(H=4)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -173,7 +173,9 @@ def test_slip(self, simulation_factory, snap): snap.mpcd.position[:] = [[4.95, -4.95, 3.85], [0.0, 0.0, -3.8]] snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=4, no_slip=False) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates(H=4, no_slip=False)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -219,7 +221,10 @@ def test_step_moving_wall(self, simulation_factory, snap): snap.mpcd.position[:] = [[4.95, -4.95, 3.85], [0.0, 0.0, -3.8]] snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-2.0, -1.0, -1.0]] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=4, V=1, no_slip=True) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates(H=4, V=1, no_slip=True), + ) ig = hoomd.mpcd.Integrator(dt=0.3, streaming_method=sm) sim.operations.integrator = ig @@ -235,7 +240,8 @@ def test_step_moving_wall(self, simulation_factory, snap): def test_validate_box(self, simulation_factory, snap): """Test box validation raises an error on run.""" sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=10) + sm = hoomd.mpcd.stream.BounceBack( + period=1, geometry=hoomd.mpcd.geometry.ParallelPlates(H=10)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -247,7 +253,8 @@ def test_test_of_bounds(self, simulation_factory, snap): if snap.communicator.rank == 0: snap.mpcd.position[0] = [4.95, -4.95, 3.85] sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.ParallelPlates(period=1, H=3.8) + sm = hoomd.mpcd.stream.BounceBack( + period=1, geometry=hoomd.mpcd.geometry.ParallelPlates(H=3.8)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -285,7 +292,9 @@ def _make_particles(self, snap): def test_step_noslip(self, simulation_factory, snap): snap = self._make_particles(snap) sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.PlanarPore(period=1, H=4, L=3, no_slip=True) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=3, no_slip=True)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -352,7 +361,9 @@ def test_step_noslip(self, simulation_factory, snap): def test_step_slip(self, simulation_factory, snap): snap = self._make_particles(snap) sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.PlanarPore(period=1, H=4, L=3, no_slip=False) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=3, no_slip=False)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -422,11 +433,13 @@ def test_validate_box(self, simulation_factory, snap): ig = hoomd.mpcd.Integrator(dt=0.1) sim.operations.integrator = ig - ig.streaming_method = hoomd.mpcd.stream.PlanarPore(period=1, H=10, L=2) + ig.streaming_method = hoomd.mpcd.stream.BounceBack( + period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=10, L=2)) with pytest.raises(RuntimeError): sim.run(1) - ig.streaming_method = hoomd.mpcd.stream.PlanarPore(period=1, H=4, L=10) + ig.streaming_method = hoomd.mpcd.stream.BounceBack( + period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=10)) with pytest.raises(RuntimeError): sim.run(1) @@ -437,10 +450,12 @@ def test_test_of_bounds(self, simulation_factory, snap): ig = hoomd.mpcd.Integrator(dt=0.1) sim.operations.integrator = ig - ig.streaming_method = hoomd.mpcd.stream.PlanarPore(period=1, H=3.8, L=3) + ig.streaming_method = hoomd.mpcd.stream.BounceBack( + period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=3.8, L=3)) with pytest.raises(RuntimeError): sim.run(1) - ig.streaming_method = hoomd.mpcd.stream.PlanarPore(period=1, H=4, L=3.5) + ig.streaming_method = hoomd.mpcd.stream.BounceBack( + period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=3.5)) with pytest.raises(RuntimeError): sim.run(1) diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index 2a9e87afb5..fa37f8d7df 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -49,7 +49,9 @@ import hoomd from hoomd.data.parameterdicts import ParameterDict +from hoomd.data.typeconverter import OnlyTypes from hoomd.mpcd import _mpcd +from hoomd.mpcd.geometry import Geometry from hoomd.operation import AutotunedObject @@ -96,136 +98,73 @@ class Bulk(StreamingMethod): def _attach_hook(self): sim = self._simulation if isinstance(sim.device, hoomd.device.GPU): - class_ = _mpcd.ConfinedStreamingMethodGPUBulk + class_ = _mpcd.BulkStreamingMethodGPU else: - class_ = _mpcd.ConfinedStreamingMethodBulk + class_ = _mpcd.BulkStreamingMethod self._cpp_obj = class_( sim.state._cpp_sys_def, sim.timestep, self.period, 0, - _mpcd.BulkGeometry(), ) super()._attach_hook() -class ParallelPlates(StreamingMethod): - r"""Parallel-plate channel. +class BounceBack(StreamingMethod): + """Streaming with bounce-back rule for surfaces. Args: period (int): Number of integration steps covered by streaming step. - H (float): Channel half-width. - V (float): Wall speed. - no_slip (bool): If True, plates have no-slip boundary condition. - Otherwise, they have the slip boundary condition. - - `ParallelPlates` streams the MPCD particles between two infinite parallel - plates centered around the origin. The plates are placed at :math:`z=-H` - and :math:`z=+H`, so the total channel width is :math:`2H`. The plates 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. + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. Attributes: - H (float): Channel half-width. - - V (float): Wall speed. - - no_slip (bool): If True, plates have no-slip boundary condition. - Otherwise, they have the slip boundary condition. - - `V` will have no effect if `no_slip` is False because the slip - surface cannot generate shear stress. + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. """ - def __init__(self, period, H, V=0.0, no_slip=True): + def __init__(self, period, geometry): super().__init__(period) - param_dict = ParameterDict( - H=float(H), - V=float(V), - no_slip=bool(no_slip), - ) + param_dict = ParameterDict(geometry=OnlyTypes(Geometry)) + param_dict["geometry"] = geometry self._param_dict.update(param_dict) 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._class_map[geom_type] + except KeyError: + class_info = (_mpcd, + "BounceBackStreamingMethod" + geom_type.__name__) + class_info = list(class_info) if isinstance(sim.device, hoomd.device.GPU): - class_ = _mpcd.ConfinedStreamingMethodGPUSlit - else: - class_ = _mpcd.ConfinedStreamingMethodSlit + class_info[1] += "GPU" + class_ = getattr(*class_info, None) + assert class_ is not None, "Streaming method for geometry not found" - bc = _mpcd.boundary.no_slip if self.no_slip else _mpcd.boundary.slip - slit = _mpcd.SlitGeometry(self.H, self.V, bc) self._cpp_obj = class_( sim.state._cpp_sys_def, sim.timestep, self.period, 0, - slit, + self.geometry._cpp_obj, ) super()._attach_hook() + def _detach_hook(self): + self.geometry._detach() + super()._detach_hook() -class PlanarPore(StreamingMethod): - r"""Parallel plate pore. - - Args: - period (int): Number of integration steps covered by streaming step. - H (float): Channel half-width. - L (float): Pore half-length. - no_slip (bool): If True, plates have no-slip boundary condition. - Otherwise, they have the slip boundary condition. - - `PlanarPore` is a finite-length pore 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 *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 - - Attributes: - H (float): Channel half-width. - - L (float): Pore half-length. - - no_slip (bool): If True, plates have no-slip boundary condition. - Otherwise, they have the slip boundary condition. - - """ - - def __init__(self, period, H, L, no_slip=True): - super().__init__(period) - - param_dict = ParameterDict( - H=float(H), - L=float(L), - no_slip=bool(no_slip), - ) - self._param_dict.update(param_dict) - - def _attach_hook(self): - sim = self._simulation - if isinstance(sim.device, hoomd.device.GPU): - class_ = _mpcd.ConfinedStreamingMethodGPUSlitPore - else: - class_ = _mpcd.ConfinedStreamingMethodSlitPore - - bc = _mpcd.boundary.no_slip if self.no_slip else _mpcd.boundary.slip - slit = _mpcd.SlitPoreGeometry(self.H, self.L, bc) - self._cpp_obj = class_( - sim.state._cpp_sys_def, - sim.timestep, - self.period, - 0, - slit, - ) + _class_map = {} - super()._attach_hook() + @classmethod + def _register_geometry(cls, geometry, module, class_name): + cls._class_map[geometry] = (module, class_name) diff --git a/hoomd/mpcd/test/CMakeLists.txt b/hoomd/mpcd/test/CMakeLists.txt index d4687c2c76..19ce96f0a7 100644 --- a/hoomd/mpcd/test/CMakeLists.txt +++ b/hoomd/mpcd/test/CMakeLists.txt @@ -4,8 +4,8 @@ set(TEST_LIST cell_list cell_thermo_compute #external_field - slit_geometry_filler - slit_pore_geometry_filler + parallel_plate_geometry_filler + planar_pore_geometry_filler sorter srd_collision_method streaming_method @@ -22,8 +22,8 @@ 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) 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 84% rename from hoomd/mpcd/test/slit_geometry_filler_mpi_test.cc rename to hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc index 2b867be6b1..ad2a4f1fe5 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); @@ -38,11 +39,9 @@ template void slit_fill_mpi_test(std::shared_ptrgetNVirtualGlobal(), 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(5.0, 0.0, true); std::shared_ptr kT = std::make_shared(1.0); - std::shared_ptr filler + std::shared_ptr filler = std::make_shared(sysdef, 2.0, 0, kT, slit); filler->setCellList(cl); @@ -80,15 +79,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 91% rename from hoomd/mpcd/test/slit_geometry_filler_test.cc rename to hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc index aa5a539010..abc9b930e4 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-2023 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,7 +13,8 @@ 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); @@ -29,11 +30,9 @@ template void slit_fill_basic_test(std::shared_ptrgetNVirtual(), 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(5.0, 1.0, true); std::shared_ptr kT = std::make_shared(1.5); - std::shared_ptr filler + std::shared_ptr filler = std::make_shared(sysdef, 2.0, 1, kT, slit); filler->setCellList(cl); @@ -184,15 +183,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 84% 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 626bf14b50..26c8d76193 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); @@ -38,12 +38,9 @@ template void slit_pore_fill_mpi_test(std::shared_ptrgetNVirtualGlobal(), 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(5.0, 8.0, true); std::shared_ptr kT = std::make_shared(1.0); - std::shared_ptr filler + std::shared_ptr filler = std::make_shared(sysdef, 2.0, 0, kT, 42, slit); filler->setCellList(cl); @@ -87,15 +84,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 91% rename from hoomd/mpcd/test/slit_pore_geometry_filler_test.cc rename to hoomd/mpcd/test/planar_pore_geometry_filler_test.cc index c784e04f63..88923e559b 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-2023 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,7 +13,8 @@ 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); @@ -29,13 +30,10 @@ template void slit_pore_fill_basic_test(std::shared_ptrgetNVirtual(), 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(5.0, 8.0, true); // fill density 2, temperature 1.5 std::shared_ptr kT = std::make_shared(1.5); - std::shared_ptr filler + std::shared_ptr filler = std::make_shared(sysdef, 2.0, 1, kT, 42, slit); filler->setCellList(cl); @@ -185,15 +183,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/streaming_method_test.cc b/hoomd/mpcd/test/streaming_method_test.cc index b575b0c8fd..396be990e9 100644 --- a/hoomd/mpcd/test/streaming_method_test.cc +++ b/hoomd/mpcd/test/streaming_method_test.cc @@ -1,10 +1,9 @@ // Copyright (c) 2009-2023 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/SnapshotSystemData.h" @@ -33,8 +32,7 @@ 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); + std::shared_ptr stream = std::make_shared(sysdef, 2, 2, 1); auto cl = std::make_shared(sysdef, 1.0, false); stream->setCellList(cl); @@ -113,16 +111,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 From 73b84e475fb8c1a9c3663f4249ef2a98cc81464d Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 4 Jan 2024 13:58:05 -0600 Subject: [PATCH 21/69] Add geometries to documentation --- hoomd/mpcd/geometry.py | 14 ++++++++++++++ sphinx-doc/module-mpcd-geometry.rst | 25 +++++++++++++++++++++++++ sphinx-doc/module-mpcd-stream.rst | 6 ++---- sphinx-doc/package-mpcd.rst | 1 + 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 sphinx-doc/module-mpcd-geometry.rst diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py index a6f4b38235..3a207f6a62 100644 --- a/hoomd/mpcd/geometry.py +++ b/hoomd/mpcd/geometry.py @@ -1,6 +1,20 @@ # Copyright (c) 2009-2023 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: + +* :class:`~hoomd.mpcd.stream.BounceBack` streaming for the solvent +* Bounce back integration for MD particles +* Virtual particle filling + +Each geometry may put constraints on the size of the simulation and where +particles are allowed. These constraints will be documented by each object. + +""" + from hoomd.data.parameterdicts import ParameterDict from hoomd.mpcd import _mpcd from hoomd.operation import _HOOMDBaseObject diff --git a/sphinx-doc/module-mpcd-geometry.rst b/sphinx-doc/module-mpcd-geometry.rst new file mode 100644 index 0000000000..c76c9bde52 --- /dev/null +++ b/sphinx-doc/module-mpcd-geometry.rst @@ -0,0 +1,25 @@ +.. Copyright (c) 2009-2023 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-stream.rst b/sphinx-doc/module-mpcd-stream.rst index 782f63357c..6f6d9e87c2 100644 --- a/sphinx-doc/module-mpcd-stream.rst +++ b/sphinx-doc/module-mpcd-stream.rst @@ -12,8 +12,7 @@ mpcd.stream :nosignatures: Bulk - ParallelPlates - PlanarPore + BounceBack StreamingMethod .. rubric:: Details @@ -22,6 +21,5 @@ mpcd.stream :synopsis: Streaming methods. :members: StreamingMethod, Bulk, - ParallelPlates, - PlanarPore + BounceBack :show-inheritance: diff --git a/sphinx-doc/package-mpcd.rst b/sphinx-doc/package-mpcd.rst index 51e3c38db5..e3eb38e299 100644 --- a/sphinx-doc/package-mpcd.rst +++ b/sphinx-doc/package-mpcd.rst @@ -17,5 +17,6 @@ hoomd.mpcd :maxdepth: 1 module-mpcd-collide + module-mpcd-geometry module-mpcd-stream module-mpcd-tune From b22fbd1672ee8c13995638d3bd21196faf46003e Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 4 Jan 2024 17:14:25 -0600 Subject: [PATCH 22/69] Reimplement virtual particle fillers --- hoomd/mpcd/CMakeLists.txt | 1 + hoomd/mpcd/Integrator.cc | 13 +- hoomd/mpcd/Integrator.h | 14 +-- hoomd/mpcd/ParallelPlateGeometryFiller.cc | 8 +- hoomd/mpcd/ParallelPlateGeometryFiller.h | 7 +- hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc | 6 +- hoomd/mpcd/ParallelPlateGeometryFillerGPU.h | 2 +- hoomd/mpcd/PlanarPoreGeometryFiller.cc | 10 +- hoomd/mpcd/PlanarPoreGeometryFiller.h | 8 +- hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc | 8 +- hoomd/mpcd/PlanarPoreGeometryFillerGPU.h | 3 +- hoomd/mpcd/VirtualParticleFiller.cc | 36 +++--- hoomd/mpcd/VirtualParticleFiller.h | 18 ++- hoomd/mpcd/__init__.py | 1 + hoomd/mpcd/collide.py | 6 +- hoomd/mpcd/fill.py | 118 ++++++++++++++++++ hoomd/mpcd/integrate.py | 60 +++++++-- hoomd/mpcd/pytest/CMakeLists.txt | 1 + hoomd/mpcd/pytest/test_fill.py | 87 +++++++++++++ hoomd/mpcd/pytest/test_integrator.py | 43 +++++++ hoomd/mpcd/stream.py | 12 +- ...parallel_plate_geometry_filler_mpi_test.cc | 3 +- .../parallel_plate_geometry_filler_test.cc | 3 +- .../planar_pore_geometry_filler_mpi_test.cc | 3 +- .../test/planar_pore_geometry_filler_test.cc | 3 +- hoomd/mpcd/tune.py | 4 +- sphinx-doc/module-mpcd-fill.rst | 23 ++++ sphinx-doc/package-mpcd.rst | 1 + 28 files changed, 419 insertions(+), 83 deletions(-) create mode 100644 hoomd/mpcd/fill.py create mode 100644 hoomd/mpcd/pytest/test_fill.py create mode 100644 sphinx-doc/module-mpcd-fill.rst diff --git a/hoomd/mpcd/CMakeLists.txt b/hoomd/mpcd/CMakeLists.txt index 0bb7d19060..02387f8c77 100644 --- a/hoomd/mpcd/CMakeLists.txt +++ b/hoomd/mpcd/CMakeLists.txt @@ -142,6 +142,7 @@ install(TARGETS _mpcd EXPORT HOOMDTargets set(files __init__.py collide.py + fill.py force.py geometry.py integrate.py diff --git a/hoomd/mpcd/Integrator.cc b/hoomd/mpcd/Integrator.cc index 2c2366ff7c..ccabc4166c 100644 --- a/hoomd/mpcd/Integrator.cc +++ b/hoomd/mpcd/Integrator.cc @@ -76,9 +76,12 @@ void mpcd::Integrator::update(uint64_t timestep) #endif // ENABLE_MPI // fill in any virtual particles - if (checkCollide(timestep) && m_filler) + if (checkCollide(timestep)) { - m_filler->fill(timestep); + for (auto& filler : m_fillers) + { + filler->fill(timestep); + } } // optionally sort for performance @@ -151,9 +154,9 @@ void mpcd::Integrator::syncCellList() m_mpcd_comm->setCellList(m_cl); } #endif - if (m_filler) + for (auto& filler : m_fillers) { - m_filler->setCellList(m_cl); + filler->setCellList(m_cl); } } @@ -174,6 +177,6 @@ void mpcd::detail::export_Integrator(pybind11::module& m) &mpcd::Integrator::getStreamingMethod, &mpcd::Integrator::setStreamingMethod) .def_property("solvent_sorter", &mpcd::Integrator::getSorter, &mpcd::Integrator::setSorter) - .def_property("filler", &mpcd::Integrator::getFiller, &mpcd::Integrator::setFiller); + .def_property_readonly("fillers", &mpcd::Integrator::getFillers); } } // end namespace hoomd diff --git a/hoomd/mpcd/Integrator.h b/hoomd/mpcd/Integrator.h index 7c5de78196..8325c9b922 100644 --- a/hoomd/mpcd/Integrator.h +++ b/hoomd/mpcd/Integrator.h @@ -126,15 +126,10 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep m_sorter = sorter; } - std::shared_ptr getFiller() const + //! Get the virtual particle fillers + std::vector>& getFillers() { - return m_filler; - } - - //! Set the virtual particle filling method - void setFiller(std::shared_ptr filler) - { - m_filler = filler; + return m_fillers; } protected: @@ -145,7 +140,8 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep #ifdef ENABLE_MPI std::shared_ptr m_mpcd_comm; //!< MPCD communicator #endif - std::shared_ptr m_filler; //!< MPCD virtual particle fillers + std::vector> + m_fillers; //!< MPCD virtual particle fillers private: //! Check if a collision will occur at the current timestep diff --git a/hoomd/mpcd/ParallelPlateGeometryFiller.cc b/hoomd/mpcd/ParallelPlateGeometryFiller.cc index 4289b0c6eb..7673321c5a 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFiller.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.cc @@ -14,11 +14,11 @@ namespace hoomd { mpcd::ParallelPlateGeometryFiller::ParallelPlateGeometryFiller( std::shared_ptr sysdef, + const std::string& type, Scalar density, - unsigned int type, std::shared_ptr T, std::shared_ptr geom) - : mpcd::VirtualParticleFiller(sysdef, density, type, T), m_geom(geom) + : mpcd::VirtualParticleFiller(sysdef, type, density, T), m_geom(geom) { m_exec_conf->msg->notice(5) << "Constructing MPCD ParallelPlateGeometryFiller" << std::endl; } @@ -148,11 +148,11 @@ void mpcd::detail::export_ParallelPlateGeometryFiller(pybind11::module& m) m, "ParallelPlateGeometryFiller") .def(pybind11::init, + const std::string&, Scalar, - unsigned int, std::shared_ptr, std::shared_ptr>()) - .def("setGeometry", &mpcd::ParallelPlateGeometryFiller::setGeometry); + .def_property_readonly("geometry", &mpcd::ParallelPlateGeometryFiller::getGeometry); } } // end namespace hoomd diff --git a/hoomd/mpcd/ParallelPlateGeometryFiller.h b/hoomd/mpcd/ParallelPlateGeometryFiller.h index 7372e6a9d2..447b980936 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFiller.h +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.h @@ -31,13 +31,18 @@ class PYBIND11_EXPORT ParallelPlateGeometryFiller : public mpcd::VirtualParticle { public: ParallelPlateGeometryFiller(std::shared_ptr sysdef, + const std::string& type, Scalar density, - unsigned int type, 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; diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc index 4d9cd7c22f..91e609e65e 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc @@ -13,11 +13,11 @@ namespace hoomd { 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::ParallelPlateGeometryFiller(sysdef, density, type, T, geom) + : mpcd::ParallelPlateGeometryFiller(sysdef, type, density, T, geom) { m_tuner.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, @@ -78,8 +78,8 @@ void mpcd::detail::export_ParallelPlateGeometryFillerGPU(pybind11::module& m) m, "ParallelPlateGeometryFillerGPU") .def(pybind11::init, + const std::string&, Scalar, - unsigned int, std::shared_ptr, std::shared_ptr>()); } diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h index 82ea62dcd2..befef60ac3 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h @@ -27,8 +27,8 @@ class PYBIND11_EXPORT ParallelPlateGeometryFillerGPU : public mpcd::ParallelPlat public: //! Constructor ParallelPlateGeometryFillerGPU(std::shared_ptr sysdef, + const std::string& type, Scalar density, - unsigned int type, std::shared_ptr T, std::shared_ptr geom); diff --git a/hoomd/mpcd/PlanarPoreGeometryFiller.cc b/hoomd/mpcd/PlanarPoreGeometryFiller.cc index f3661bf7ab..9fddc8a5c0 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFiller.cc +++ b/hoomd/mpcd/PlanarPoreGeometryFiller.cc @@ -16,12 +16,11 @@ namespace hoomd { 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), + : mpcd::VirtualParticleFiller(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 PlanarPoreGeometryFiller" << std::endl; @@ -222,12 +221,11 @@ void mpcd::detail::export_PlanarPoreGeometryFiller(pybind11::module& m) mpcd::VirtualParticleFiller, 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::PlanarPoreGeometryFiller::setGeometry); + .def_property_readonly("geometry", &mpcd::PlanarPoreGeometryFiller::getGeometry); } } // end namespace hoomd diff --git a/hoomd/mpcd/PlanarPoreGeometryFiller.h b/hoomd/mpcd/PlanarPoreGeometryFiller.h index 50779e1809..4f7e8a80b7 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFiller.h +++ b/hoomd/mpcd/PlanarPoreGeometryFiller.h @@ -31,14 +31,18 @@ class PYBIND11_EXPORT PlanarPoreGeometryFiller : public mpcd::VirtualParticleFil { public: 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); virtual ~PlanarPoreGeometryFiller(); + std::shared_ptr getGeometry() const + { + return m_geom; + } + void setGeometry(std::shared_ptr geom) { m_geom = geom; diff --git a/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc index 2c76d5957b..30097a6f0b 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc @@ -13,12 +13,11 @@ namespace hoomd { 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::PlanarPoreGeometryFiller(sysdef, density, type, T, seed, geom) + : mpcd::PlanarPoreGeometryFiller(sysdef, type, density, T, geom) { m_tuner.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, @@ -82,10 +81,9 @@ void mpcd::detail::export_PlanarPoreGeometryFillerGPU(pybind11::module& m) m, "PlanarPoreGeometryFillerGPU") .def(pybind11::init, + const std::string&, Scalar, - unsigned int, std::shared_ptr, - unsigned int, std::shared_ptr>()); } diff --git a/hoomd/mpcd/PlanarPoreGeometryFillerGPU.h b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.h index 4c7e9ad707..626f8adf1f 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFillerGPU.h +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.h @@ -27,10 +27,9 @@ class PYBIND11_EXPORT PlanarPoreGeometryFillerGPU : public mpcd::PlanarPoreGeome public: //! Constructor 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); protected: diff --git a/hoomd/mpcd/VirtualParticleFiller.cc b/hoomd/mpcd/VirtualParticleFiller.cc index efe4fbca83..d827b2ed6d 100644 --- a/hoomd/mpcd/VirtualParticleFiller.cc +++ b/hoomd/mpcd/VirtualParticleFiller.cc @@ -11,13 +11,14 @@ namespace hoomd { 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) + m_mpcd_pdata(m_sysdef->getMPCDParticleData()), m_density(density), m_T(T), m_N_fill(0), + m_first_tag(0) { + setType(type); } void mpcd::VirtualParticleFiller::fill(uint64_t timestep) @@ -67,15 +68,14 @@ 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()) - { - m_exec_conf->msg->error() << "Invalid type id specified for MPCD virtual particle filler" - << std::endl; - throw std::runtime_error("Invalid type id"); - } - m_type = type; + return m_mpcd_pdata->getNameByType(m_type); + } + +void mpcd::VirtualParticleFiller::setType(const std::string& type) + { + m_type = m_mpcd_pdata->getTypeByName(type); } /*! @@ -87,12 +87,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 13e2e6b392..7ac26dcbc6 100644 --- a/hoomd/mpcd/VirtualParticleFiller.h +++ b/hoomd/mpcd/VirtualParticleFiller.h @@ -20,6 +20,8 @@ #include "hoomd/Variant.h" #include +#include + namespace hoomd { namespace mpcd @@ -38,8 +40,8 @@ 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() { } @@ -47,11 +49,23 @@ class PYBIND11_EXPORT VirtualParticleFiller : public Autotuned //! Fill up virtual particles void fill(uint64_t timestep); + Scalar getDensity() const + { + return m_density; + } + //! Set the fill particle density void setDensity(Scalar density); + std::string getType() const; + //! Set the fill particle type - void setType(unsigned int type); + void setType(const std::string& type); + + std::shared_ptr getTemperature() const + { + return m_T; + } //! Set the fill particle temperature void setTemperature(std::shared_ptr T) diff --git a/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index 81b0c7210d..36d08fc02e 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -69,6 +69,7 @@ from hoomd.md import _md from hoomd.mpcd import collide +from hoomd.mpcd import fill from hoomd.mpcd import geometry from hoomd.mpcd import integrate from hoomd.mpcd.integrate import Integrator diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index e6fb1136c2..e17ebdd549 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -17,10 +17,10 @@ from hoomd.data.parameterdicts import ParameterDict from hoomd.data.typeconverter import OnlyTypes, variant_preprocessing from hoomd.mpcd import _mpcd -from hoomd.operation import AutotunedObject +from hoomd.operation import Compute, Operation -class CellList(AutotunedObject): +class CellList(Compute): """Collision cell list. Args: @@ -67,7 +67,7 @@ def _attach_hook(self): super()._attach_hook() -class CollisionMethod(AutotunedObject): +class CollisionMethod(Operation): """Base collision method. Args: diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py new file mode 100644 index 0000000000..974b1c29a5 --- /dev/null +++ b/hoomd/mpcd/fill.py @@ -0,0 +1,118 @@ +# Copyright (c) 2009-2023 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 solvent 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 solvent +properties may differ. In practice, this means that the boundary conditions do +not appear to be properly enforced. +""" + +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`. + + Attributes: + type (str): Type of particles to fill. + + density (float): Particle number density. + + kT (hoomd.variant.variant_like): Temperature of particles. + + """ + + 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 known 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. + + Attributes: + geometry (hoomd.mpcd.geometry.Geometry): Surface to fill around. + + """ + + 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 + + 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._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() + + _class_map = {} + + @classmethod + def _register_geometry(cls, geometry, module, class_name): + cls._class_map[geometry] = (module, class_name) + + +GeometryFiller._register_geometry(ParallelPlates, _mpcd, + "ParallelPlateGeometryFiller") diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index d72a05a71b..07945896ec 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -1,12 +1,16 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. +import itertools + 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 +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 @@ -46,6 +50,9 @@ class Integrator(_MDIntegrator): collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method for the MPCD solvent and any embedded particles. + solvent_fillers (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): Solvent + virtual-particle filler(s). + solvent_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the MPCD particles. @@ -73,6 +80,9 @@ class Integrator(_MDIntegrator): collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method for the MPCD solvent and any embedded particles. + solvent_fillers (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): Solvent + virtual-particle filler(s). + solvent_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the MPCD particles (recommended). @@ -92,6 +102,7 @@ def __init__( half_step_hook=None, streaming_method=None, collision_method=None, + solvent_fillers=None, solvent_sorter=None, ): super().__init__( @@ -104,20 +115,28 @@ def __init__( half_step_hook, ) - param_dict = ParameterDict(streaming_method=OnlyTypes(StreamingMethod, - allow_none=True), - collision_method=OnlyTypes(CollisionMethod, - allow_none=True), - solvent_sorter=OnlyTypes(ParticleSorter, - allow_none=True)) - param_dict.update( - dict(streaming_method=streaming_method, - collision_method=collision_method, - solvent_sorter=solvent_sorter)) - self._param_dict.update(param_dict) + solvent_fillers = [] if solvent_fillers is None else solvent_fillers + self._solvent_fillers = syncedlist.SyncedList( + VirtualParticleFiller, + syncedlist._PartialGetAttr("_cpp_obj"), + iterable=solvent_fillers, + ) self._cell_list = CellList(cell_size=1.0, shift=True) + param_dict = ParameterDict( + streaming_method=OnlyTypes(StreamingMethod, allow_none=True), + collision_method=OnlyTypes(CollisionMethod, allow_none=True), + solvent_sorter=OnlyTypes(ParticleSorter, allow_none=True), + ) + param_dict.update( + dict( + streaming_method=streaming_method, + collision_method=collision_method, + solvent_sorter=solvent_sorter, + )) + self._param_dict.update(param_dict) + @property def cell_list(self): """hoomd.mpcd.collide.CellList: Collision cell list. @@ -129,6 +148,21 @@ def cell_list(self): """ return self._cell_list + @property + def solvent_fillers(self): + return self._solvent_fillers + + @solvent_fillers.setter + def solvent_fillers(self, value): + _set_synced_list(self._solvent_fillers, value) + + @property + def _children(self): + children = super()._children + for child in itertools.chain(self.solvent_fillers): + children.extend(child._children) + return children + def _attach_hook(self): self._cell_list._attach(self._simulation) if self.streaming_method is not None: @@ -140,11 +174,13 @@ def _attach_hook(self): self._cpp_obj = _mpcd.Integrator(self._simulation.state._cpp_sys_def, self.dt) + self._solvent_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._solvent_fillers._unsync() self._cell_list._detach() if self.streaming_method is not None: self.streaming_method._detach() diff --git a/hoomd/mpcd/pytest/CMakeLists.txt b/hoomd/mpcd/pytest/CMakeLists.txt index 3f64f275d9..8b63f785d4 100644 --- a/hoomd/mpcd/pytest/CMakeLists.txt +++ b/hoomd/mpcd/pytest/CMakeLists.txt @@ -1,6 +1,7 @@ # 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_geometry.py test_integrator.py test_snapshot.py diff --git a/hoomd/mpcd/pytest/test_fill.py b/hoomd/mpcd/pytest/test_fill.py new file mode 100644 index 0000000000..407a3e43fc --- /dev/null +++ b/hoomd/mpcd/pytest/test_fill.py @@ -0,0 +1,87 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import hoomd +from hoomd.conftest import pickling_check +import numpy as np +import pytest + + +@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, { + "H": 4.0 + }), + (hoomd.mpcd.geometry.PlanarPore, { + "H": 4.0, + "L": 5.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, solvent_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_integrator.py b/hoomd/mpcd/pytest/test_integrator.py index c688202f97..15886c7e44 100644 --- a/hoomd/mpcd/pytest/test_integrator.py +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -84,6 +84,47 @@ def test_streaming_method(make_simulation): assert ig.streaming_method is stream +def test_solvent_fillers(make_simulation): + sim = make_simulation() + geom = hoomd.mpcd.geometry.ParallelPlates(H=4.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, solvent_fillers=[filler]) + sim.operations.integrator = ig + assert len(ig.solvent_fillers) == 1 + assert filler in ig.solvent_fillers + sim.run(0) + assert len(ig.solvent_fillers) == 1 + assert filler in ig.solvent_fillers + + # attach a second filler + filler2 = hoomd.mpcd.fill.GeometryFiller( + type="A", + density=1.0, + kT=1.0, + geometry=geom, + ) + ig.solvent_fillers.append(filler2) + assert len(ig.solvent_fillers) == 2 + assert filler in ig.solvent_fillers + assert filler2 in ig.solvent_fillers + sim.run(0) + assert len(ig.solvent_fillers) == 2 + assert filler in ig.solvent_fillers + assert filler2 in ig.solvent_fillers + + ig.solvent_fillers = [] + assert len(ig.solvent_fillers) == 0 + sim.run(0) + assert len(ig.solvent_fillers) == 0 + + def test_solvent_sorter(make_simulation): sim = make_simulation() sorter = hoomd.mpcd.tune.ParticleSorter(trigger=1) @@ -119,6 +160,8 @@ def test_attach_and_detach(make_simulation): assert ig.cell_list._attached assert ig.streaming_method is None assert ig.collision_method is None + assert len(ig.solvent_fillers) == 0 + assert ig.solvent_sorter is None # attach with both methods ig.streaming_method = hoomd.mpcd.stream.Bulk(period=1) diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index fa37f8d7df..c7973e56fa 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -27,8 +27,7 @@ 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. +beginning and the end of this process.) Although a streaming geometry is enforced on the MPCD solvent particles, there are a few important caveats: @@ -49,14 +48,13 @@ import hoomd from hoomd.data.parameterdicts import ParameterDict -from hoomd.data.typeconverter import OnlyTypes from hoomd.mpcd import _mpcd from hoomd.mpcd.geometry import Geometry -from hoomd.operation import AutotunedObject +from hoomd.operation import Operation -# TODO: add force and filler -class StreamingMethod(AutotunedObject): +# TODO: add force +class StreamingMethod(Operation): """Base streaming method. Args: @@ -127,7 +125,7 @@ class BounceBack(StreamingMethod): def __init__(self, period, geometry): super().__init__(period) - param_dict = ParameterDict(geometry=OnlyTypes(Geometry)) + param_dict = ParameterDict(geometry=Geometry) param_dict["geometry"] = geometry self._param_dict.update(param_dict) diff --git a/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc b/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc index ad2a4f1fe5..96b8dd7d46 100644 --- a/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc +++ b/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc @@ -24,6 +24,7 @@ void parallel_plate_fill_mpi_test(std::shared_ptr exec_c 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, 1, 1); snap->mpcd_data.velocity[0] = vec3(123, 456, 789); @@ -42,7 +43,7 @@ void parallel_plate_fill_mpi_test(std::shared_ptr exec_c auto slit = std::make_shared(5.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::make_shared(sysdef, "B", 2.0, kT, slit); filler->setCellList(cl); /* diff --git a/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc index abc9b930e4..39549cf30a 100644 --- a/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc +++ b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc @@ -21,6 +21,7 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec 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)); @@ -33,7 +34,7 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec auto slit = std::make_shared(5.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::make_shared(sysdef, "B", 2.0, kT, slit); filler->setCellList(cl); /* diff --git a/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc b/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc index 26c8d76193..2b96e38e1b 100644 --- a/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc +++ b/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc @@ -23,6 +23,7 @@ template void planar_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); @@ -41,7 +42,7 @@ template void planar_pore_fill_mpi_test(std::shared_ptr(5.0, 8.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::make_shared(sysdef, "A", 2.0, kT, slit); filler->setCellList(cl); /* diff --git a/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc b/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc index 88923e559b..ba13aa4f0e 100644 --- a/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc +++ b/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc @@ -21,6 +21,7 @@ void planar_pore_fill_basic_test(std::shared_ptr exec_co 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)); @@ -34,7 +35,7 @@ void planar_pore_fill_basic_test(std::shared_ptr exec_co // 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::make_shared(sysdef, "B", 2.0, kT, slit); filler->setCellList(cl); /* diff --git a/hoomd/mpcd/tune.py b/hoomd/mpcd/tune.py index 31dfc9682f..0b791e8c2a 100644 --- a/hoomd/mpcd/tune.py +++ b/hoomd/mpcd/tune.py @@ -10,10 +10,10 @@ import hoomd from hoomd.mpcd import _mpcd -from hoomd.operation import Tuner +from hoomd.operation import TriggeredOperation -class ParticleSorter(Tuner): +class ParticleSorter(TriggeredOperation): r"""MPCD particle sorter. Args: diff --git a/sphinx-doc/module-mpcd-fill.rst b/sphinx-doc/module-mpcd-fill.rst new file mode 100644 index 0000000000..4f334ccf52 --- /dev/null +++ b/sphinx-doc/module-mpcd-fill.rst @@ -0,0 +1,23 @@ +.. Copyright (c) 2009-2023 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/package-mpcd.rst b/sphinx-doc/package-mpcd.rst index e3eb38e299..3c8306b54b 100644 --- a/sphinx-doc/package-mpcd.rst +++ b/sphinx-doc/package-mpcd.rst @@ -17,6 +17,7 @@ hoomd.mpcd :maxdepth: 1 module-mpcd-collide + module-mpcd-fill module-mpcd-geometry module-mpcd-stream module-mpcd-tune From 9c79bb6539094a1b569c172b1c4a4d5394943228 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Fri, 5 Jan 2024 12:41:27 -0600 Subject: [PATCH 23/69] Rename attribute and fix docs --- hoomd/mpcd/fill.py | 2 +- hoomd/mpcd/integrate.py | 40 +++++++++++++++------------- hoomd/mpcd/pytest/test_fill.py | 2 +- hoomd/mpcd/pytest/test_integrator.py | 34 +++++++++++------------ 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py index 974b1c29a5..ee7e28f3dd 100644 --- a/hoomd/mpcd/fill.py +++ b/hoomd/mpcd/fill.py @@ -62,7 +62,7 @@ class GeometryFiller(VirtualParticleFiller): 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. + specific `geometry`. Attributes: geometry (hoomd.mpcd.geometry.Geometry): Surface to fill around. diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index 07945896ec..fccdf2bf3c 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -17,7 +17,7 @@ @hoomd.logging.modify_namespace(("mpcd", "Integrator")) class Integrator(_MDIntegrator): - """MPCD integrator. + r"""MPCD integrator. Args: dt (float): Integrator time step size :math:`[\mathrm{time}]`. @@ -50,7 +50,7 @@ class Integrator(_MDIntegrator): collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method for the MPCD solvent and any embedded particles. - solvent_fillers (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): Solvent + virtual_particle_fillers (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): Solvent virtual-particle filler(s). solvent_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the @@ -80,15 +80,15 @@ class Integrator(_MDIntegrator): collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method for the MPCD solvent and any embedded particles. - solvent_fillers (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): Solvent - virtual-particle filler(s). - solvent_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the MPCD particles (recommended). streaming_method (hoomd.mpcd.stream.StreamingMethod): Streaming method for the MPCD solvent. + virtual_particle_fillers (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): + Solvent virtual-particle filler(s). + """ def __init__( @@ -102,7 +102,7 @@ def __init__( half_step_hook=None, streaming_method=None, collision_method=None, - solvent_fillers=None, + virtual_particle_fillers=None, solvent_sorter=None, ): super().__init__( @@ -115,15 +115,16 @@ def __init__( half_step_hook, ) - solvent_fillers = [] if solvent_fillers is None else solvent_fillers - self._solvent_fillers = syncedlist.SyncedList( + 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=solvent_fillers, + iterable=virtual_particle_fillers, ) - self._cell_list = CellList(cell_size=1.0, shift=True) - param_dict = ParameterDict( streaming_method=OnlyTypes(StreamingMethod, allow_none=True), collision_method=OnlyTypes(CollisionMethod, allow_none=True), @@ -149,17 +150,17 @@ def cell_list(self): return self._cell_list @property - def solvent_fillers(self): - return self._solvent_fillers + def virtual_particle_fillers(self): + return self._virtual_particle_fillers - @solvent_fillers.setter - def solvent_fillers(self, value): - _set_synced_list(self._solvent_fillers, value) + @virtual_particle_fillers.setter + def virtual_particle_fillers(self, value): + _set_synced_list(self._virtual_particle_fillers, value) @property def _children(self): children = super()._children - for child in itertools.chain(self.solvent_fillers): + for child in itertools.chain(self.virtual_particle_fillers): children.extend(child._children) return children @@ -174,14 +175,15 @@ def _attach_hook(self): self._cpp_obj = _mpcd.Integrator(self._simulation.state._cpp_sys_def, self.dt) - self._solvent_fillers._sync(self._simulation, self._cpp_obj.fillers) + 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._solvent_fillers._unsync() 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: diff --git a/hoomd/mpcd/pytest/test_fill.py b/hoomd/mpcd/pytest/test_fill.py index 407a3e43fc..2e00220402 100644 --- a/hoomd/mpcd/pytest/test_fill.py +++ b/hoomd/mpcd/pytest/test_fill.py @@ -70,7 +70,7 @@ def test_run(self, simulation_factory, snap, cls, init_args): kT=1.0, geometry=cls(**init_args)) sim = simulation_factory(snap) - ig = hoomd.mpcd.Integrator(dt=0.1, solvent_fillers=[filler]) + ig = hoomd.mpcd.Integrator(dt=0.1, virtual_particle_fillers=[filler]) sim.operations.integrator = ig sim.run(1) diff --git a/hoomd/mpcd/pytest/test_integrator.py b/hoomd/mpcd/pytest/test_integrator.py index 15886c7e44..bf442c77f8 100644 --- a/hoomd/mpcd/pytest/test_integrator.py +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -84,7 +84,7 @@ def test_streaming_method(make_simulation): assert ig.streaming_method is stream -def test_solvent_fillers(make_simulation): +def test_virtual_particle_fillers(make_simulation): sim = make_simulation() geom = hoomd.mpcd.geometry.ParallelPlates(H=4.0) filler = hoomd.mpcd.fill.GeometryFiller( @@ -95,13 +95,13 @@ def test_solvent_fillers(make_simulation): ) # check that constructor assigns right - ig = hoomd.mpcd.Integrator(dt=0.1, solvent_fillers=[filler]) + ig = hoomd.mpcd.Integrator(dt=0.1, virtual_particle_fillers=[filler]) sim.operations.integrator = ig - assert len(ig.solvent_fillers) == 1 - assert filler in ig.solvent_fillers + assert len(ig.virtual_particle_fillers) == 1 + assert filler in ig.virtual_particle_fillers sim.run(0) - assert len(ig.solvent_fillers) == 1 - assert filler in ig.solvent_fillers + assert len(ig.virtual_particle_fillers) == 1 + assert filler in ig.virtual_particle_fillers # attach a second filler filler2 = hoomd.mpcd.fill.GeometryFiller( @@ -110,19 +110,19 @@ def test_solvent_fillers(make_simulation): kT=1.0, geometry=geom, ) - ig.solvent_fillers.append(filler2) - assert len(ig.solvent_fillers) == 2 - assert filler in ig.solvent_fillers - assert filler2 in ig.solvent_fillers + 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.solvent_fillers) == 2 - assert filler in ig.solvent_fillers - assert filler2 in ig.solvent_fillers + assert len(ig.virtual_particle_fillers) == 2 + assert filler in ig.virtual_particle_fillers + assert filler2 in ig.virtual_particle_fillers - ig.solvent_fillers = [] - assert len(ig.solvent_fillers) == 0 + ig.virtual_particle_fillers = [] + assert len(ig.virtual_particle_fillers) == 0 sim.run(0) - assert len(ig.solvent_fillers) == 0 + assert len(ig.virtual_particle_fillers) == 0 def test_solvent_sorter(make_simulation): @@ -160,7 +160,7 @@ def test_attach_and_detach(make_simulation): assert ig.cell_list._attached assert ig.streaming_method is None assert ig.collision_method is None - assert len(ig.solvent_fillers) == 0 + assert len(ig.virtual_particle_fillers) == 0 assert ig.solvent_sorter is None # attach with both methods From 2152ff9a8aebd42b6ead112f7a9e032fa6ca4a05 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Sat, 6 Jan 2024 14:16:05 -0600 Subject: [PATCH 24/69] Reimplement bounce-back integrator for MD particles --- hoomd/mpcd/BounceBackNVE.h | 15 --- hoomd/mpcd/CMakeLists.txt | 1 + hoomd/mpcd/__init__.py | 1 + hoomd/mpcd/methods.py | 107 +++++++++++++++++++ hoomd/mpcd/pytest/CMakeLists.txt | 1 + hoomd/mpcd/pytest/test_methods.py | 164 +++++++++++++++++++++++++++++ hoomd/mpcd/pytest/test_stream.py | 2 +- hoomd/mpcd/stream.py | 44 ++++---- sphinx-doc/module-mpcd-methods.rst | 21 ++++ sphinx-doc/package-mpcd.rst | 1 + 10 files changed, 319 insertions(+), 38 deletions(-) create mode 100644 hoomd/mpcd/methods.py create mode 100644 hoomd/mpcd/pytest/test_methods.py create mode 100644 sphinx-doc/module-mpcd-methods.rst diff --git a/hoomd/mpcd/BounceBackNVE.h b/hoomd/mpcd/BounceBackNVE.h index e73d449467..31a61b4e77 100644 --- a/hoomd/mpcd/BounceBackNVE.h +++ b/hoomd/mpcd/BounceBackNVE.h @@ -106,14 +106,6 @@ template BounceBackNVE::~BounceBackNVE() 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(); @@ -174,13 +166,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); diff --git a/hoomd/mpcd/CMakeLists.txt b/hoomd/mpcd/CMakeLists.txt index 02387f8c77..c6050d9874 100644 --- a/hoomd/mpcd/CMakeLists.txt +++ b/hoomd/mpcd/CMakeLists.txt @@ -146,6 +146,7 @@ set(files force.py geometry.py integrate.py + methods.py stream.py tune.py ) diff --git a/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index 36d08fc02e..ff1a5d6c0a 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -73,5 +73,6 @@ 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 tune diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py new file mode 100644 index 0000000000..f8dab18895 --- /dev/null +++ b/hoomd/mpcd/methods.py @@ -0,0 +1,107 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +r""" MPCD integration methods + +Defines extra integration methods useful for solutes (MD particles) embedded in +an MPCD solvent. However, 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. + +""" + +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 rule for 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 solvent. 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, 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. + + 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. + + Attributes: + filter (hoomd.filter.filter_like): Subset of particles on which to apply + this method. + + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. + + """ + + 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 _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._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() + + _class_map = {} + + @classmethod + def _register_geometry(cls, geometry, module, class_name): + cls._class_map[geometry] = (module, class_name) diff --git a/hoomd/mpcd/pytest/CMakeLists.txt b/hoomd/mpcd/pytest/CMakeLists.txt index 8b63f785d4..07e804c1bd 100644 --- a/hoomd/mpcd/pytest/CMakeLists.txt +++ b/hoomd/mpcd/pytest/CMakeLists.txt @@ -4,6 +4,7 @@ set(files __init__.py test_fill.py test_geometry.py test_integrator.py + test_methods.py test_snapshot.py test_stream.py test_tune.py diff --git a/hoomd/mpcd/pytest/test_methods.py b/hoomd/mpcd/pytest/test_methods.py new file mode 100644 index 0000000000..5825799a31 --- /dev/null +++ b/hoomd/mpcd/pytest/test_methods.py @@ -0,0 +1,164 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import hoomd +from hoomd.conftest import pickling_check +import numpy as np +import pytest + + +@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, -4.95, 3.85], [0.0, 0.0, -3.8]] + 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(H=4)) + ig = hoomd.mpcd.Integrator(dt=0.1, methods=[bb]) + return ig + + +class TestBounceBack: + + 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, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + 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, 4.95, 3.95], [-0.2, -0.2, -4.0]]) + 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, -4.95, 3.85], [-0.1, -0.1, -3.9]]) + 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, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + 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, 4.85, 3.95], [-0.2, -0.2, -4.0]]) + 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 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, 4.75, 3.85], [-0.3, -0.3, -3.9]]) + 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.V = 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, -4.95, 3.85], [-0.4, -0.1, -3.9]]) + 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, -2, 4) + 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.11, 0.12], [-0.095, -0.105, -0.09]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, [[1.2, -1.2, 1.4], [-0.9, -1.1, -0.8]]) + + def test_validate_box(self, simulation_factory, snap, integrator): + """Test box validation raises an error on run.""" + integrator.methods[0].geometry.H = 10 + + sim = simulation_factory(snap) + sim.operations.integrator = integrator + + with pytest.raises(RuntimeError): + sim.run(1) + + def test_test_of_bounds(self, simulation_factory, snap, integrator): + """Test box validation raises an error on run.""" + integrator.methods[0].geometry.H = 3.8 + + sim = simulation_factory(snap) + sim.operations.integrator = integrator + + with pytest.raises(RuntimeError): + sim.run(1) diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index 4f53be9285..c570b86a96 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -166,7 +166,7 @@ def test_step_noslip(self, simulation_factory, snap): np.testing.assert_array_almost_equal( snap.mpcd.velocity, [[-1.0, 1.0, -1.0], [1.0, 1.0, 1.0]]) - def test_slip(self, simulation_factory, snap): + def test_step_slip(self, simulation_factory, snap): """Test step with slip boundary conditions.""" if snap.communicator.rank == 0: snap.mpcd.N = 2 diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index c7973e56fa..4e0b56023a 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -22,28 +22,6 @@ **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`. -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.) - -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. -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 to ensure all particles are - "inside" the geometry. An error will be raised otherwise. - """ import hoomd @@ -117,6 +95,28 @@ class BounceBack(StreamingMethod): period (int): Number of integration steps covered by streaming step. geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. + 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 solvent 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 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 (`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. An error will be raised otherwise. + Attributes: geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. diff --git a/sphinx-doc/module-mpcd-methods.rst b/sphinx-doc/module-mpcd-methods.rst new file mode 100644 index 0000000000..1850e8a03f --- /dev/null +++ b/sphinx-doc/module-mpcd-methods.rst @@ -0,0 +1,21 @@ +.. Copyright (c) 2009-2023 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/package-mpcd.rst b/sphinx-doc/package-mpcd.rst index 3c8306b54b..d3df8f9ac8 100644 --- a/sphinx-doc/package-mpcd.rst +++ b/sphinx-doc/package-mpcd.rst @@ -19,5 +19,6 @@ hoomd.mpcd module-mpcd-collide module-mpcd-fill module-mpcd-geometry + module-mpcd-methods module-mpcd-stream module-mpcd-tune From ddeba574b9a1cf29084b5d25f4b4402a4e600916 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Fri, 12 Jan 2024 09:28:02 -0600 Subject: [PATCH 25/69] Implement suggestions from code review --- hoomd/mpcd/tune.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hoomd/mpcd/tune.py b/hoomd/mpcd/tune.py index 31dfc9682f..ce225f6738 100644 --- a/hoomd/mpcd/tune.py +++ b/hoomd/mpcd/tune.py @@ -17,8 +17,8 @@ class ParticleSorter(Tuner): r"""MPCD particle sorter. Args: - trigger (hoomd.trigger.trigger_like): Number of integration steps - between sorting. + 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 @@ -28,7 +28,7 @@ class ParticleSorter(Tuner): 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` should be a multiple of `hoomd.mpcd.collide.CollisionMethod.period` + 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. From 5707a0c4608ce2908d4c25a18578e81df1fe75cb Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Fri, 19 Jan 2024 15:42:18 -0600 Subject: [PATCH 26/69] Refactor streaming methods to not use templates --- BUILDING.rst | 3 +- hoomd/CMakeLists.txt | 6 +- hoomd/GPUPolymorph.cuh | 105 -------- hoomd/GPUPolymorph.h | 250 ------------------ hoomd/mpcd/BlockForce.cc | 31 +++ hoomd/mpcd/BlockForce.h | 143 ++++++++++ hoomd/mpcd/BounceBackStreamingMethod.cc.inc | 27 ++ hoomd/mpcd/BounceBackStreamingMethod.h | 62 +++-- .../mpcd/BounceBackStreamingMethodGPU.cc.inc | 27 ++ hoomd/mpcd/BounceBackStreamingMethodGPU.cu | 39 --- .../mpcd/BounceBackStreamingMethodGPU.cu.inc | 32 +++ hoomd/mpcd/BounceBackStreamingMethodGPU.cuh | 50 ++-- hoomd/mpcd/BounceBackStreamingMethodGPU.h | 41 ++- hoomd/mpcd/BulkStreamingMethod.cc | 28 -- hoomd/mpcd/BulkStreamingMethod.cc.inc | 26 ++ hoomd/mpcd/BulkStreamingMethod.h | 30 ++- hoomd/mpcd/BulkStreamingMethodGPU.cc | 28 -- hoomd/mpcd/BulkStreamingMethodGPU.cc.inc | 26 ++ hoomd/mpcd/BulkStreamingMethodGPU.cu | 29 -- hoomd/mpcd/BulkStreamingMethodGPU.cu.inc | 31 +++ hoomd/mpcd/BulkStreamingMethodGPU.h | 29 +- hoomd/mpcd/CMakeLists.txt | 220 ++++++++------- hoomd/mpcd/ConstantForce.cc | 27 ++ hoomd/mpcd/ConstantForce.h | 88 ++++++ hoomd/mpcd/ExternalField.cc | 40 --- hoomd/mpcd/ExternalField.cu | 25 -- hoomd/mpcd/NoForce.cc | 25 ++ hoomd/mpcd/NoForce.h | 65 +++++ hoomd/mpcd/SineForce.cc | 28 ++ hoomd/mpcd/SineForce.h | 114 ++++++++ hoomd/mpcd/StreamingMethod.cc | 3 +- hoomd/mpcd/StreamingMethod.h | 15 -- hoomd/mpcd/module.cc | 35 ++- hoomd/mpcd/stream.py | 19 +- hoomd/mpcd/test/CMakeLists.txt | 2 - hoomd/mpcd/test/external_field_test.cc | 141 ---------- hoomd/mpcd/test/external_field_test.cu | 33 --- hoomd/mpcd/test/external_field_test.cuh | 18 -- hoomd/mpcd/test/streaming_method_test.cc | 7 +- hoomd/test/test_gpu_polymorph.cc | 88 ------ hoomd/test/test_gpu_polymorph.cu | 32 --- 41 files changed, 1003 insertions(+), 1065 deletions(-) delete mode 100644 hoomd/GPUPolymorph.cuh delete mode 100644 hoomd/GPUPolymorph.h create mode 100644 hoomd/mpcd/BlockForce.cc create mode 100644 hoomd/mpcd/BlockForce.h create mode 100644 hoomd/mpcd/BounceBackStreamingMethod.cc.inc create mode 100644 hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc delete mode 100644 hoomd/mpcd/BounceBackStreamingMethodGPU.cu create mode 100644 hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc delete mode 100644 hoomd/mpcd/BulkStreamingMethod.cc create mode 100644 hoomd/mpcd/BulkStreamingMethod.cc.inc delete mode 100644 hoomd/mpcd/BulkStreamingMethodGPU.cc create mode 100644 hoomd/mpcd/BulkStreamingMethodGPU.cc.inc delete mode 100644 hoomd/mpcd/BulkStreamingMethodGPU.cu create mode 100644 hoomd/mpcd/BulkStreamingMethodGPU.cu.inc create mode 100644 hoomd/mpcd/ConstantForce.cc create mode 100644 hoomd/mpcd/ConstantForce.h delete mode 100644 hoomd/mpcd/ExternalField.cc delete mode 100644 hoomd/mpcd/ExternalField.cu create mode 100644 hoomd/mpcd/NoForce.cc create mode 100644 hoomd/mpcd/NoForce.h create mode 100644 hoomd/mpcd/SineForce.cc create mode 100644 hoomd/mpcd/SineForce.h delete mode 100644 hoomd/mpcd/test/external_field_test.cc delete mode 100644 hoomd/mpcd/test/external_field_test.cu delete mode 100644 hoomd/mpcd/test/external_field_test.cuh delete mode 100644 hoomd/test/test_gpu_polymorph.cc delete mode 100644 hoomd/test/test_gpu_polymorph.cu diff --git a/BUILDING.rst b/BUILDING.rst index b7834b6687..f1a065bdc5 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:: diff --git a/hoomd/CMakeLists.txt b/hoomd/CMakeLists.txt index 03fac4ce00..98c536d460 100644 --- a/hoomd/CMakeLists.txt +++ b/hoomd/CMakeLists.txt @@ -142,8 +142,6 @@ set(_hoomd_headers GPUArray.h GPUFlags.h GPUPartition.cuh - GPUPolymorph.h - GPUPolymorph.cuh GPUVector.h GSD.h GSDDequeWriter.h @@ -211,7 +209,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 @@ -409,7 +407,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 a139348445..0000000000 --- a/hoomd/GPUPolymorph.cuh +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) 2009-2023 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 0f4a33773d..0000000000 --- a/hoomd/GPUPolymorph.h +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) 2009-2023 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/mpcd/BlockForce.cc b/hoomd/mpcd/BlockForce.cc new file mode 100644 index 0000000000..5aeaac3673 --- /dev/null +++ b/hoomd/mpcd/BlockForce.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2009-2023 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("half_separation", + &BlockForce::getHalfSeparation, + &BlockForce::setHalfSeparation) + .def_property("half_width", &BlockForce::getHalfWidth, &BlockForce::setHalfWidth); + } + + } // 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..7051e9906e --- /dev/null +++ b/hoomd/mpcd/BlockForce.h @@ -0,0 +1,143 @@ +// Copyright (c) 2009-2023 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 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 __attribute__((visibility("default"))) BlockForce + { + public: + //! Default constructor + HOSTDEVICE BlockForce() : BlockForce(0, 0, 0) { } + + //! 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 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.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); + } + + //! 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 half separation distance between blocks + Scalar getHalfSeparation() const + { + return 0.5 * (m_H_plus_w + m_H_minus_w); + } + + //! Set the half separation distance between blocks + void setHalfSeparation(Scalar H) + { + const Scalar w = getHalfWidth(); + m_H_plus_w = H + w; + m_H_minus_w = H - w; + } + + //! Get the block half width + Scalar getHalfWidth() const + { + return 0.5 * (m_H_plus_w - m_H_minus_w); + } + + //! Set the block half width + void setHalfWidth(Scalar w) + { + const Scalar H = getHalfSeparation(); + m_H_plus_w = H + w; + m_H_minus_w = 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, H + w + Scalar m_H_minus_w; //!< Lower bound on upper block, H - w + }; + +#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/BounceBackStreamingMethod.cc.inc b/hoomd/mpcd/BounceBackStreamingMethod.cc.inc new file mode 100644 index 0000000000..88f1119129 --- /dev/null +++ b/hoomd/mpcd/BounceBackStreamingMethod.cc.inc @@ -0,0 +1,27 @@ +// Copyright (c) 2009-2023 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" +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace detail + { +template void export_BounceBackStreamingMethod<@_geometry @, @_force @>(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BounceBackStreamingMethod.h b/hoomd/mpcd/BounceBackStreamingMethod.h index 3948c08cbd..15c6860bb7 100644 --- a/hoomd/mpcd/BounceBackStreamingMethod.h +++ b/hoomd/mpcd/BounceBackStreamingMethod.h @@ -26,6 +26,7 @@ namespace mpcd * particles in confined geometries. * * \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 @@ -40,7 +41,7 @@ namespace mpcd * geometry. * */ -template +template class PYBIND11_EXPORT BounceBackStreamingMethod : public mpcd::StreamingMethod { public: @@ -51,14 +52,16 @@ class PYBIND11_EXPORT BounceBackStreamingMethod : 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 */ BounceBackStreamingMethod(std::shared_ptr sysdef, unsigned int cur_timestep, unsigned int period, int phase, - std::shared_ptr geom) + std::shared_ptr geom, + std::shared_ptr force) : mpcd::StreamingMethod(sysdef, cur_timestep, period, phase), m_geom(geom), - m_validate_geom(true) + m_validate_geom(true), m_force(force) { } @@ -78,9 +81,22 @@ class PYBIND11_EXPORT BounceBackStreamingMethod : public mpcd::StreamingMethod m_geom = geom; } + //! Set the solvent force + std::shared_ptr getForce() const + { + return m_force; + } + + //! Get the solvent force + void setForce(std::shared_ptr force) + { + m_force = force; + } + protected: std::shared_ptr m_geom; //!< Streaming geometry bool m_validate_geom; //!< If true, run a validation check on the geometry + std::shared_ptr m_force; //!< Solvent force //! Validate the system with the streaming geometry void validate(); @@ -92,7 +108,8 @@ class PYBIND11_EXPORT BounceBackStreamingMethod : public mpcd::StreamingMethod /*! * \param timestep Current time to stream */ -template void BounceBackStreamingMethod::stream(uint64_t timestep) +template +void BounceBackStreamingMethod::stream(uint64_t timestep) { if (!shouldStream(timestep)) return; @@ -118,8 +135,8 @@ template void BounceBackStreamingMethod::stream(uint64 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 +147,7 @@ template void BounceBackStreamingMethod::stream(uint64 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 +158,7 @@ template void BounceBackStreamingMethod::stream(uint64 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,7 +173,7 @@ template void BounceBackStreamingMethod::stream(uint64 m_mpcd_pdata->invalidateCellCache(); } -template void BounceBackStreamingMethod::validate() +template void BounceBackStreamingMethod::validate() { // ensure that the global box is padded enough for periodic boundaries const BoxDim box = m_pdata->getGlobalBox(); @@ -194,7 +205,8 @@ template void BounceBackStreamingMethod::validate() * 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 BounceBackStreamingMethod::validateParticles() +template +bool BounceBackStreamingMethod::validateParticles() { ArrayHandle h_pos(m_mpcd_pdata->getPositions(), access_location::host, @@ -226,18 +238,24 @@ namespace detail /*! * \param m Python module to export to */ -template void export_BounceBackStreamingMethod(pybind11::module& m) +template void export_BounceBackStreamingMethod(pybind11::module& m) { - const std::string name = "BounceBackStreamingMethod" + 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_readonly("geometry", &mpcd::BounceBackStreamingMethod::getGeometry); + std::shared_ptr, + std::shared_ptr>()) + .def_property_readonly("geometry", + &mpcd::BounceBackStreamingMethod::getGeometry) + .def_property_readonly("force", + &mpcd::BounceBackStreamingMethod::getForce); } } // 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..fa244c4a6a --- /dev/null +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc @@ -0,0 +1,27 @@ +// Copyright (c) 2009-2023 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" +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace detail + { +template void export_BounceBackStreamingMethodGPU<@_geometry @, @_force @>(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BounceBackStreamingMethodGPU.cu b/hoomd/mpcd/BounceBackStreamingMethodGPU.cu deleted file mode 100644 index 0baa68fdae..0000000000 --- a/hoomd/mpcd/BounceBackStreamingMethodGPU.cu +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file mpcd/BounceBackStreamingMethodGPU.cu - * \brief Defines GPU functions and kernels used by mpcd::BounceBackStreamingMethodGPU - * - * \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 "BounceBackStreamingMethodGPU.cuh" -#include "StreamingGeometry.h" - -#include "ExternalField.h" -#include "hoomd/GPUPolymorph.cuh" - -namespace hoomd - { -namespace mpcd - { -namespace gpu - { - -//! Template instantiation of parallel plate geometry streaming -template cudaError_t __attribute__((visibility("default"))) -confined_stream(const stream_args_t& args, - const mpcd::ParallelPlateGeometry& geom); - -//! Template instantiation of planar pore geometry streaming -template cudaError_t __attribute__((visibility("default"))) -confined_stream(const stream_args_t& args, - const mpcd::PlanarPoreGeometry& geom); - - } // end namespace gpu - } // 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..2f36643a7f --- /dev/null +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc @@ -0,0 +1,32 @@ +// Copyright (c) 2009-2023 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" +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace gpu + { +//! Template instantiation of bulk geometry streaming +template cudaError_t __attribute__((visibility("default"))) +confined_stream<@_geometry @, @_force @>(const stream_args_t& args, + const @_geometry @& geom, + const @_force @& force); + } // end namespace gpu + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BounceBackStreamingMethodGPU.cuh b/hoomd/mpcd/BounceBackStreamingMethodGPU.cuh index 4aee515faf..ef1e878573 100644 --- a/hoomd/mpcd/BounceBackStreamingMethodGPU.cuh +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cuh @@ -9,7 +9,6 @@ * \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/BounceBackStreamingMethodGPU.h b/hoomd/mpcd/BounceBackStreamingMethodGPU.h index 3446a256f8..deab017ce4 100644 --- a/hoomd/mpcd/BounceBackStreamingMethodGPU.h +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.h @@ -26,9 +26,9 @@ namespace mpcd * This method implements the GPU version of ballistic propagation of MPCD * particles in a confined geometry. */ -template +template class PYBIND11_EXPORT BounceBackStreamingMethodGPU - : public mpcd::BounceBackStreamingMethod + : public mpcd::BounceBackStreamingMethod { public: //! Constructor @@ -38,13 +38,20 @@ class PYBIND11_EXPORT BounceBackStreamingMethodGPU * \param period Number of timesteps between collisions * \param phase Phase shift for periodic updates * \param geom Streaming geometry + * \param force Solvent force */ BounceBackStreamingMethodGPU(std::shared_ptr sysdef, unsigned int cur_timestep, unsigned int period, int phase, - std::shared_ptr geom) - : mpcd::BounceBackStreamingMethod(sysdef, cur_timestep, period, phase, geom) + 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, @@ -62,7 +69,8 @@ class PYBIND11_EXPORT BounceBackStreamingMethodGPU /*! * \param timestep Current time to stream */ -template void BounceBackStreamingMethodGPU::stream(uint64_t timestep) +template +void BounceBackStreamingMethodGPU::stream(uint64_t timestep) { if (!this->shouldStream(timestep)) return; @@ -89,15 +97,16 @@ template void BounceBackStreamingMethodGPU::stream(uin 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(); @@ -112,17 +121,21 @@ namespace detail /*! * \param m Python module to export to */ -template void export_BounceBackStreamingMethodGPU(pybind11::module& m) +template void export_BounceBackStreamingMethodGPU(pybind11::module& m) { - const std::string name = "BounceBackStreamingMethod" + Geometry::getName() + "GPU"; - pybind11::class_, - mpcd::BounceBackStreamingMethod, - 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/BulkStreamingMethod.cc b/hoomd/mpcd/BulkStreamingMethod.cc deleted file mode 100644 index 43f46132c4..0000000000 --- a/hoomd/mpcd/BulkStreamingMethod.cc +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -#include "BulkStreamingMethod.h" - -namespace hoomd - { -mpcd::BulkStreamingMethod::BulkStreamingMethod(std::shared_ptr sysdef, - unsigned int cur_timestep, - unsigned int period, - int phase) - : mpcd::BounceBackStreamingMethod( - sysdef, - cur_timestep, - period, - phase, - std::make_shared()) - { - } - -void mpcd::detail::export_BulkStreamingMethod(pybind11::module& m) - { - pybind11::class_>(m, "BulkStreamingMethod") - .def(pybind11::init, unsigned int, unsigned int, int>()); - } - } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethod.cc.inc b/hoomd/mpcd/BulkStreamingMethod.cc.inc new file mode 100644 index 0000000000..31cbf30bc0 --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethod.cc.inc @@ -0,0 +1,26 @@ +// Copyright (c) 2009-2023 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" +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace detail + { +template void export_BulkStreamingMethod<@_force @>(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethod.h b/hoomd/mpcd/BulkStreamingMethod.h index 30b68ac692..312151550d 100644 --- a/hoomd/mpcd/BulkStreamingMethod.h +++ b/hoomd/mpcd/BulkStreamingMethod.h @@ -22,14 +22,25 @@ namespace hoomd namespace mpcd { +template class PYBIND11_EXPORT BulkStreamingMethod - : public BounceBackStreamingMethod + : public BounceBackStreamingMethod { public: BulkStreamingMethod(std::shared_ptr sysdef, unsigned int cur_timestep, unsigned int period, - int phase); + int phase, + std::shared_ptr force) + : mpcd::BounceBackStreamingMethod( + sysdef, + cur_timestep, + period, + phase, + std::make_shared(), + force) + { + } }; namespace detail @@ -38,7 +49,20 @@ namespace detail /*! * \param m Python module to export to */ -void export_BulkStreamingMethod(pybind11::module& m); +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("force", &mpcd::BulkStreamingMethod::getForce); + } + } // end namespace detail } // end namespace mpcd } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cc b/hoomd/mpcd/BulkStreamingMethodGPU.cc deleted file mode 100644 index 3df88075a8..0000000000 --- a/hoomd/mpcd/BulkStreamingMethodGPU.cc +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -#include "BulkStreamingMethodGPU.h" - -namespace hoomd - { -mpcd::BulkStreamingMethodGPU::BulkStreamingMethodGPU(std::shared_ptr sysdef, - unsigned int cur_timestep, - unsigned int period, - int phase) - : mpcd::BounceBackStreamingMethodGPU( - sysdef, - cur_timestep, - period, - phase, - std::make_shared()) - { - } - -void mpcd::detail::export_BulkStreamingMethodGPU(pybind11::module& m) - { - pybind11::class_>(m, "BulkStreamingMethodGPU") - .def(pybind11::init, unsigned int, unsigned int, int>()); - } - } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc b/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc new file mode 100644 index 0000000000..32656954ef --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc @@ -0,0 +1,26 @@ +// Copyright (c) 2009-2023 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" +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace detail + { +template void export_BulkStreamingMethodGPU<@_force @>(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cu b/hoomd/mpcd/BulkStreamingMethodGPU.cu deleted file mode 100644 index d63c493794..0000000000 --- a/hoomd/mpcd/BulkStreamingMethodGPU.cu +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file mpcd/BulkStreamingMethodGPU.cu - * \brief Defines GPU functions and kernels used by mpcd::BulkStreamingMethodGPU - */ - -#include "BounceBackStreamingMethodGPU.cuh" -#include "BulkGeometry.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); - - } // end namespace gpu - } // 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..e13a1f015f --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc @@ -0,0 +1,31 @@ +// Copyright (c) 2009-2023 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" +// 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 @& force); + } // end namespace gpu + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.h b/hoomd/mpcd/BulkStreamingMethodGPU.h index 57df4254e0..571bd4e6a3 100644 --- a/hoomd/mpcd/BulkStreamingMethodGPU.h +++ b/hoomd/mpcd/BulkStreamingMethodGPU.h @@ -22,14 +22,25 @@ namespace hoomd namespace mpcd { +template class PYBIND11_EXPORT BulkStreamingMethodGPU - : public BounceBackStreamingMethodGPU + : public BounceBackStreamingMethodGPU { public: BulkStreamingMethodGPU(std::shared_ptr sysdef, unsigned int cur_timestep, unsigned int period, - int phase); + int phase, + std::shared_ptr force) + : mpcd::BounceBackStreamingMethodGPU( + sysdef, + cur_timestep, + period, + phase, + std::make_shared(), + force) + { + } }; namespace detail @@ -38,7 +49,19 @@ namespace detail /*! * \param m Python module to export to */ -void export_BulkStreamingMethodGPU(pybind11::module& m); +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 diff --git a/hoomd/mpcd/CMakeLists.txt b/hoomd/mpcd/CMakeLists.txt index c6050d9874..f8a6f3ffe7 100644 --- a/hoomd/mpcd/CMakeLists.txt +++ b/hoomd/mpcd/CMakeLists.txt @@ -1,18 +1,16 @@ -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 - BulkStreamingMethod.cc CellCommunicator.cc CellThermoCompute.cc CellList.cc CollisionMethod.cc Communicator.cc - ExternalField.cc Integrator.cc ParallelPlateGeometryFiller.cc PlanarPoreGeometryFiller.cc @@ -35,7 +33,6 @@ set(_mpcd_headers BounceBackStreamingMethod.h Communicator.h CommunicatorUtilities.h - ExternalField.h Integrator.h ParticleData.h ParticleDataSnapshot.h @@ -51,94 +48,143 @@ set(_mpcd_headers VirtualParticleFiller.h ) -if (ENABLE_HIP) -list(APPEND _mpcd_sources - ATCollisionMethodGPU.cc - BulkStreamingMethodGPU.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 "") + +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 - BulkStreamingMethodGPU.cu - CellThermoComputeGPU.cu - CellListGPU.cu - BounceBackStreamingMethodGPU.cu - CommunicatorGPU.cu - ExternalField.cu - ParticleData.cu - ParallelPlateGeometryFillerGPU.cu - PlanarPoreGeometryFillerGPU.cu - SorterGPU.cu - SRDCollisionMethodGPU.cu +# generate cc and cu templates for forces and streaming geometries +set(_forces + BlockForce + ConstantForce + SineForce + NoForce ) +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() -if (ENABLE_HIP) - set(_cuda_sources ${_mpcd_cu_sources}) -endif (ENABLE_HIP) - -pybind11_add_module(_mpcd SHARED ${_mpcd_sources} ${LINK_OBJ} ${_cuda_sources} ${_mpcd_headers} NO_EXTRAS) +pybind11_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 @@ -150,22 +196,10 @@ set(files stream.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/ConstantForce.cc b/hoomd/mpcd/ConstantForce.cc new file mode 100644 index 0000000000..2b4e6f28dc --- /dev/null +++ b/hoomd/mpcd/ConstantForce.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2009-2023 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", &ConstantForce::getForce, &ConstantForce::setForce); + } + + } // 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..b35d30c32a --- /dev/null +++ b/hoomd/mpcd/ConstantForce.h @@ -0,0 +1,88 @@ +// Copyright (c) 2009-2023 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 1d7574e853..0000000000 --- a/hoomd/mpcd/ExternalField.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2009-2023 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 e74b94563f..0000000000 --- a/hoomd/mpcd/ExternalField.cu +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2009-2023 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/NoForce.cc b/hoomd/mpcd/NoForce.cc new file mode 100644 index 0000000000..78ba69c986 --- /dev/null +++ b/hoomd/mpcd/NoForce.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2009-2023 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..a98024d00d --- /dev/null +++ b/hoomd/mpcd/NoForce.h @@ -0,0 +1,65 @@ +// Copyright (c) 2009-2023 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/SineForce.cc b/hoomd/mpcd/SineForce.cc new file mode 100644 index 0000000000..b172f84ddd --- /dev/null +++ b/hoomd/mpcd/SineForce.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2009-2023 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..bdb4ebc045 --- /dev/null +++ b/hoomd/mpcd/SineForce.h @@ -0,0 +1,114 @@ +// Copyright (c) 2009-2023 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 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 __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 z. 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.z), 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_F = 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 z + }; + +#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/StreamingMethod.cc b/hoomd/mpcd/StreamingMethod.cc index 8cfedc6daa..287fff82e1 100644 --- a/hoomd/mpcd/StreamingMethod.cc +++ b/hoomd/mpcd/StreamingMethod.cc @@ -120,8 +120,7 @@ void mpcd::detail::export_StreamingMethod(pybind11::module& m) m, "StreamingMethod") .def(pybind11::init, unsigned int, unsigned int, int>()) - .def_property_readonly("period", &mpcd::StreamingMethod::getPeriod) - .def_property("field", &mpcd::StreamingMethod::getField, &mpcd::StreamingMethod::setField); + .def_property_readonly("period", &mpcd::StreamingMethod::getPeriod); } } // end namespace hoomd diff --git a/hoomd/mpcd/StreamingMethod.h b/hoomd/mpcd/StreamingMethod.h index 29487f009d..83d3796b8b 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,17 +64,6 @@ class PYBIND11_EXPORT StreamingMethod : public Autotuned return m_mpcd_dt; } - std::shared_ptr> getField() const - { - return m_field; - } - - //! Set the external field - void setField(std::shared_ptr> field) - { - m_field = field; - } - //! Get the streaming period unsigned int getPeriod() const { @@ -103,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/module.cc b/hoomd/mpcd/module.cc index 409f613def..d40f508d02 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" @@ -120,6 +126,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); @@ -134,14 +145,24 @@ PYBIND11_MODULE(_mpcd, m) mpcd::detail::export_PlanarPoreGeometry(m); mpcd::detail::export_StreamingMethod(m); - mpcd::detail::export_ExternalFieldPolymorph(m); - mpcd::detail::export_BulkStreamingMethod(m); - mpcd::detail::export_BounceBackStreamingMethod(m); - mpcd::detail::export_BounceBackStreamingMethod(m); + mpcd::detail::export_BulkStreamingMethod(m); + mpcd::detail::export_BulkStreamingMethod(m); + 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_BulkStreamingMethodGPU(m); - mpcd::detail::export_BounceBackStreamingMethodGPU(m); - mpcd::detail::export_BounceBackStreamingMethodGPU(m); + mpcd::detail::export_BulkStreamingMethodGPU(m); + mpcd::detail::export_BulkStreamingMethodGPU(m); + 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); diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index 4e0b56023a..08a68e9888 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -74,16 +74,12 @@ class Bulk(StreamingMethod): def _attach_hook(self): sim = self._simulation if isinstance(sim.device, hoomd.device.GPU): - class_ = _mpcd.BulkStreamingMethodGPU + class_ = _mpcd.BulkStreamingMethodNoForceGPU else: - class_ = _mpcd.BulkStreamingMethod + class_ = _mpcd.BulkStreamingMethodNoForce - self._cpp_obj = class_( - sim.state._cpp_sys_def, - sim.timestep, - self.period, - 0, - ) + self._cpp_obj = class_(sim.state._cpp_sys_def, sim.timestep, + self.period, 0, None) super()._attach_hook() @@ -139,8 +135,10 @@ def _attach_hook(self): try: class_info = self._class_map[geom_type] except KeyError: - class_info = (_mpcd, - "BounceBackStreamingMethod" + geom_type.__name__) + class_info = ( + _mpcd, + "BounceBackStreamingMethod" + geom_type.__name__ + "NoForce", + ) class_info = list(class_info) if isinstance(sim.device, hoomd.device.GPU): class_info[1] += "GPU" @@ -153,6 +151,7 @@ def _attach_hook(self): self.period, 0, self.geometry._cpp_obj, + None, ) super()._attach_hook() diff --git a/hoomd/mpcd/test/CMakeLists.txt b/hoomd/mpcd/test/CMakeLists.txt index 19ce96f0a7..704eecc993 100644 --- a/hoomd/mpcd/test/CMakeLists.txt +++ b/hoomd/mpcd/test/CMakeLists.txt @@ -3,7 +3,6 @@ set(TEST_LIST at_collision_method cell_list cell_thermo_compute - #external_field parallel_plate_geometry_filler planar_pore_geometry_filler sorter @@ -29,7 +28,6 @@ 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 "") diff --git a/hoomd/mpcd/test/external_field_test.cc b/hoomd/mpcd/test/external_field_test.cc deleted file mode 100644 index e4dbd2baff..0000000000 --- a/hoomd/mpcd/test/external_field_test.cc +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2009-2023 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 81989d26ae..0000000000 --- a/hoomd/mpcd/test/external_field_test.cu +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2009-2023 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 d195b5cd68..0000000000 --- a/hoomd/mpcd/test/external_field_test.cuh +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2009-2023 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/streaming_method_test.cc b/hoomd/mpcd/test/streaming_method_test.cc index 396be990e9..63c7ac6f59 100644 --- a/hoomd/mpcd/test/streaming_method_test.cc +++ b/hoomd/mpcd/test/streaming_method_test.cc @@ -5,6 +5,7 @@ #ifdef ENABLE_HIP #include "hoomd/mpcd/BulkStreamingMethodGPU.h" #endif // ENABLE_HIP +#include "hoomd/mpcd/NoForce.h" #include "hoomd/SnapshotSystemData.h" #include "hoomd/test/upp11_config.h" @@ -32,7 +33,7 @@ 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 - std::shared_ptr stream = std::make_shared(sysdef, 2, 2, 1); + std::shared_ptr stream = std::make_shared(sysdef, 2, 2, 1, nullptr); auto cl = std::make_shared(sysdef, 1.0, false); stream->setCellList(cl); @@ -111,14 +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) { - 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) { - streaming_method_basic_test( + streaming_method_basic_test>( std::make_shared(ExecutionConfiguration::GPU)); } #endif // ENABLE_HIP diff --git a/hoomd/test/test_gpu_polymorph.cc b/hoomd/test/test_gpu_polymorph.cc deleted file mode 100644 index 43426a57db..0000000000 --- a/hoomd/test/test_gpu_polymorph.cc +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2009-2023 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 02738d97b3..0000000000 --- a/hoomd/test/test_gpu_polymorph.cu +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2009-2023 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*); From 41eb86b63d71da46d729bc17288cee1d19058292 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Wed, 31 Jan 2024 11:35:24 -0600 Subject: [PATCH 27/69] Remove unused Integrator._children --- hoomd/mpcd/integrate.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index fccdf2bf3c..a8ef0816de 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -1,8 +1,6 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -import itertools - import hoomd from hoomd.data.parameterdicts import ParameterDict from hoomd.data import syncedlist @@ -157,13 +155,6 @@ def virtual_particle_fillers(self): def virtual_particle_fillers(self, value): _set_synced_list(self._virtual_particle_fillers, value) - @property - def _children(self): - children = super()._children - for child in itertools.chain(self.virtual_particle_fillers): - children.extend(child._children) - return children - def _attach_hook(self): self._cell_list._attach(self._simulation) if self.streaming_method is not None: From 4c84257bc8ced31519ea84b731b08af019cc045c Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Wed, 31 Jan 2024 14:01:57 -0600 Subject: [PATCH 28/69] Fix compile errors from auto-formatter --- hoomd/mpcd/BounceBackStreamingMethod.cc.inc | 5 ++++- hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc | 5 ++++- hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc | 9 ++++++--- hoomd/mpcd/BulkStreamingMethod.cc.inc | 4 +++- hoomd/mpcd/BulkStreamingMethodGPU.cc.inc | 4 +++- hoomd/mpcd/BulkStreamingMethodGPU.cu.inc | 8 +++++--- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/hoomd/mpcd/BounceBackStreamingMethod.cc.inc b/hoomd/mpcd/BounceBackStreamingMethod.cc.inc index 88f1119129..e9f7c49b3a 100644 --- a/hoomd/mpcd/BounceBackStreamingMethod.cc.inc +++ b/hoomd/mpcd/BounceBackStreamingMethod.cc.inc @@ -13,6 +13,9 @@ #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 @@ -21,7 +24,7 @@ namespace mpcd { namespace detail { -template void export_BounceBackStreamingMethod<@_geometry @, @_force @>(pybind11::module& m); +template void export_BounceBackStreamingMethod(pybind11::module& m); } // end namespace detail } // end namespace mpcd } // end namespace hoomd diff --git a/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc b/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc index fa244c4a6a..8b559e6db3 100644 --- a/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc @@ -13,6 +13,9 @@ #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 @@ -21,7 +24,7 @@ namespace mpcd { namespace detail { -template void export_BounceBackStreamingMethodGPU<@_geometry @, @_force @>(pybind11::module& m); +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 index 2f36643a7f..2b561ad87b 100644 --- a/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc @@ -14,6 +14,9 @@ #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 @@ -24,9 +27,9 @@ namespace gpu { //! Template instantiation of bulk geometry streaming template cudaError_t __attribute__((visibility("default"))) -confined_stream<@_geometry @, @_force @>(const stream_args_t& args, - const @_geometry @& geom, - const @_force @& force); +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/BulkStreamingMethod.cc.inc b/hoomd/mpcd/BulkStreamingMethod.cc.inc index 31cbf30bc0..ded2b404ff 100644 --- a/hoomd/mpcd/BulkStreamingMethod.cc.inc +++ b/hoomd/mpcd/BulkStreamingMethod.cc.inc @@ -12,6 +12,8 @@ // clang-format off #include "hoomd/mpcd/@_force@.h" #include "hoomd/mpcd/BulkStreamingMethod.h" + +#define FORCE_CLASS @_force@ // clang-format on namespace hoomd @@ -20,7 +22,7 @@ namespace mpcd { namespace detail { -template void export_BulkStreamingMethod<@_force @>(pybind11::module& m); +template void export_BulkStreamingMethod(pybind11::module& m); } // end namespace detail } // end namespace mpcd } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc b/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc index 32656954ef..d051fe82bc 100644 --- a/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc @@ -12,6 +12,8 @@ // clang-format off #include "hoomd/mpcd/@_force@.h" #include "hoomd/mpcd/BulkStreamingMethodGPU.h" + +#define FORCE_CLASS @_force@ // clang-format on namespace hoomd @@ -20,7 +22,7 @@ namespace mpcd { namespace detail { -template void export_BulkStreamingMethodGPU<@_force @>(pybind11::module& m); +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 index e13a1f015f..03e2d2b530 100644 --- a/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc @@ -13,6 +13,8 @@ #include "hoomd/mpcd/@_force@.h" #include "hoomd/mpcd/BulkGeometry.h" #include "hoomd/mpcd/BounceBackStreamingMethodGPU.cuh" + +#define FORCE_CLASS @_force@ // clang-format on namespace hoomd @@ -23,9 +25,9 @@ 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 @& force); +confined_stream(const stream_args_t& args, + const detail::BulkGeometry& geom, + const FORCE_CLASS& force); } // end namespace gpu } // end namespace mpcd } // end namespace hoomd From 12fbb37887fa7120fea2f124a67489c5d57cdbd6 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Wed, 31 Jan 2024 14:03:46 -0600 Subject: [PATCH 29/69] Expose solvent forces to Python and test --- hoomd/mpcd/ConstantForce.cc | 14 +- hoomd/mpcd/SineForce.h | 2 +- hoomd/mpcd/__init__.py | 1 + hoomd/mpcd/force.py | 291 +++++++++---------------------- hoomd/mpcd/pytest/CMakeLists.txt | 1 + hoomd/mpcd/pytest/test_force.py | 90 ++++++++++ hoomd/mpcd/test/CMakeLists.txt | 5 +- hoomd/mpcd/test/force_test.cc | 77 ++++++++ 8 files changed, 273 insertions(+), 208 deletions(-) create mode 100644 hoomd/mpcd/pytest/test_force.py create mode 100644 hoomd/mpcd/test/force_test.cc diff --git a/hoomd/mpcd/ConstantForce.cc b/hoomd/mpcd/ConstantForce.cc index 2b4e6f28dc..b62254d71c 100644 --- a/hoomd/mpcd/ConstantForce.cc +++ b/hoomd/mpcd/ConstantForce.cc @@ -19,7 +19,19 @@ void export_ConstantForce(pybind11::module& m) { pybind11::class_>(m, "ConstantForce") .def(pybind11::init()) - .def_property("force", &ConstantForce::getForce, &ConstantForce::setForce); + .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 diff --git a/hoomd/mpcd/SineForce.h b/hoomd/mpcd/SineForce.h index bdb4ebc045..f995e85350 100644 --- a/hoomd/mpcd/SineForce.h +++ b/hoomd/mpcd/SineForce.h @@ -83,7 +83,7 @@ class __attribute__((visibility("default"))) SineForce //! Set the sine wavenumber void setWavenumber(Scalar k) { - m_F = k; + m_k = k; } #ifndef __HIPCC__ diff --git a/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index ff1a5d6c0a..86e8f01f5f 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -70,6 +70,7 @@ 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 diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index b7e82a9591..d2e54a9ad8 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -1,69 +1,48 @@ # Copyright (c) 2009-2023 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 solvent 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`). - -.. note:: - - 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. +MPCD can apply a body force to each MPCD particle as a function of position. +The external force should be compatible with the chosen `~hoomd.mpcd.geometry`. +Global momentum conservation is typically broken by adding a solvent 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 +coupled to the solvent through the collision step. Additionally, a thermostat +will likely be required to maintain temperature control in the driven system. """ -import hoomd from hoomd import _hoomd - -from . import _mpcd +from hoomd.data.parameterdicts import ParameterDict +from hoomd.mpcd import _mpcd +from hoomd.operation import _HOOMDBaseObject -class _force(): - r""" Base external force field. +class SolventForce(_HOOMDBaseObject): + """Solvent force. - 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 SolventForce is a body force applied to each solvent 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(SolventForce): + 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. + half_separation (float): Half the distance between the centers of the + blocks. + half_width (float): Half the 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 particles + in blocks defined along the *z* direction by the ``half_separation`` *H* and + the ``half_width`` *w*. The force in *x* is :math:`+F` in the upper block, + :math:`-F` in the lower block, and zero otherwise. .. math:: :nowrap: @@ -76,152 +55,79 @@ class block(_force): \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. - - This force field can be used to implement the double-parabola method for measuring + The BlockForce 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. - - Examples:: - - # fully specified blocks - force.block(F=1.0, H=5.0, w=5.0) + the size of the simulation box in *z*. - # default blocks to full box - force.block(F=0.5) + Warning: + You should define the blocks to lie fully within the simulation box and + to not overlap each other. - .. note:: + Attributes: + force (float): Magnitude of the force in *x* per particle. - 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. + half_separation (float): Half the distance between the centers of the + blocks. - .. versionadded:: 2.6 + half_width (float): Half the width of each block. """ - 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. - - Args: - F (tuple): 3d vector specifying the force per particle. - - 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. + def __init__(self, force, half_separation=None, half_width=None): + super().__init__() - Examples:: + param_dict = ParameterDict( + force=float(force), + half_separation=float(half_separation), + half_width=float(half_width), + ) + self._param_dict.update(param_dict) - # tuple - force.constant((1.,0.,0.)) + def _attach_hook(self): + self._cpp_obj = _mpcd.BlockForce(self.force, self.half_separation, + self.half_width) + super()._attach_hook() - # list - force.constant([1.,2.,3.]) - # NumPy array - g = np.array([0.,0.,-1.]) - force.constant(g) +class ConstantForce(SolventForce): + r"""Constant force. - .. note:: + Args: + force (`tuple` [`float`, `float`, `float`]): Force vector per particle. - 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. + 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 having no-slip boundary conditions. + It is also useful for measuring diffusion coefficients with nonequilibrium + methods. - .. versionadded:: 2.6 + Attributes: + force (`tuple` [`float`, `float`, `float`]): Force vector per particle. """ - def __init__(self, F): + def __init__(self, force): + super().__init__() - 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') + param_dict = ParameterDict(force=(float, float, float)) + param_dict["force"] = force + self._param_dict.update(param_dict) - # initialize python level - _force.__init__(self) - self._F = 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() - # initialize c++ - self._cpp.ConstantForce( - _hoomd.make_scalar3(self.F[0], self.F[1], self.F[2])) - @property - def F(self): - return self._F - - -class sine(_force): - r""" Sine force. +class SineForce(SolventForce): + 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 *z* with wavenumber *k*: .. math:: @@ -229,45 +135,22 @@ class sine(_force): 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. - - Examples:: + one period of the sine. - # one period - k0 = 2.*np.pi/box.Lz - force.sine(F=1.0, k=k0) + Attributes: + amplitude (float): Amplitude of the sinusoid. - # two periods - force.sine(F=0.5, k=2*k0) - - 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. - - .. note:: - - 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. - - .. versionadded:: 2.6 + wavenumber (float): Wavenumber for the sinusoid. """ - def __init__(self, F, k): - - # initialize python level - _force.__init__(self) - self._F = F - self._k = k - - # 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/pytest/CMakeLists.txt b/hoomd/mpcd/pytest/CMakeLists.txt index 07e804c1bd..cbdddfcaae 100644 --- a/hoomd/mpcd/pytest/CMakeLists.txt +++ b/hoomd/mpcd/pytest/CMakeLists.txt @@ -2,6 +2,7 @@ set(files __init__.py test_collide.py test_fill.py + test_force.py test_geometry.py test_integrator.py test_methods.py diff --git a/hoomd/mpcd/pytest/test_force.py b/hoomd/mpcd/pytest/test_force.py new file mode 100644 index 0000000000..c8f09d79fb --- /dev/null +++ b/hoomd/mpcd/pytest/test_force.py @@ -0,0 +1,90 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import hoomd +from hoomd.conftest import pickling_check +import numpy as np +import pytest + + +def test_block_force(simulation_factory): + # make the force + force = hoomd.mpcd.force.BlockForce(force=2.0, + half_separation=3.0, + half_width=0.5) + assert force.force == 2.0 + assert force.half_separation == 3.0 + assert force.half_width == 0.5 + pickling_check(force) + + # try changing the values + force.force = 1.0 + force.half_separation = 2.0 + force.half_width = 0.25 + assert force.force == 1.0 + assert force.half_separation == 2.0 + assert force.half_width == 0.25 + + # now attach and check again + sim = simulation_factory() + force._attach(sim) + assert force.force == 1.0 + assert force.half_separation == 2.0 + assert force.half_width == 0.25 + + # change values while attached + force.force = 2.0 + force.half_separation = 3.0 + force.half_width = 0.5 + assert force.force == 2.0 + assert force.half_separation == 3.0 + assert force.half_width == 0.5 + 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/test/CMakeLists.txt b/hoomd/mpcd/test/CMakeLists.txt index 704eecc993..1fc3d23344 100644 --- a/hoomd/mpcd/test/CMakeLists.txt +++ b/hoomd/mpcd/test/CMakeLists.txt @@ -3,6 +3,7 @@ set(TEST_LIST at_collision_method cell_list cell_thermo_compute + force parallel_plate_geometry_filler planar_pore_geometry_filler sorter @@ -48,7 +49,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) @@ -62,7 +63,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/force_test.cc b/hoomd/mpcd/test/force_test.cc new file mode 100644 index 0000000000..4a554405ba --- /dev/null +++ b/hoomd/mpcd/test/force_test.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2009-2023 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, 3, 0.2); + std::vector ref_pos = {make_scalar3(0, 0, 2.7), + make_scalar3(1, 2, 2.9), + make_scalar3(2, 1, 3.3), + make_scalar3(-1, 0, -2), + make_scalar3(4, 5, -3), + make_scalar3(4, 5, -4)}; + 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, 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_force(force, ref_pos, ref_force); + } From a3c01b7c67a17366c07c9e55a9f662b9f192fc17 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Wed, 31 Jan 2024 16:56:35 -0600 Subject: [PATCH 30/69] Add force to streaming method and test --- hoomd/mpcd/BounceBackStreamingMethod.h | 2 +- hoomd/mpcd/BulkStreamingMethod.h | 2 +- hoomd/mpcd/module.cc | 41 ++++++++--- hoomd/mpcd/pytest/test_stream.py | 52 ++++++++++++++ hoomd/mpcd/stream.py | 99 +++++++++++++++++++++----- 5 files changed, 168 insertions(+), 28 deletions(-) diff --git a/hoomd/mpcd/BounceBackStreamingMethod.h b/hoomd/mpcd/BounceBackStreamingMethod.h index 15c6860bb7..b70fd8a346 100644 --- a/hoomd/mpcd/BounceBackStreamingMethod.h +++ b/hoomd/mpcd/BounceBackStreamingMethod.h @@ -254,7 +254,7 @@ template void export_BounceBackStreamingMethod(pybi std::shared_ptr>()) .def_property_readonly("geometry", &mpcd::BounceBackStreamingMethod::getGeometry) - .def_property_readonly("force", + .def_property_readonly("solvent_force", &mpcd::BounceBackStreamingMethod::getForce); } } // end namespace detail diff --git a/hoomd/mpcd/BulkStreamingMethod.h b/hoomd/mpcd/BulkStreamingMethod.h index 312151550d..a9495e4424 100644 --- a/hoomd/mpcd/BulkStreamingMethod.h +++ b/hoomd/mpcd/BulkStreamingMethod.h @@ -60,7 +60,7 @@ template void export_BulkStreamingMethod(pybind11::module& m) unsigned int, int, std::shared_ptr>()) - .def_property_readonly("force", &mpcd::BulkStreamingMethod::getForce); + .def_property_readonly("solvent_force", &mpcd::BulkStreamingMethod::getForce); } } // end namespace detail diff --git a/hoomd/mpcd/module.cc b/hoomd/mpcd/module.cc index d40f508d02..0b8f867198 100644 --- a/hoomd/mpcd/module.cc +++ b/hoomd/mpcd/module.cc @@ -145,24 +145,47 @@ PYBIND11_MODULE(_mpcd, m) mpcd::detail::export_PlanarPoreGeometry(m); mpcd::detail::export_StreamingMethod(m); - mpcd::detail::export_BulkStreamingMethod(m); + // bulk + mpcd::detail::export_BulkStreamingMethod(m); mpcd::detail::export_BulkStreamingMethod(m); - mpcd::detail::export_BounceBackStreamingMethod(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); + 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_BulkStreamingMethodGPU(m); - mpcd::detail::export_BulkStreamingMethodGPU(m); - mpcd::detail::export_BounceBackStreamingMethodGPU( + mpcd::detail::export_BounceBackStreamingMethodGPU( m); - mpcd::detail::export_BounceBackStreamingMethodGPU(m); - 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); diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index c570b86a96..72b1a3ed32 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -65,6 +65,58 @@ def test_pickling(self, simulation_factory, snap, cls, init_args): sim.run(0) pickling_check(sm) + @pytest.mark.parametrize( + "force", + [ + hoomd.mpcd.force.BlockForce( + force=2.0, half_separation=3.0, half_width=0.5), + hoomd.mpcd.force.ConstantForce(force=(1, -2, 3)), + hoomd.mpcd.force.SineForce(amplitude=2.0, wavenumber=1), + ], + ids=["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, solvent_force=force) + assert sm.solvent_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.solvent_force is force + pickling_check(sm) + + def test_forced_step(self, simulation_factory, snap, cls, init_args): + """Test a step with particle starting in the middle, 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, + solvent_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: diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index 08a68e9888..b8a721056f 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -26,17 +26,19 @@ import hoomd from hoomd.data.parameterdicts import ParameterDict +from hoomd.data.typeconverter import OnlyTypes from hoomd.mpcd import _mpcd +from hoomd.mpcd.force import SolventForce from hoomd.mpcd.geometry import Geometry from hoomd.operation import Operation -# TODO: add force class StreamingMethod(Operation): """Base streaming method. Args: period (int): Number of integration steps covered by streaming step. + solvent_force (SolventForce): Force on solvent. Attributes: period (int): Number of integration steps covered by streaming step. @@ -50,12 +52,17 @@ class StreamingMethod(Operation): used if an external force is applied, and more faithful numerical integration is needed. + solvent_force (SolventForce): Force on solvent. + """ - def __init__(self, period): + def __init__(self, period, solvent_force=None): super().__init__() - param_dict = ParameterDict(period=int(period),) + param_dict = ParameterDict(period=int(period), + solvent_force=OnlyTypes(SolventForce, + allow_none=True)) + param_dict["solvent_force"] = solvent_force self._param_dict.update(param_dict) @@ -64,6 +71,7 @@ class Bulk(StreamingMethod): Args: period (int): Number of integration steps covered by streaming step. + solvent_force (SolventForce): Force on solvent. `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 @@ -73,16 +81,54 @@ class Bulk(StreamingMethod): def _attach_hook(self): sim = self._simulation - if isinstance(sim.device, hoomd.device.GPU): - class_ = _mpcd.BulkStreamingMethodNoForceGPU + + # attach and use solvent force if present + if self.solvent_force is not None: + self.solvent_force._attach(sim) + solvent_force = self.solvent_force._cpp_obj else: - class_ = _mpcd.BulkStreamingMethodNoForce + solvent_force = None + + # try to find force in map, otherwise use default + force_type = type(self.solvent_force) + try: + class_info = self._cpp_class_map[force_type] + except KeyError: + if self.solvent_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, None) + self._cpp_obj = class_( + sim.state._cpp_sys_def, + sim.timestep, + self.period, + 0, + solvent_force, + ) super()._attach_hook() + def _detach_hook(self): + if self.solvent_force is not None: + self.solvent_force._detach() + super()._detach_hook() + + _cpp_class_map = {} + + @classmethod + def _register_cpp_class(cls, force, module, class_name): + cls._cpp_class_map[force] = (module, class_name) + class BounceBack(StreamingMethod): """Streaming with bounce-back rule for surfaces. @@ -90,6 +136,7 @@ class BounceBack(StreamingMethod): Args: period (int): Number of integration steps covered by streaming step. geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. + solvent_force (SolventForce): Force on solvent. 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 @@ -118,8 +165,8 @@ class BounceBack(StreamingMethod): """ - def __init__(self, period, geometry): - super().__init__(period) + def __init__(self, period, geometry, solvent_force=None): + super().__init__(period, solvent_force) param_dict = ParameterDict(geometry=Geometry) param_dict["geometry"] = geometry @@ -130,14 +177,27 @@ def _attach_hook(self): self.geometry._attach(sim) - # try to find class in map, otherwise default to internal MPCD module + # attach and use solvent force if present + if self.solvent_force is not None: + self.solvent_force._attach(sim) + solvent_force = self.solvent_force._cpp_obj + else: + solvent_force = None + + # try to find force in map, otherwise use default geom_type = type(self.geometry) + force_type = type(self.solvent_force) try: - class_info = self._class_map[geom_type] + class_info = self._cpp_class_map[geom_type, force_type] except KeyError: + if self.solvent_force is not None: + force_name = force_type.__name__ + else: + force_name = "NoForce" + class_info = ( _mpcd, - "BounceBackStreamingMethod" + geom_type.__name__ + "NoForce", + "BounceBackStreamingMethod" + geom_type.__name__ + force_name, ) class_info = list(class_info) if isinstance(sim.device, hoomd.device.GPU): @@ -151,17 +211,22 @@ def _attach_hook(self): self.period, 0, self.geometry._cpp_obj, - None, + solvent_force, ) super()._attach_hook() def _detach_hook(self): self.geometry._detach() + if self.solvent_force is not None: + self.solvent_force._detach() super()._detach_hook() - _class_map = {} + _cpp_class_map = {} @classmethod - def _register_geometry(cls, geometry, module, class_name): - cls._class_map[geometry] = (module, class_name) + def _register_cpp_class(cls, geometry, force, module, class_name): + # we will allow "None" for the force, but we need its class type not its value + if force is None: + force = type(None) + cls._cpp_cpp_class_map[geometry, force] = (module, class_name) From f369ea865dd358b41d7f559248c4878260267705 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Wed, 31 Jan 2024 17:01:53 -0600 Subject: [PATCH 31/69] Add forces to documentation --- hoomd/mpcd/force.py | 11 ++++++----- sphinx-doc/module-mpcd-force.rst | 27 +++++++++++++++++++++++++++ sphinx-doc/package-mpcd.rst | 1 + 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 sphinx-doc/module-mpcd-force.rst diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index d2e54a9ad8..b4b8f8bbfa 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -4,8 +4,8 @@ r""" MPCD solvent forces. MPCD can apply a body force to each MPCD particle as a function of position. -The external force should be compatible with the chosen `~hoomd.mpcd.geometry`. -Global momentum conservation is typically broken by adding a solvent force, so +The external force should be compatible with the chosen :mod:`~hoomd.mpcd.geometry`. +Global momentum conservation can be broken by adding a solvent 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 @@ -23,10 +23,11 @@ class SolventForce(_HOOMDBaseObject): """Solvent force. - The SolventForce is a body force applied to each solvent particle. This + The `SolventForce` is a body force applied to each solvent particle. This class should not be instantiated directly. It exists for type checking. """ + pass @@ -55,7 +56,7 @@ class BlockForce(SolventForce): \end{cases} \end{equation} - The BlockForce can be used to implement the double-parabola method for measuring + The `BlockForce` 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*. @@ -126,7 +127,7 @@ class SineForce(SolventForce): amplitude (float): Amplitude of the sinusoid. wavenumber (float): Wavenumber for the sinusoid. - SineForce applies a force with amplitude *F* in *x* that is sinusoidally + `SineForce` applies a force with amplitude *F* in *x* that is sinusoidally varying in *z* with wavenumber *k*: .. math:: diff --git a/sphinx-doc/module-mpcd-force.rst b/sphinx-doc/module-mpcd-force.rst new file mode 100644 index 0000000000..a929ec61f8 --- /dev/null +++ b/sphinx-doc/module-mpcd-force.rst @@ -0,0 +1,27 @@ +.. Copyright (c) 2009-2023 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: + + SolventForce + BlockForce + ConstantForce + SineForce + +.. rubric:: Details + +.. automodule:: hoomd.mpcd.force + :synopsis: Solvent forces. + :members: SolventForce, + BlockForce, + ConstantForce, + SineForce + :show-inheritance: diff --git a/sphinx-doc/package-mpcd.rst b/sphinx-doc/package-mpcd.rst index d3df8f9ac8..c55658e807 100644 --- a/sphinx-doc/package-mpcd.rst +++ b/sphinx-doc/package-mpcd.rst @@ -18,6 +18,7 @@ hoomd.mpcd module-mpcd-collide module-mpcd-fill + module-mpcd-force module-mpcd-geometry module-mpcd-methods module-mpcd-stream From 0160cba2fd71b9a8e3c0ea17a8174820cbb5c960 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 1 Feb 2024 10:10:23 -0600 Subject: [PATCH 32/69] Remove unused header --- hoomd/mpcd/ExternalField.h | 185 ------------------------------------- 1 file changed, 185 deletions(-) delete mode 100644 hoomd/mpcd/ExternalField.h diff --git a/hoomd/mpcd/ExternalField.h b/hoomd/mpcd/ExternalField.h deleted file mode 100644 index 8cf97f434e..0000000000 --- a/hoomd/mpcd/ExternalField.h +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) 2009-2023 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_ From 90bc3b0e065dfd7fc39d3c7abf5e9090be9dd2b1 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 1 Feb 2024 14:21:16 -0600 Subject: [PATCH 33/69] Rotate geometries to be finite in y instead of z --- hoomd/mpcd/BounceBackNVE.h | 79 ++----- hoomd/mpcd/BounceBackNVEGPU.h | 7 +- hoomd/mpcd/BounceBackStreamingMethod.h | 74 ++---- hoomd/mpcd/BounceBackStreamingMethodGPU.h | 12 +- hoomd/mpcd/BulkGeometry.h | 9 - hoomd/mpcd/ParallelPlateGeometry.h | 44 ++-- hoomd/mpcd/ParallelPlateGeometryFiller.cc | 33 ++- hoomd/mpcd/ParallelPlateGeometryFiller.h | 4 +- hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc | 4 +- hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu | 28 +-- hoomd/mpcd/ParallelPlateGeometryFillerGPU.cuh | 4 +- hoomd/mpcd/PlanarPoreGeometry.h | 37 +-- hoomd/mpcd/PlanarPoreGeometryFiller.cc | 45 ++-- hoomd/mpcd/PlanarPoreGeometryFillerGPU.cu | 4 +- hoomd/mpcd/geometry.py | 6 +- hoomd/mpcd/methods.py | 12 + hoomd/mpcd/pytest/test_methods.py | 60 +++-- hoomd/mpcd/pytest/test_stream.py | 219 ++++++++---------- hoomd/mpcd/stream.py | 12 + .../parallel_plate_geometry_filler_test.cc | 24 +- .../test/planar_pore_geometry_filler_test.cc | 12 +- 21 files changed, 279 insertions(+), 450 deletions(-) diff --git a/hoomd/mpcd/BounceBackNVE.h b/hoomd/mpcd/BounceBackNVE.h index 31a61b4e77..00369c7f41 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,63 +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_validate_geom) - validate(); - // particle data ArrayHandle h_pos(m_pdata->getPositions(), access_location::host, @@ -203,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); @@ -254,10 +204,6 @@ 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; } } @@ -277,10 +223,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.h b/hoomd/mpcd/BounceBackNVEGPU.h index 18d21fae4e..c90633f392 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.h b/hoomd/mpcd/BounceBackStreamingMethod.h index b70fd8a346..f5f582be26 100644 --- a/hoomd/mpcd/BounceBackStreamingMethod.h +++ b/hoomd/mpcd/BounceBackStreamingMethod.h @@ -33,12 +33,10 @@ namespace mpcd * 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 @@ -58,10 +56,9 @@ class PYBIND11_EXPORT BounceBackStreamingMethod : public mpcd::StreamingMethod unsigned int cur_timestep, unsigned int period, int phase, - std::shared_ptr geom, + std::shared_ptr geom, std::shared_ptr force) - : mpcd::StreamingMethod(sysdef, cur_timestep, period, phase), m_geom(geom), - m_validate_geom(true), m_force(force) + : mpcd::StreamingMethod(sysdef, cur_timestep, period, phase), m_geom(geom), m_force(force) { } @@ -69,15 +66,14 @@ class PYBIND11_EXPORT BounceBackStreamingMethod : 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; } @@ -93,16 +89,12 @@ class PYBIND11_EXPORT BounceBackStreamingMethod : public mpcd::StreamingMethod m_force = force; } - protected: - std::shared_ptr m_geom; //!< Streaming geometry - bool m_validate_geom; //!< If true, run a validation check on the geometry - std::shared_ptr m_force; //!< Solvent force - - //! Validate the system with the streaming geometry - void validate(); - //! 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 }; /*! @@ -119,12 +111,6 @@ void BounceBackStreamingMethod::stream(uint64_t timestep) 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(), @@ -173,40 +159,12 @@ void BounceBackStreamingMethod::stream(uint64_t timestep) m_mpcd_pdata->invalidateCellCache(); } -template void BounceBackStreamingMethod::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() << "BounceBackStreamingMethod: 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 BounceBackStreamingMethod::validateParticles() +bool BounceBackStreamingMethod::checkParticles() { ArrayHandle h_pos(m_mpcd_pdata->getPositions(), access_location::host, @@ -221,10 +179,6 @@ bool BounceBackStreamingMethod::validateParticles() 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; } } @@ -250,12 +204,14 @@ template void export_BounceBackStreamingMethod(pybi unsigned int, unsigned int, int, - std::shared_ptr, + std::shared_ptr, std::shared_ptr>()) .def_property_readonly("geometry", &mpcd::BounceBackStreamingMethod::getGeometry) .def_property_readonly("solvent_force", - &mpcd::BounceBackStreamingMethod::getForce); + &mpcd::BounceBackStreamingMethod::getForce) + .def("check_solvent_particles", + &BounceBackStreamingMethod::checkParticles); } } // end namespace detail } // end namespace mpcd diff --git a/hoomd/mpcd/BounceBackStreamingMethodGPU.h b/hoomd/mpcd/BounceBackStreamingMethodGPU.h index deab017ce4..2b04f507f1 100644 --- a/hoomd/mpcd/BounceBackStreamingMethodGPU.h +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.h @@ -44,7 +44,7 @@ class PYBIND11_EXPORT BounceBackStreamingMethodGPU unsigned int cur_timestep, unsigned int period, int phase, - std::shared_ptr geom, + std::shared_ptr geom, std::shared_ptr force) : mpcd::BounceBackStreamingMethod(sysdef, cur_timestep, @@ -80,14 +80,6 @@ void BounceBackStreamingMethodGPU::stream(uint64_t timestep) throw std::runtime_error("Cell list has not been set"); } - // 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) - { - this->validate(); - this->m_validate_geom = false; - } - ArrayHandle d_pos(this->m_mpcd_pdata->getPositions(), access_location::device, access_mode::readwrite); @@ -134,7 +126,7 @@ template void export_BounceBackStreamingMethodGPU(p unsigned int, unsigned int, int, - std::shared_ptr, + std::shared_ptr, std::shared_ptr>()); } } // end namespace detail diff --git a/hoomd/mpcd/BulkGeometry.h b/hoomd/mpcd/BulkGeometry.h index 06b3dfd418..9646363c45 100644 --- a/hoomd/mpcd/BulkGeometry.h +++ b/hoomd/mpcd/BulkGeometry.h @@ -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/ParallelPlateGeometry.h b/hoomd/mpcd/ParallelPlateGeometry.h index 1464451ff1..b22ff13be3 100644 --- a/hoomd/mpcd/ParallelPlateGeometry.h +++ b/hoomd/mpcd/ParallelPlateGeometry.h @@ -32,16 +32,16 @@ namespace mpcd * * 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. @@ -79,8 +79,8 @@ class __attribute__((visibility("default"))) ParallelPlateGeometry { /* * 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 @@ -88,10 +88,10 @@ class __attribute__((visibility("default"))) ParallelPlateGeometry * 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; @@ -99,27 +99,27 @@ class __attribute__((visibility("default"))) ParallelPlateGeometry /* * 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_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; } @@ -131,23 +131,7 @@ class __attribute__((visibility("default"))) ParallelPlateGeometry */ HOSTDEVICE bool isOutside(const Scalar3& pos) const { - return (pos.z > m_H || pos.z < -m_H); - } - - //! Validate that the simulation box is large enough for the geometry - /*! - * \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. - */ - HOSTDEVICE bool validateBox(const BoxDim& box, Scalar cell_size) 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 (pos.y > m_H || pos.y < -m_H); } //! Get channel half width diff --git a/hoomd/mpcd/ParallelPlateGeometryFiller.cc b/hoomd/mpcd/ParallelPlateGeometryFiller.cc index 7673321c5a..cc11e60126 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFiller.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.cc @@ -30,15 +30,8 @@ mpcd::ParallelPlateGeometryFiller::~ParallelPlateGeometryFiller() 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(); @@ -47,8 +40,8 @@ void mpcd::ParallelPlateGeometryFiller::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; /* @@ -57,17 +50,17 @@ void mpcd::ParallelPlateGeometryFiller::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 @@ -108,13 +101,13 @@ void mpcd::ParallelPlateGeometryFiller::drawParticles(uint64_t timestep) 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; diff --git a/hoomd/mpcd/ParallelPlateGeometryFiller.h b/hoomd/mpcd/ParallelPlateGeometryFiller.h index 447b980936..b295c590fb 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFiller.h +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.h @@ -50,8 +50,8 @@ class PYBIND11_EXPORT ParallelPlateGeometryFiller : public mpcd::VirtualParticle protected: std::shared_ptr m_geom; - Scalar m_z_min; //!< Min z coordinate for filling - Scalar m_z_max; //!< Max z coordinate for filling + 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 diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc index 91e609e65e..884f4a3d12 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc @@ -49,8 +49,8 @@ void mpcd::ParallelPlateGeometryFillerGPU::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, diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu index 066d595843..1c1153a8d7 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu @@ -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 @@ -46,8 +46,8 @@ __global__ void slit_draw_particles(Scalar4* d_pos, Scalar4* d_vel, unsigned int* d_tag, const mpcd::ParallelPlateGeometry geom, - const Scalar z_min, - const Scalar z_max, + 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 @@ -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 @@ -130,8 +130,8 @@ cudaError_t slit_draw_particles(Scalar4* d_pos, Scalar4* d_vel, unsigned int* d_tag, const mpcd::ParallelPlateGeometry& geom, - const Scalar z_min, - const Scalar z_max, + 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/ParallelPlateGeometryFillerGPU.cuh b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cuh index 65ee74ecfe..eddece1865 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cuh +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cuh @@ -26,8 +26,8 @@ cudaError_t slit_draw_particles(Scalar4* d_pos, Scalar4* d_vel, unsigned int* d_tag, const mpcd::ParallelPlateGeometry& geom, - const Scalar z_min, - const Scalar z_max, + const Scalar y_min, + const Scalar y_max, const BoxDim& box, const Scalar mass, const unsigned int type, diff --git a/hoomd/mpcd/PlanarPoreGeometry.h b/hoomd/mpcd/PlanarPoreGeometry.h index d3331a73c2..886602cb10 100644 --- a/hoomd/mpcd/PlanarPoreGeometry.h +++ b/hoomd/mpcd/PlanarPoreGeometry.h @@ -27,13 +27,13 @@ namespace mpcd //! 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). @@ -75,7 +75,7 @@ class __attribute__((visibility("default"))) PlanarPoreGeometry * 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"))) PlanarPoreGeometry 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"))) PlanarPoreGeometry // 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"))) PlanarPoreGeometry 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 0 && dts.y < dt); @@ -133,7 +133,7 @@ class __attribute__((visibility("default"))) PlanarPoreGeometry else if (!hits.x && hits.y) { dt = dts.y; - n.z = -sign.y; + n.y = -sign.y; } else { @@ -165,24 +165,7 @@ class __attribute__((visibility("default"))) PlanarPoreGeometry */ HOSTDEVICE bool isOutside(const Scalar3& pos) const { - return ((pos.x > -m_L && pos.x < m_L) && (pos.z > m_H || pos.z < -m_H)); - } - - //! Validate that the simulation box is large enough for the geometry - /*! - * \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. - */ - HOSTDEVICE bool validateBox(const BoxDim& box, Scalar cell_size) 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 ((pos.x > -m_L && pos.x < m_L) && (pos.y > m_H || pos.y < -m_H)); } //! Get pore half width diff --git a/hoomd/mpcd/PlanarPoreGeometryFiller.cc b/hoomd/mpcd/PlanarPoreGeometryFiller.cc index 9fddc8a5c0..05d4de5210 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFiller.cc +++ b/hoomd/mpcd/PlanarPoreGeometryFiller.cc @@ -59,13 +59,6 @@ void mpcd::PlanarPoreGeometryFiller::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(); @@ -73,7 +66,7 @@ void mpcd::PlanarPoreGeometryFiller::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. @@ -82,25 +75,25 @@ void mpcd::PlanarPoreGeometryFiller::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); @@ -111,16 +104,16 @@ void mpcd::PlanarPoreGeometryFiller::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 @@ -190,8 +183,8 @@ void mpcd::PlanarPoreGeometryFiller::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; diff --git a/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cu b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cu index 99c8548cc3..4a8682a61b 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cu +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cu @@ -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; } } diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py index 3a207f6a62..412748f480 100644 --- a/hoomd/mpcd/geometry.py +++ b/hoomd/mpcd/geometry.py @@ -53,8 +53,8 @@ class ParallelPlates(Geometry): Otherwise, they have the slip boundary condition. `ParallelPlates` confines the MPCD particles between two infinite parallel - plates centered around the origin. The plates are placed at :math:`z=-H` - and :math:`z=+H`, so the total channel width is :math:`2H`. The plates may + plates centered around the origin. The plates are placed at :math:`y=-H` + and :math:`y=+H`, so the total channel width is :math:`2H`. The plates 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. @@ -92,7 +92,7 @@ class PlanarPore(Geometry): 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 *y*. Outside the pore, the simulation box + 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. diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py index f8dab18895..33e9938996 100644 --- a/hoomd/mpcd/methods.py +++ b/hoomd/mpcd/methods.py @@ -74,6 +74,18 @@ def __init__(self, filter, 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`. + + """ + self._cpp_obj.check_particles() + def _attach_hook(self): sim = self._simulation diff --git a/hoomd/mpcd/pytest/test_methods.py b/hoomd/mpcd/pytest/test_methods.py index 5825799a31..f540fd7bdf 100644 --- a/hoomd/mpcd/pytest/test_methods.py +++ b/hoomd/mpcd/pytest/test_methods.py @@ -14,8 +14,8 @@ def snap(): snap_.configuration.box = [10, 10, 10, 0, 0, 0] snap_.particles.N = 2 snap_.particles.types = ["A"] - snap_.particles.position[:] = [[4.95, -4.95, 3.85], [0.0, 0.0, -3.8]] - snap_.particles.velocity[:] = [[1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]] + 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_ @@ -31,6 +31,14 @@ def integrator(): 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) @@ -42,9 +50,9 @@ def test_step_noslip(self, simulation_factory, snap, integrator): if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( snap.particles.position, - [[-4.95, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + [[-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]]) + 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) @@ -52,10 +60,10 @@ def test_step_noslip(self, simulation_factory, snap, integrator): if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( snap.particles.position, - [[-4.95, 4.95, 3.95], [-0.2, -0.2, -4.0]]) + [[-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]]) + [[-1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]]) # take another step, reflecting the second particle sim.run(1) @@ -63,9 +71,9 @@ def test_step_noslip(self, simulation_factory, snap, integrator): if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( snap.particles.position, - [[4.95, -4.95, 3.85], [-0.1, -0.1, -3.9]]) + [[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]]) + 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.""" @@ -80,9 +88,9 @@ def test_step_slip(self, simulation_factory, snap, integrator): if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( snap.particles.position, - [[-4.95, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + [[-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]]) + 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) @@ -90,7 +98,7 @@ def test_step_slip(self, simulation_factory, snap, integrator): if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( snap.particles.position, - [[-4.85, 4.85, 3.95], [-0.2, -0.2, -4.0]]) + [[-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]]) @@ -101,9 +109,9 @@ def test_step_slip(self, simulation_factory, snap, integrator): if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( snap.particles.position, - [[-4.75, 4.75, 3.85], [-0.3, -0.3, -3.9]]) + [[-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]]) + 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 @@ -120,13 +128,13 @@ def test_step_moving_wall(self, simulation_factory, snap, integrator): if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( snap.particles.position, - [[-4.75, -4.95, 3.85], [-0.4, -0.1, -3.9]]) + [[-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]]) + 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, -2, 4) + force.constant_force["A"] = (2, 4, -2) integrator.forces.append(force) if snap.communicator.rank == 0: @@ -139,26 +147,16 @@ def test_accel(self, simulation_factory, snap, integrator): if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( snap.particles.position, - [[0.11, -0.11, 0.12], [-0.095, -0.105, -0.09]]) + [[0.11, 0.12, -0.11], [-0.095, -0.09, -0.105]]) np.testing.assert_array_almost_equal( - snap.particles.velocity, [[1.2, -1.2, 1.4], [-0.9, -1.1, -0.8]]) - - def test_validate_box(self, simulation_factory, snap, integrator): - """Test box validation raises an error on run.""" - integrator.methods[0].geometry.H = 10 - - sim = simulation_factory(snap) - sim.operations.integrator = integrator - - with pytest.raises(RuntimeError): - sim.run(1) + snap.particles.velocity, [[1.2, 1.4, -1.2], [-0.9, -0.8, -1.1]]) - def test_test_of_bounds(self, simulation_factory, snap, integrator): + def test_test_out_of_bounds(self, simulation_factory, snap, integrator): """Test box validation raises an error on run.""" integrator.methods[0].geometry.H = 3.8 sim = simulation_factory(snap) sim.operations.integrator = integrator - with pytest.raises(RuntimeError): - sim.run(1) + sim.run(0) + assert not integrator.methods[0].check_particles() diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index 72b1a3ed32..5f510ca0a9 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -183,8 +183,8 @@ 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, -4.95, 3.85], [0.0, 0.0, -3.8]] - snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]] + 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(H=4)) @@ -196,34 +196,34 @@ def test_step_noslip(self, simulation_factory, snap): snap = sim.state.get_snapshot() if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( - snap.mpcd.position, [[-4.95, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + 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]]) + 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, 4.95, 3.95], [-0.2, -0.2, -4.0]]) + 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]]) + 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, -4.95, 3.85], [-0.1, -0.1, -3.9]]) + 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]]) + 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, -4.95, 3.85], [0.0, 0.0, -3.8]] - snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]] + 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, @@ -236,16 +236,16 @@ def test_step_slip(self, simulation_factory, snap): snap = sim.state.get_snapshot() if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( - snap.mpcd.position, [[-4.95, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + 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]]) + 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, 4.85, 3.95], [-0.2, -0.2, -4.0]]) + 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]]) @@ -254,9 +254,9 @@ def test_step_slip(self, simulation_factory, snap): snap = sim.state.get_snapshot() if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( - snap.mpcd.position, [[-4.75, 4.75, 3.85], [-0.3, -0.3, -3.9]]) + 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]]) + 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. @@ -270,8 +270,8 @@ def test_step_moving_wall(self, simulation_factory, snap): """ if snap.communicator.rank == 0: snap.mpcd.N = 2 - snap.mpcd.position[:] = [[4.95, -4.95, 3.85], [0.0, 0.0, -3.8]] - snap.mpcd.velocity[:] = [[1.0, -1.0, 1.0], [-2.0, -1.0, -1.0]] + 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, @@ -285,33 +285,21 @@ def test_step_moving_wall(self, simulation_factory, snap): snap = sim.state.get_snapshot() if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( - snap.mpcd.position, [[-4.75, -4.95, 3.85], [-0.4, -0.1, -3.9]]) + 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]]) + snap.mpcd.velocity, [[1.0, -1.0, 1.0], [0.0, 1.0, 1.0]]) - def test_validate_box(self, simulation_factory, snap): - """Test box validation raises an error on run.""" - sim = simulation_factory(snap) - sm = hoomd.mpcd.stream.BounceBack( - period=1, geometry=hoomd.mpcd.geometry.ParallelPlates(H=10)) - ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) - sim.operations.integrator = ig - - with pytest.raises(RuntimeError): - sim.run(1) - - def test_test_of_bounds(self, simulation_factory, snap): - """Test box validation raises an error on run.""" + def test_test_out_of_bounds(self, simulation_factory, snap): if snap.communicator.rank == 0: - snap.mpcd.position[0] = [4.95, -4.95, 3.85] + snap.mpcd.position[0] = [0, 3.85, 0] sim = simulation_factory(snap) sm = hoomd.mpcd.stream.BounceBack( period=1, geometry=hoomd.mpcd.geometry.ParallelPlates(H=3.8)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig - with pytest.raises(RuntimeError): - sim.run(1) + sim.run(0) + assert not sm.check_solvent_particles() class TestPlanarPore: @@ -320,24 +308,24 @@ def _make_particles(self, snap): if snap.communicator.rank == 0: snap.mpcd.N = 8 snap.mpcd.position[:] = [ - [-3.05, -4, -4.11], - [3.05, 4, 4.11], - [-3.05, -2, 4.11], - [3.05, 2, -4.11], - [0, 0, 3.95], - [0, 0, -3.95], - [3.03, 0, -3.98], - [3.02, 0, -3.97], + [-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, 0.0, -1.0], - [-1.0, 0.0, 1.0], - [0.0, 0.0, 1.0], - [0.0, 0.0, -1.0], - [-1.0, 0.0, -1.0], - [-1.0, 0.0, -1.0], + [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 @@ -355,60 +343,60 @@ def test_step_noslip(self, simulation_factory, snap): snap = sim.state.get_snapshot() if snap.communicator.rank == 0: np.testing.assert_array_almost_equal(snap.mpcd.position[0], - [-3.05, -4, -4.11]) + [-3.05, -4.11, -4]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[0], - [-1.0, 1.0, -1.0]) + [-1.0, -1.0, 1.0]) np.testing.assert_array_almost_equal(snap.mpcd.position[1], - [3.05, 4, 4.11]) + [3.05, 4.11, 4]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[1], - [1.0, -1.0, 1.0]) + [1.0, 1.0, -1.0]) np.testing.assert_array_almost_equal(snap.mpcd.position[2], - [-3.05, -2, 4.11]) + [-3.05, 4.11, -2]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[2], - [-1.0, 0.0, 1.0]) + [-1.0, 1.0, 0.0]) np.testing.assert_array_almost_equal(snap.mpcd.position[3], - [3.05, 2, -4.11]) + [3.05, -4.11, 2]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[3], - [1.0, 0.0, -1.0]) + [1.0, -1.0, 0.0]) np.testing.assert_array_almost_equal(snap.mpcd.position[4], - [0, 0, 3.95]) + [0, 3.95, 0]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[4], - [0, 0, -1.0]) + [0, -1.0, 0]) np.testing.assert_array_almost_equal(snap.mpcd.position[5], - [0, 0, -3.95]) + [0, -3.95, 0]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[5], - [0, 0, 1.0]) - # hits z = -4 after 0.02, then reverses. x is 3.01, so reverses to 3.09 + [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, 0, -3.92]) + [3.09, -3.92, 0]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[6], - [1, 0, 1]) - # hits x = 3 after 0.02, then reverses. z is -3.99, so reverses to -3.91 + [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, 0, -3.91]) + [3.08, -3.91, 0]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[7], - [1, 0, 1]) + [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.9, -4.21]) + [-3.15, -4.21, -3.9]) np.testing.assert_array_almost_equal(snap.mpcd.position[1], - [3.15, 3.9, 4.21]) + [3.15, 4.21, 3.9]) np.testing.assert_array_almost_equal(snap.mpcd.position[2], - [-3.15, -2, 4.21]) + [-3.15, 4.21, -2]) np.testing.assert_array_almost_equal(snap.mpcd.position[3], - [3.15, 2, -4.21]) + [3.15, -4.21, 2]) np.testing.assert_array_almost_equal(snap.mpcd.position[4], - [0, 0, 3.85]) + [0, 3.85, 0]) np.testing.assert_array_almost_equal(snap.mpcd.position[5], - [0, 0, -3.85]) + [0, -3.85, 0]) np.testing.assert_array_almost_equal(snap.mpcd.position[6], - [3.19, 0, -3.82]) + [3.19, -3.82, 0]) np.testing.assert_array_almost_equal(snap.mpcd.position[7], - [3.18, 0, -3.81]) + [3.18, -3.81, 0]) def test_step_slip(self, simulation_factory, snap): snap = self._make_particles(snap) @@ -424,78 +412,62 @@ def test_step_slip(self, simulation_factory, snap): snap = sim.state.get_snapshot() if snap.communicator.rank == 0: np.testing.assert_array_almost_equal(snap.mpcd.position[0], - [-3.05, -4.1, -4.01]) + [-3.05, -4.01, -4.1]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[0], - [-1.0, -1.0, 1.0]) + [-1.0, 1.0, -1.0]) np.testing.assert_array_almost_equal(snap.mpcd.position[1], - [3.05, 4.1, 4.01]) + [3.05, 4.01, 4.1]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[1], - [1.0, 1.0, -1.0]) + [1.0, -1.0, 1.0]) np.testing.assert_array_almost_equal(snap.mpcd.position[2], - [-3.05, -2, 4.01]) + [-3.05, 4.01, -2]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[2], - [-1.0, 0.0, -1.0]) + [-1.0, -1.0, 0.0]) np.testing.assert_array_almost_equal(snap.mpcd.position[3], - [3.05, 2, -4.01]) + [3.05, -4.01, 2]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[3], - [1.0, 0.0, 1.0]) + [1.0, 1.0, 0.0]) np.testing.assert_array_almost_equal(snap.mpcd.position[4], - [0, 0, 3.95]) + [0, 3.95, 0]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[4], - [0, 0, -1.0]) + [0, -1.0, 0]) np.testing.assert_array_almost_equal(snap.mpcd.position[5], - [0, 0, -3.95]) + [0, -3.95, 0]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[5], - [0, 0, 1.0]) - # hits z = -4 after 0.02, then reverses. x is not touched because slip + [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, 0, -3.92]) + [2.93, -3.92, 0]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[6], - [-1, 0, 1]) - # hits x = 3 after 0.02, then reverses. z is not touched because slip + [-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, 0, -4.07]) + [3.08, -4.07, 0]) np.testing.assert_array_almost_equal(snap.mpcd.velocity[7], - [1, 0, -1]) + [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.2, -3.91]) + [-3.15, -3.91, -4.2]) np.testing.assert_array_almost_equal(snap.mpcd.position[1], - [3.15, 4.2, 3.91]) + [3.15, 3.91, 4.2]) np.testing.assert_array_almost_equal(snap.mpcd.position[2], - [-3.15, -2, 3.91]) + [-3.15, 3.91, -2]) np.testing.assert_array_almost_equal(snap.mpcd.position[3], - [3.15, 2, -3.91]) + [3.15, -3.91, 2]) np.testing.assert_array_almost_equal(snap.mpcd.position[4], - [0, 0, 3.85]) + [0, 3.85, 0]) np.testing.assert_array_almost_equal(snap.mpcd.position[5], - [0, 0, -3.85]) + [0, -3.85, 0]) np.testing.assert_array_almost_equal(snap.mpcd.position[6], - [2.83, 0, -3.82]) + [2.83, -3.82, 0]) np.testing.assert_array_almost_equal(snap.mpcd.position[7], - [3.18, 0, -4.17]) + [3.18, -4.17, 0]) - def test_validate_box(self, simulation_factory, snap): - """Test box validation raises an error on run.""" - 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(H=10, L=2)) - with pytest.raises(RuntimeError): - sim.run(1) - - ig.streaming_method = hoomd.mpcd.stream.BounceBack( - period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=10)) - with pytest.raises(RuntimeError): - sim.run(1) - - def test_test_of_bounds(self, simulation_factory, snap): + def test_test_out_of_bounds(self, simulation_factory, snap): """Test box validation raises an error on run.""" snap = self._make_particles(snap) sim = simulation_factory(snap) @@ -504,10 +476,9 @@ def test_test_of_bounds(self, simulation_factory, snap): ig.streaming_method = hoomd.mpcd.stream.BounceBack( period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=3.8, L=3)) - with pytest.raises(RuntimeError): - sim.run(1) + sim.run(0) + assert not ig.streaming_method.check_solvent_particles() ig.streaming_method = hoomd.mpcd.stream.BounceBack( period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=3.5)) - with pytest.raises(RuntimeError): - sim.run(1) + assert not ig.streaming_method.check_solvent_particles() diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index b8a721056f..08d91e4141 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -172,6 +172,18 @@ def __init__(self, period, geometry, solvent_force=None): param_dict["geometry"] = geometry self._param_dict.update(param_dict) + def check_solvent_particles(self): + """Check if solvent particles are inside `geometry`. + + This method can only be called after this object is attached to a + simulation. + + Returns: + True if all solvent particles are inside `geometry`. + + """ + self._cpp_obj.check_solvent_particles() + def _attach_hook(self): sim = self._simulation diff --git a/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc index 39549cf30a..38e91eae4b 100644 --- a/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc +++ b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc @@ -68,10 +68,10 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec // type should be set UP_ASSERT_EQUAL(__scalar_as_int(h_pos.data[i].w), 1); - 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.0)) ++N_lo; - else if (z >= Scalar(5.0)) + else if (y >= Scalar(5.0)) ++N_hi; } UP_ASSERT_EQUAL(N_lo, 2 * (2 * 20 * 20)); @@ -97,10 +97,10 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec // tag should equal index on one rank with one filler UP_ASSERT_EQUAL(h_tag.data[i], 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.0)) ++N_lo; - else if (z >= Scalar(5.0)) + else if (y >= Scalar(5.0)) ++N_hi; } UP_ASSERT_EQUAL(N_lo, 2 * 2 * (2 * 20 * 20)); @@ -121,10 +121,10 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec unsigned int N_lo(0), N_hi(0); for (unsigned int i = pdata->getN(); 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.0)) ++N_lo; - else if (z >= Scalar(5.0)) + else if (y >= Scalar(5.0)) ++N_hi; } UP_ASSERT_EQUAL(N_lo, 2 * (20 * 20 / 2)); @@ -151,16 +151,16 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec for (unsigned int i = pdata->getN(); 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(-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)) { v_hi += vel; T_avg += dot(vel - make_scalar3(1.0, 0, 0), vel - make_scalar3(1.0, 0, 0)); diff --git a/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc b/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc index ba13aa4f0e..a9f097e2c9 100644 --- a/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc +++ b/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc @@ -75,9 +75,9 @@ void planar_pore_fill_basic_test(std::shared_ptr exec_co const Scalar4 r = h_pos.data[i]; if (r.x >= 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; } } @@ -109,9 +109,9 @@ void planar_pore_fill_basic_test(std::shared_ptr exec_co const Scalar4 r = h_pos.data[i]; if (r.x >= 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; } } @@ -138,9 +138,9 @@ void planar_pore_fill_basic_test(std::shared_ptr exec_co const Scalar4 r = h_pos.data[i]; if (r.x >= 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; } } From 0c2a13e46cf800b85464a1b3deb1d40dc4f0d473 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 1 Feb 2024 14:21:37 -0600 Subject: [PATCH 34/69] Remove unused validation directory --- hoomd/mpcd/validation/CMakeLists.txt | 75 ---------------------------- 1 file changed, 75 deletions(-) delete mode 100644 hoomd/mpcd/validation/CMakeLists.txt 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) From a699046d6740b882d9df120ebd275e8d1428c860 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 1 Feb 2024 14:27:47 -0600 Subject: [PATCH 35/69] Rotate forces --- hoomd/mpcd/BlockForce.h | 14 +++++++------- hoomd/mpcd/SineForce.h | 10 +++++----- hoomd/mpcd/force.py | 16 ++++++++-------- hoomd/mpcd/test/force_test.cc | 14 +++++++------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/hoomd/mpcd/BlockForce.h b/hoomd/mpcd/BlockForce.h index 7051e9906e..bc32f5186c 100644 --- a/hoomd/mpcd/BlockForce.h +++ b/hoomd/mpcd/BlockForce.h @@ -28,11 +28,11 @@ namespace mpcd //! Constant, opposite force applied to particles in a block /*! - * Imposes a constant force in x as a function of position in z: + * Imposes a constant force in x as a function of position in y: * * \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{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} * @@ -40,8 +40,8 @@ namespace mpcd * 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. + * 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 { @@ -69,8 +69,8 @@ class __attribute__((visibility("default"))) BlockForce 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.z >= m_H_minus_w && r.z < m_H_plus_w) - - (r.z >= -m_H_plus_w && r.z < -m_H_minus_w)); + 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); } diff --git a/hoomd/mpcd/SineForce.h b/hoomd/mpcd/SineForce.h index f995e85350..6f21ea7e98 100644 --- a/hoomd/mpcd/SineForce.h +++ b/hoomd/mpcd/SineForce.h @@ -28,11 +28,11 @@ namespace mpcd //! Shearing sine force /*! - * Imposes a sinusoidally varying force in x as a function of position in z. + * 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_z) \mathbf{e}_x + * \mathbf{F}(\mathbf{r}) = F \sin (k r_y) \mathbf{e}_x * \f] */ class __attribute__((visibility("default"))) SineForce @@ -53,13 +53,13 @@ class __attribute__((visibility("default"))) SineForce * \param r Particle position. * \returns Force on the particle. * - * Specifies the force to act in x as a function of z. Fast math + * 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.z), 0, 0); + return make_scalar3(m_F * fast::sin(m_k * r.y), 0, 0); } //! Get the sine amplitude @@ -96,7 +96,7 @@ class __attribute__((visibility("default"))) SineForce private: Scalar m_F; //!< Force constant - Scalar m_k; //!< Wavenumber for force in z + Scalar m_k; //!< Wavenumber for force in y }; #ifndef __HIPCC__ diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index b4b8f8bbfa..682460e21d 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -41,7 +41,7 @@ class BlockForce(SolventForce): half_width (float): Half the width of each block. The ``force`` magnitude *F* is applied in the *x* direction on the particles - in blocks defined along the *z* direction by the ``half_separation`` *H* and + in blocks defined along the *y* direction by the ``half_separation`` *H* and the ``half_width`` *w*. The force in *x* is :math:`+F` in the upper block, :math:`-F` in the lower block, and zero otherwise. @@ -50,15 +50,15 @@ class BlockForce(SolventForce): \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 `BlockForce` 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*. + viscosity by setting :math:`H = L_y/4` and :math:`w = L_y/4`, 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 @@ -128,14 +128,14 @@ class SineForce(SolventForce): wavenumber (float): Wavenumber for the sinusoid. `SineForce` applies a force with amplitude *F* in *x* that is sinusoidally - varying in *z* with wavenumber *k*: + varying in *y* with wavenumber *k*: .. 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 + with the simulation box. For example, :math:`k = 2\pi/L_y` will generate one period of the sine. Attributes: diff --git a/hoomd/mpcd/test/force_test.cc b/hoomd/mpcd/test/force_test.cc index 4a554405ba..c185956b17 100644 --- a/hoomd/mpcd/test/force_test.cc +++ b/hoomd/mpcd/test/force_test.cc @@ -34,12 +34,12 @@ void test_force(std::shared_ptr force, UP_TEST(block_force) { auto force = std::make_shared(2, 3, 0.2); - std::vector ref_pos = {make_scalar3(0, 0, 2.7), - make_scalar3(1, 2, 2.9), - make_scalar3(2, 1, 3.3), - make_scalar3(-1, 0, -2), - make_scalar3(4, 5, -3), - make_scalar3(4, 5, -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), @@ -71,7 +71,7 @@ UP_TEST(no_force) UP_TEST(sine_force) { auto force = std::make_shared(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_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); } From d2f5fb07e870f303f3249f09a175f8f647c184f6 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 1 Feb 2024 15:21:24 -0600 Subject: [PATCH 36/69] Fix issues with binding filler vector --- hoomd/mpcd/Integrator.cc | 7 +++++++ hoomd/mpcd/pytest/test_integrator.py | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/hoomd/mpcd/Integrator.cc b/hoomd/mpcd/Integrator.cc index ccabc4166c..5ebff83554 100644 --- a/hoomd/mpcd/Integrator.cc +++ b/hoomd/mpcd/Integrator.cc @@ -15,6 +15,9 @@ #endif #endif +#include +PYBIND11_MAKE_OPAQUE(std::vector>); + namespace hoomd { /*! @@ -165,6 +168,10 @@ void mpcd::Integrator::syncCellList() */ void mpcd::detail::export_Integrator(pybind11::module& m) { + pybind11::bind_vector>>( + m, + "VirtualParticleFillerList"); + pybind11::class_>(m, "Integrator") diff --git a/hoomd/mpcd/pytest/test_integrator.py b/hoomd/mpcd/pytest/test_integrator.py index bf442c77f8..040e6d79a1 100644 --- a/hoomd/mpcd/pytest/test_integrator.py +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -119,6 +119,11 @@ def test_virtual_particle_fillers(make_simulation): 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) From 43d5cd6ad4af2fd0438c7c729ef3a4baf07abccf Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 22 Feb 2024 20:44:46 -0600 Subject: [PATCH 37/69] Implement suggestions from code review --- hoomd/mpcd/fill.py | 14 +++++++------- hoomd/mpcd/methods.py | 10 +++++----- hoomd/mpcd/pytest/test_methods.py | 20 ++++++++++++++++++++ hoomd/mpcd/stream.py | 10 +++++----- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py index ee7e28f3dd..e0b9a9caea 100644 --- a/hoomd/mpcd/fill.py +++ b/hoomd/mpcd/fill.py @@ -69,6 +69,8 @@ class GeometryFiller(VirtualParticleFiller): """ + _cpp_class_map = {} + def __init__(self, type, density, kT, geometry): super().__init__(type, density, kT) @@ -84,7 +86,7 @@ def _attach_hook(self): # try to find class in map, otherwise default to internal MPCD module geom_type = type(self.geometry) try: - class_info = self._class_map[geom_type] + class_info = self._cpp_class_map[geom_type] except KeyError: class_info = (_mpcd, geom_type.__name__ + "GeometryFiller") class_info = list(class_info) @@ -107,12 +109,10 @@ def _detach_hook(self): self.geometry._detach() super()._detach_hook() - _class_map = {} - @classmethod - def _register_geometry(cls, geometry, module, class_name): - cls._class_map[geometry] = (module, class_name) + def _register_cpp_class(cls, geometry, module, cpp_class_name): + cls._cpp_class_map[geometry] = (module, cpp_class_name) -GeometryFiller._register_geometry(ParallelPlates, _mpcd, - "ParallelPlateGeometryFiller") +GeometryFiller._register_cpp_class(ParallelPlates, _mpcd, + "ParallelPlateGeometryFiller") diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py index f8dab18895..383cca155d 100644 --- a/hoomd/mpcd/methods.py +++ b/hoomd/mpcd/methods.py @@ -67,6 +67,8 @@ class BounceBack(Method): """ + _cpp_class_map = {} + def __init__(self, filter, geometry): super().__init__() @@ -82,7 +84,7 @@ def _attach_hook(self): # try to find class in map, otherwise default to internal MPCD module geom_type = type(self.geometry) try: - class_info = self._class_map[geom_type] + class_info = self._cpp_class_map[geom_type] except KeyError: class_info = (_mpcd, "BounceBackNVE" + geom_type.__name__) class_info = list(class_info) @@ -100,8 +102,6 @@ def _detach_hook(self): self.geometry._detach() super()._detach_hook() - _class_map = {} - @classmethod - def _register_geometry(cls, geometry, module, class_name): - cls._class_map[geometry] = (module, class_name) + def _register_cpp_class(cls, geometry, module, cpp_class_name): + cls._cpp_class_map[geometry] = (module, cpp_class_name) diff --git a/hoomd/mpcd/pytest/test_methods.py b/hoomd/mpcd/pytest/test_methods.py index 5825799a31..9761d25ca5 100644 --- a/hoomd/mpcd/pytest/test_methods.py +++ b/hoomd/mpcd/pytest/test_methods.py @@ -162,3 +162,23 @@ def test_test_of_bounds(self, simulation_factory, snap, integrator): with pytest.raises(RuntimeError): sim.run(1) + + 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(H=4)) + 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, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + 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/stream.py b/hoomd/mpcd/stream.py index 4e0b56023a..94e3fab1cd 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -122,6 +122,8 @@ class BounceBack(StreamingMethod): """ + _cpp_class_map = {} + def __init__(self, period, geometry): super().__init__(period) @@ -137,7 +139,7 @@ def _attach_hook(self): # try to find class in map, otherwise default to internal MPCD module geom_type = type(self.geometry) try: - class_info = self._class_map[geom_type] + class_info = self._cpp_class_map[geom_type] except KeyError: class_info = (_mpcd, "BounceBackStreamingMethod" + geom_type.__name__) @@ -161,8 +163,6 @@ def _detach_hook(self): self.geometry._detach() super()._detach_hook() - _class_map = {} - @classmethod - def _register_geometry(cls, geometry, module, class_name): - cls._class_map[geometry] = (module, class_name) + def _register_cpp_class(cls, geometry, module, cpp_class_name): + cls._cpp_class_map[geometry] = (module, cpp_class_name) From 2866f4da37d9a936c5b51f699f018ef461c8b213 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Sat, 2 Mar 2024 21:22:46 -0600 Subject: [PATCH 38/69] Add no force to attach test --- hoomd/mpcd/pytest/test_stream.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index 72b1a3ed32..45275f75e3 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -68,12 +68,13 @@ def test_pickling(self, simulation_factory, snap, cls, init_args): @pytest.mark.parametrize( "force", [ + None, hoomd.mpcd.force.BlockForce( force=2.0, half_separation=3.0, half_width=0.5), hoomd.mpcd.force.ConstantForce(force=(1, -2, 3)), hoomd.mpcd.force.SineForce(amplitude=2.0, wavenumber=1), ], - ids=["BlockForce", "ConstantForce", "SineForce"], + ids=["NoForce", "BlockForce", "ConstantForce", "SineForce"], ) def test_force_attach(self, simulation_factory, snap, cls, init_args, force): From 48ec8f35bcba05ca712c16581583849085959a16 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Sun, 3 Mar 2024 22:03:27 -0600 Subject: [PATCH 39/69] Remove out of date graphic --- hoomd/mpcd/geometry.py | 2 -- sphinx-doc/mpcd_slit_pore.png | Bin 133538 -> 0 bytes 2 files changed, 2 deletions(-) delete mode 100644 sphinx-doc/mpcd_slit_pore.png diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py index 412748f480..6365bf7852 100644 --- a/hoomd/mpcd/geometry.py +++ b/hoomd/mpcd/geometry.py @@ -96,8 +96,6 @@ class PlanarPore(Geometry): 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 - Attributes: H (float): Pore half-width. diff --git a/sphinx-doc/mpcd_slit_pore.png b/sphinx-doc/mpcd_slit_pore.png deleted file mode 100644 index 136576e8c408dbbfcc31939a5b14f087e97dae69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 133538 zcmeFYXHb*f7d=YvMS2GT0hQi+FCtY%dW}eLk=~JRp;+i$QIHY{y%Rz}kS<8*kbv|S zL~01R!QcPReP{0H`|X~YOeT4fXP&dqK6|ab_CBv4nLMDTV5h*t!=rwvuk!>CkE9(B zk6@OJ1osU;E8h_AjVM6t;Zri)h$MUd3ip{DsBayBhi9jE^TKD$(ALC#sTinh6=>$? z8W;lbcfs=k1OmPN0=+$+-41r~5Ag8wy)7jsB_< zhj$zAp^oO$(1N|CkU+Bs%ioV`7ds^TzyEy}F7zom0gsn_yKCQfUeNl9e184*oVWhr z{?gy&hWuqRfjb!*WL+divQ9ltz{5%Oz~Fmt!+LL#AN57!iUD_)l-n=a?7pBEbL(ck z-=ZjxqEMm{y*2>mwYKP{LgirxDWsT4gTLY zpoIeJ^kR|APS)SpT(dj%gxPYWM&Ruf<&Ahh&XmH3N=}I%jJ&> zJ;-AIYF=gWdwrd_o$5cfCzty1;|Hfw;Goa|{8!?CZpBBR!5H}ZKg+8B=*fh@e}AC) zP&4~K*Iy9{5ZnLv`u_(vctaM5PtQq@oB8nYaeIuvzm#?|&B;F#7}wU*M_UtHRPAEA zIyA#=57W#aKh_Km4*obTc=J@F+Tr0!@87?Vj#*MwM4_3-qvo4pqgA2Lj!cc0>*q?U zISq!5goUeuAe`R5%klDw=L&c4d(1oQ>1uh4^nF*bv7_qi(`Zd%ZVc5G083K-dD|Id z@pZu|>7j&ytbbyu47irn_78wa=eF2Ybzk)uL&+lrq`^HZN;#Z?&zr==k0wP@<>rpvGe+J*Vwk;#+pQnF4^{a9l`romwCnG2@N&GN^OVI zVIq+jDV+ysP_>|Q1GF0hc4qNj@vuonckrG$t);7Pky$m?Z|@M_ZD~@t_yH6tRTmxKOqmTy~ouf4h4?Ng1CIs_#X)qzvm> zmhivJDa}4#e%WGjR5VJ$GnSBFl)Q&oRuvMVDl5dFs!gzX-3>Cm^9vU+>V#|K;-v20 z4OwK)nu)v`0AZpFF{kK{=$`C_p13gS^&RF-AMVSxo%n2w-ICUt!_=BrM3~6SB9K!I zd7rM99Z{cN&ce>XSj)*i<&?Pu-zuSp58_P&qV3H7Fa~9=fYmwvqm`6Tj02k)Wz ziM$AxI(l9%OL*Q*;UVUlB(MzlW(%RN3FC~m+rR|qohD-!31CZ+kT?2Mxc#JAHMzt0 z(x0M$!k*|+(Pf6Eti6wJ<|BB*fY-xImfm(k}%c8N)!{OX<>lkJsI{r-EOYm|+;do{v0QU$Zwa zY=oe&Da0SMz!zU$spFObQc>T|D!R`5%3t25^0HZx^r4HGNy?j_8CW?N^D?eL$u29i zH{`Xl1f#kN7F=av^M}5UT0}Yz+>2b_Z+r=eXgz-#dSW(%<_RS>DgIV$d-Lo^se4ht zMFH>(jL`#R3r(5%WJn6hWctO&SMtDes*KY4av-rx z(a>wtin7`=WN>T8ire&L&o}?E_TY;KG(bPm&lVnj zUROMD^C1uXN<-1!CzN#<3lN{7Dl^c!d*w0o6Ci3V5zUifk(;$!x@PJ@8_=RUqr3tmnVCvkhrK zWMH9O>7>F$T-4TNiSYFrG0k=vF5RlG98#nAwgufDQKPS3gf)J+u9qKZyfEB3z|DrN z`|>RAqfkCjMr{lmL=tao4P5#t0qei?JfPa8#)e3zaBp?A>*N_|R^;PVS-dmf8CA5) zA0Ix+Gf97A`a2u?Mm4sE56BqU#*(6$U7Km1cbMCyTPFL|NN4GGwVuMp#tQ=xokV%e z4FBYl=Yg@5CJATVFS=qbg@IQJsXI5Y{Jtz`dI{_qkH*l^p|&X{!gH{m6fQ_)+O0hW z7G5j3?*o*1#2u>u3MlgxNmO_*M{(~47w{JyuvZdz70UX{ve?U8F7!y+s%0AePFNAY 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 _BrBh*^R({cFcKZtyNdM~Q~%u;h$$r!P5#M3^&m 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 From 9be5b65478673a731e67bd37152477d9f6c5f3d4 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Tue, 5 Mar 2024 07:55:25 -0600 Subject: [PATCH 40/69] Rotate coordinates of test added on merged branch --- hoomd/mpcd/pytest/test_methods.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hoomd/mpcd/pytest/test_methods.py b/hoomd/mpcd/pytest/test_methods.py index a7f17a1fd0..154928eebe 100644 --- a/hoomd/mpcd/pytest/test_methods.py +++ b/hoomd/mpcd/pytest/test_methods.py @@ -177,6 +177,6 @@ def test_md_integrator(self, simulation_factory, snap): if snap.communicator.rank == 0: np.testing.assert_array_almost_equal( snap.particles.position, - [[-4.95, 4.95, 3.95], [-0.1, -0.1, -3.9]]) + [[-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]]) + snap.particles.velocity, [[1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]]) From 287eb129cd9e8a1b2ec20199610d0d751f5c21a9 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Sun, 3 Mar 2024 21:34:59 -0600 Subject: [PATCH 41/69] Add sybil examples to MPCD documentation --- hoomd/mpcd/__init__.py | 41 ++++---- hoomd/mpcd/collide.py | 153 ++++++++++++++++++++++++++---- hoomd/mpcd/fill.py | 58 ++++++++++- hoomd/mpcd/force.py | 92 ++++++++++++++++-- hoomd/mpcd/geometry.py | 74 +++++++++++++-- hoomd/mpcd/integrate.py | 87 +++++++++++++++-- hoomd/mpcd/methods.py | 33 +++++-- hoomd/mpcd/pytest/test_methods.py | 8 +- hoomd/mpcd/pytest/test_stream.py | 13 ++- hoomd/mpcd/stream.py | 66 ++++++++++++- hoomd/mpcd/tune.py | 18 ++++ hoomd/util.py | 16 +++- 12 files changed, 569 insertions(+), 90 deletions(-) diff --git a/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index 86e8f01f5f..8828cd9cd9 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -4,38 +4,34 @@ """ Multiparticle collision dynamics. Simulating complex fluids and soft matter using conventional molecular dynamics -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, 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 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: +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 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: * :class:`~hoomd.mpcd.collide.StochasticRotationDynamics` * :class:`~hoomd.mpcd.collide.AndersenThermostat` 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 +dynamics methods can be applied to the solute. Coupling to the MPCD solvent introduces both hydrodynamic interactions and a heat bath that acts as a thermostat. @@ -63,11 +59,6 @@ """ -# 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 diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index e17ebdd549..1d31794e64 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -11,6 +11,11 @@ hydrodynamic interactions, and the choice of collision rule and solvent properties determine 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 @@ -38,11 +43,31 @@ class CellList(Compute): penalty from grid shifting is small, so it is recommended to enable it in all simulations. + .. rubric:: Example: + + Access default cell list from integrator. + + .. code-block:: python + + cell_list = simulation.operations.integrator.cell_list + Attributes: cell_size (float): Edge length of a collision cell. + .. rubric:: Example: + + .. code-block:: python + + cell_list.cell_size = 1.0 + shift (bool): When True, randomly shift underlying collision cells. + .. rubric:: Example: + + .. code-block:: python + + cell_list.shift = True + """ def __init__(self, cell_size, shift=True): @@ -74,6 +99,11 @@ class CollisionMethod(Operation): period (int): Number of integration steps between collisions. embedded_particles (hoomd.filter.filter_like): HOOMD particles to include in collision. + .. invisible-code-block: python + + collision_method = hoomd.mpcd.collide.CollisionMethod(period=1) + simulation.operations.integrator.collision_method = collision_method + Attributes: embedded_particles (hoomd.filter.filter_like): HOOMD particles to include in collision. @@ -92,6 +122,12 @@ class CollisionMethod(Operation): will not be correctly transferred to the body. Support for this is planned in future. + .. rubric:: Example: + + .. code-block:: python + + collision_method.embedded_particles = hoomd.filter.All() + period (int): Number of integration steps between collisions. A collision is executed each time the :attr:`~hoomd.Simulation.timestep` @@ -114,7 +150,7 @@ def __init__(self, period, embedded_particles=None): class AndersenThermostat(CollisionMethod): - r"""Andersen thermostat method. + r"""Andersen thermostat collision method. Args: period (int): Number of integration steps between collisions. @@ -126,12 +162,32 @@ class AndersenThermostat(CollisionMethod): 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. New particle velocities are then randomly - drawn from a Gaussian distribution 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`. + `_. Every + :attr:`~CollisionMethod.period` steps, the particles are binned into cells. + New particle velocities are then randomly drawn from a Gaussian distribution + 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`. + + .. rubric:: Examples: + + Solvent collision. + + .. code-block:: python + + at = hoomd.mpcd.collide.AndersenThermostat(period=1, kT=1.0) + simulation.operations.integrator.collision_method = at + + Collision including embedded particles. + + .. code-block:: python + + at = hoomd.mpcd.collide.AndersenThermostat( + period=20, + kT=1.0, + embedded_particles=hoomd.filter.All()) + simulation.operations.integrator.collision_method = at Attributes: kT (hoomd.variant.variant_like): Temperature of the solvent @@ -140,6 +196,18 @@ class AndersenThermostat(CollisionMethod): This temperature determines the distribution used to generate the random numbers. + .. rubric:: Examples: + + Constant temperature. + + .. code-block:: python + + at.kT = 1.0 + + Variable temperature. + + at.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) + """ def __init__(self, period, kT, embedded_particles=None): @@ -167,7 +235,7 @@ def _attach_hook(self): class StochasticRotationDynamics(CollisionMethod): - r"""Stochastic rotation dynamics method. + r"""Stochastic rotation dynamics collision method. Args: period (int): Number of integration steps between collisions. @@ -178,32 +246,79 @@ class StochasticRotationDynamics(CollisionMethod): embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to include in collision. - This class implements the classic stochastic rotation dynamics collision + This class implements the stochastic rotation dynamics collision rule for MPCD proposed by `Malevanets and Kapral - `_. Every ``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. + `_. 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 the `embedded_particles`. 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 + 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 the (correct) isothermal ensemble. The temperature is defined + 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: + + Standard solvent collision. + + .. code-block:: python + + srd = hoomd.mpcd.collide.StochasticRotationDynamics(period=1, angle=130) + simulation.operations.integrator.collision_method = srd + + Solvent collision 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. + + srd.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) + """ def __init__(self, period, angle, kT=None, embedded_particles=None): diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py index e0b9a9caea..d6b23499dc 100644 --- a/hoomd/mpcd/fill.py +++ b/hoomd/mpcd/fill.py @@ -7,8 +7,14 @@ 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 solvent -properties may differ. In practice, this means that the boundary conditions do -not appear to be properly enforced. +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 @@ -30,13 +36,45 @@ class VirtualParticleFiller(Operation): 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. + + filler.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) + """ def __init__(self, type, density, kT): @@ -52,7 +90,7 @@ def __init__(self, type, density, kT): class GeometryFiller(VirtualParticleFiller): - """Virtual-particle filler for known geometry. + """Virtual-particle filler for a bounce-back geometry. Args: type (str): Type of particles to fill. @@ -64,6 +102,20 @@ class GeometryFiller(VirtualParticleFiller): 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(H=3.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. diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index 682460e21d..213917066e 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -4,14 +4,19 @@ r""" MPCD solvent forces. MPCD can apply a body force to each MPCD particle as a function of position. -The external force should be compatible with the chosen :mod:`~hoomd.mpcd.geometry`. +The external force should be compatible with the chosen `~hoomd.mpcd.geometry.Geometry`. Global momentum conservation can be broken by adding a solvent 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 -coupled to the solvent through the collision step. Additionally, a thermostat +embedded in the solvent through the collision step. Additionally, a thermostat will likely be required to maintain temperature control in the driven system. +.. invisible-code-block: python + + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + simulation.operations.integrator = hoomd.mpcd.Integrator(dt=0.1) + """ from hoomd import _hoomd @@ -40,9 +45,9 @@ class BlockForce(SolventForce): blocks. half_width (float): Half the width of each block. - The ``force`` magnitude *F* is applied in the *x* direction on the particles - in blocks defined along the *y* direction by the ``half_separation`` *H* and - the ``half_width`` *w*. The force in *x* is :math:`+F` in the upper block, + The `force` magnitude *F* is applied in the *x* direction on the solvent particles + in blocks defined along the *y* direction by the `half_separation` *H* and + the `half_width` *w*. The force in *x* is :math:`+F` in the upper block, :math:`-F` in the lower block, and zero otherwise. .. math:: @@ -64,14 +69,45 @@ class BlockForce(SolventForce): 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, half_separation=Ly/4, half_width=Ly/4) + stream = hoomd.mpcd.stream.Bulk(period=1, solvent_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 + half_separation (float): Half the distance between the centers of the blocks. + .. rubric:: Example: + + .. code-block:: python + + Ly = simulation.state.box.Ly + force.half_separation = Ly / 4 + half_width (float): Half the width of each block. + .. rubric:: Example: + + .. code-block:: python + + Ly = simulation.state.box.Ly + force.half_width = Ly / 4 + """ def __init__(self, force, half_separation=None, half_width=None): @@ -96,15 +132,29 @@ class ConstantForce(SolventForce): Args: force (`tuple` [`float`, `float`, `float`]): Force vector per particle. - The same constant force is applied to all particles, independently of time - and their positions. This force is useful for simulating pressure-driven + The same constant force is applied to all solvent 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. + .. rubric:: Example: + + .. code-block:: python + + force = hoomd.mpcd.force.ConstantForce((1.0, 0, 0)) + stream = hoomd.mpcd.stream.Bulk(period=1, solvent_force=force) + simulation.operations.integrator.streaming_method = stream + Attributes: force (`tuple` [`float`, `float`, `float`]): Force vector per particle. + .. rubric:: Example: + + .. code-block:: python + + force.force = (1.0, 0.0, 0.0) + """ def __init__(self, force): @@ -128,7 +178,7 @@ class SineForce(SolventForce): wavenumber (float): Wavenumber for the sinusoid. `SineForce` applies a force with amplitude *F* in *x* that is sinusoidally - varying in *y* with wavenumber *k*: + varying in *y* with wavenumber *k* to all solvent particles: .. math:: @@ -138,11 +188,37 @@ class SineForce(SolventForce): with the simulation box. For example, :math:`k = 2\pi/L_y` will generate one period of the sine. + .. rubric:: Example: + + Sine force with one period. + + .. code-block:: python + + 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, solvent_force=force) + simulation.operations.integrator.streaming_method = stream + Attributes: amplitude (float): Amplitude of the sinusoid. + .. rubric:: Example: + + .. code-block:: python + + force.amplitude = 1.0 + wavenumber (float): Wavenumber for the sinusoid. + .. rubric:: Example: + + .. code-block:: python + + Ly = simulation.state.box.Ly + force.wavenumber = 2 * numpy.pi / Ly + """ def __init__(self, amplitude, wavenumber): diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py index 6365bf7852..60408a6034 100644 --- a/hoomd/mpcd/geometry.py +++ b/hoomd/mpcd/geometry.py @@ -6,13 +6,18 @@ A geometry defines solid boundaries that cannot be penetrated. These geometries are used for various operations in the MPCD algorithm including: -* :class:`~hoomd.mpcd.stream.BounceBack` streaming for the solvent -* Bounce back integration for MD particles -* Virtual particle filling +* 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 @@ -24,15 +29,27 @@ class Geometry(_HOOMDBaseObject): r"""Geometry. Args: - no_slip (bool): If True, surfaces have no-slip boundary condition. - Otherwise, they have the slip boundary condition. + 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 no-slip boundary condition. - Otherwise, they have the slip boundary condition. + no_slip (bool): If True, plates have a no-slip boundary condition. + Otherwise, they have a slip boundary condition. - `V` will have no effect if `no_slip` is False because the slip - surface cannot generate shear stress. + 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. + + .. rubric:: Example: + + .. code-block:: python + + geometry.no_slip = True """ @@ -52,18 +69,47 @@ class ParallelPlates(Geometry): no_slip (bool): If True, surfaces have no-slip boundary condition. Otherwise, they have the slip boundary condition. - `ParallelPlates` confines the MPCD particles between two infinite parallel + `ParallelPlates` confines particles between two infinite parallel plates centered around the origin. The plates are placed at :math:`y=-H` and :math:`y=+H`, so the total channel width is :math:`2H`. The plates 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. + .. rubric:: Examples: + + Stationary parallel plates with no-slip boundary condition. + + .. code-block:: python + + plates = hoomd.mpcd.geometry.ParallelPlates(H=3.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(H=3.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(H=3.0, V=1.0, no_slip=True) + stream = hoomd.mpcd.stream.BounceBack(period=1, geometry=plates) + simulation.operations.integrator.streaming_method = stream + Attributes: H (float): Channel half-width. V (float): Wall speed. + `V` will have no effect if `no_slip` is False because the slip + surface cannot generate shear stress. + """ def __init__(self, H, V=0.0, no_slip=True): @@ -96,6 +142,14 @@ class PlanarPore(Geometry): 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(H=3.0, L=2.0) + stream = hoomd.mpcd.stream.BounceBack(period=1, geometry=pore) + simulation.operations.integrator.streaming_method = stream + Attributes: H (float): Pore half-width. diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index a8ef0816de..c12f877137 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -57,12 +57,14 @@ class Integrator(_MDIntegrator): The MPCD `Integrator` enables the MPCD algorithm concurrently with standard MD methods. - 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:: + 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: |---->|---->|---->|---->|---->| @@ -73,6 +75,72 @@ class Integrator(_MDIntegrator): 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"]) + + MPCD integrator for pure solvent. + + .. 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, + solvent_sorter=hoomd.mpcd.tune.ParticleSorter(trigger=20)) + simulation.operations.integrator = integrator + + MPCD integrator with solutes. + + .. code-block:: python + + dt_md = 0.005 + dt_mpcd = 0.1 + md_steps_per_collision = numpy.round(dt_mpcd / dt_md).astype(int) + + 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, + solvent_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(H=3.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], + solvent_sorter=hoomd.mpcd.tune.ParticleSorter(trigger=20)) + simulation.operations.integrator = integrator Attributes: collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method @@ -84,9 +152,6 @@ class Integrator(_MDIntegrator): streaming_method (hoomd.mpcd.stream.StreamingMethod): Streaming method for the MPCD solvent. - virtual_particle_fillers (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): - Solvent virtual-particle filler(s). - """ def __init__( @@ -149,6 +214,10 @@ def cell_list(self): @property def virtual_particle_fillers(self): + """Sequence[hoomd.mpcd.fill.VirtualParticleFiller]: Solvent + virtual-particle fillers. + + """ return self._virtual_particle_fillers @virtual_particle_fillers.setter diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py index 4730f81e8f..f81039641d 100644 --- a/hoomd/mpcd/methods.py +++ b/hoomd/mpcd/methods.py @@ -3,10 +3,15 @@ r""" MPCD integration methods -Defines extra integration methods useful for solutes (MD particles) embedded in -an MPCD solvent. However, 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. +Extra integration methods for solutes (MD particles) embedded in an MPCD +solvent. 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) """ @@ -19,7 +24,7 @@ class BounceBack(Method): - r"""Velocity Verlet integration method with bounce-back rule for surfaces. + r"""Velocity Verlet integration method with bounce-back from surfaces. Args: filter (hoomd.filter.filter_like): Subset of particles on which to @@ -59,6 +64,16 @@ class BounceBack(Method): 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(H=3.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. @@ -85,8 +100,14 @@ def check_particles(self): Returns: True if all particles are inside `geometry`. + .. rubric:: Example: + + .. code-block:: python + + assert nve.check_particles() + """ - self._cpp_obj.check_particles() + return self._cpp_obj.check_particles() def _attach_hook(self): sim = self._simulation diff --git a/hoomd/mpcd/pytest/test_methods.py b/hoomd/mpcd/pytest/test_methods.py index 154928eebe..9f932de362 100644 --- a/hoomd/mpcd/pytest/test_methods.py +++ b/hoomd/mpcd/pytest/test_methods.py @@ -151,15 +151,17 @@ def test_accel(self, simulation_factory, snap, integrator): np.testing.assert_array_almost_equal( snap.particles.velocity, [[1.2, 1.4, -1.2], [-0.9, -0.8, -1.1]]) - def test_test_out_of_bounds(self, simulation_factory, snap, integrator): + @pytest.mark.parametrize("H,expected_result", [(4.0, True), (3.8, False)]) + def test_test_out_of_bounds(self, simulation_factory, snap, integrator, H, + expected_result): """Test box validation raises an error on run.""" - integrator.methods[0].geometry.H = 3.8 + integrator.methods[0].geometry.H = H sim = simulation_factory(snap) sim.operations.integrator = integrator sim.run(0) - assert not integrator.methods[0].check_particles() + 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.""" diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index c52e85331d..20a075cc07 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -290,17 +290,19 @@ def test_step_moving_wall(self, simulation_factory, snap): np.testing.assert_array_almost_equal( snap.mpcd.velocity, [[1.0, -1.0, 1.0], [0.0, 1.0, 1.0]]) - def test_test_out_of_bounds(self, simulation_factory, snap): + @pytest.mark.parametrize("H,expected_result", [(4.0, True), (3.8, False)]) + def test_test_out_of_bounds(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(H=3.8)) + period=1, geometry=hoomd.mpcd.geometry.ParallelPlates(H=H)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig sim.run(0) - assert not sm.check_solvent_particles() + assert sm.check_solvent_particles() is expected_result class TestPlanarPore: @@ -475,6 +477,11 @@ def test_test_out_of_bounds(self, 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(H=4, L=3)) + sim.run(0) + assert ig.streaming_method.check_solvent_particles() + ig.streaming_method = hoomd.mpcd.stream.BounceBack( period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=3.8, L=3)) sim.run(0) diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index fd445b9e8a..571150eee5 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -22,6 +22,11 @@ **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`. +.. invisible-code-block: python + + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + simulation.operations.integrator = hoomd.mpcd.Integrator(dt=0.1) + """ import hoomd @@ -40,6 +45,10 @@ class StreamingMethod(Operation): period (int): Number of integration steps covered by streaming step. solvent_force (SolventForce): Force on solvent. + .. invisible-code-block: python + + streaming_method = hoomd.mpcd.stream.StreamingMethod(period=1) + Attributes: period (int): Number of integration steps covered by streaming step. @@ -52,6 +61,12 @@ class StreamingMethod(Operation): used if an external force is applied, and more faithful numerical integration is needed. + .. rubric:: Example: + + .. code-block:: python + + streaming_method.period = 1 + solvent_force (SolventForce): Force on solvent. """ @@ -77,6 +92,24 @@ class Bulk(StreamingMethod): This geometry is appropriate for modeling bulk fluids, i.e., those that are not confined by any surfaces. + .. rubric:: Examples: + + Bulk streaming. + + .. code-block:: python + + stream = hoomd.mpcd.stream.Bulk(period=1) + simulation.operations.integrator.streaming_method = stream + + Bulk streaming with applied force. + + .. code-block:: python + + stream = hoomd.mpcd.stream.Bulk( + period=1, + solvent_force=hoomd.mpcd.force.ConstantForce((1, 0, 0))) + simulation.operations.integrator.streaming_method = stream + """ def _attach_hook(self): @@ -142,8 +175,8 @@ class BounceBack(StreamingMethod): to complex boundaries, defined by a `geometry`. This `StreamingMethod` reflects the MPCD solvent 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.) + 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 solvent particles, there are a few important caveats: @@ -160,6 +193,27 @@ class BounceBack(StreamingMethod): You must initialize your system carefully to ensure all particles are "inside" the geometry. An error will be raised otherwise. + .. rubric:: Examples: + + Shear flow between moving parallel plates. + + .. code-block:: python + + stream = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates(H=3.0, V=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(H=3.0, no_slip=True), + solvent_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. @@ -183,8 +237,14 @@ def check_solvent_particles(self): Returns: True if all solvent particles are inside `geometry`. + .. rubric:: Examples: + + .. code-block:: python + + assert stream.check_solvent_particles() + """ - self._cpp_obj.check_solvent_particles() + return self._cpp_obj.check_solvent_particles() def _attach_hook(self): sim = self._simulation diff --git a/hoomd/mpcd/tune.py b/hoomd/mpcd/tune.py index c6313d7989..4ab48baefb 100644 --- a/hoomd/mpcd/tune.py +++ b/hoomd/mpcd/tune.py @@ -6,6 +6,11 @@ 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 @@ -38,10 +43,23 @@ class ParticleSorter(TriggeredOperation): 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.solvent_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): diff --git a/hoomd/util.py b/hoomd/util.py index a2439dc0eb..737105d9d3 100644 --- a/hoomd/util.py +++ b/hoomd/util.py @@ -265,7 +265,10 @@ def __init__(self, *args, **kwargs): "This build of HOOMD-blue does not support GPUs.") -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 @@ -279,6 +282,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. @@ -292,6 +298,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() @@ -306,6 +313,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) From b069e0228a4dbdb31b578b2b56621b7445757712 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Sun, 3 Mar 2024 21:41:11 -0600 Subject: [PATCH 42/69] Rename check particles tests --- hoomd/mpcd/pytest/test_methods.py | 4 ++-- hoomd/mpcd/pytest/test_stream.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hoomd/mpcd/pytest/test_methods.py b/hoomd/mpcd/pytest/test_methods.py index 9f932de362..258ea49bdf 100644 --- a/hoomd/mpcd/pytest/test_methods.py +++ b/hoomd/mpcd/pytest/test_methods.py @@ -152,8 +152,8 @@ def test_accel(self, simulation_factory, snap, integrator): 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_test_out_of_bounds(self, simulation_factory, snap, integrator, H, - expected_result): + def test_check_particles(self, simulation_factory, snap, integrator, H, + expected_result): """Test box validation raises an error on run.""" integrator.methods[0].geometry.H = H diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index 20a075cc07..c8e66fc91a 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -291,8 +291,8 @@ def test_step_moving_wall(self, simulation_factory, snap): 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_test_out_of_bounds(self, simulation_factory, snap, H, - expected_result): + def test_check_solvent_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) @@ -470,7 +470,7 @@ def test_step_slip(self, simulation_factory, snap): np.testing.assert_array_almost_equal(snap.mpcd.position[7], [3.18, -4.17, 0]) - def test_test_out_of_bounds(self, simulation_factory, snap): + def test_check_solvent_particles(self, simulation_factory, snap): """Test box validation raises an error on run.""" snap = self._make_particles(snap) sim = simulation_factory(snap) From 74d2c8395f976e8f84fe325cc3c415089590788e Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Sun, 3 Mar 2024 21:45:31 -0600 Subject: [PATCH 43/69] Fix sphinx error --- hoomd/mpcd/force.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index 213917066e..4a922d515b 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -4,13 +4,14 @@ r""" MPCD solvent forces. MPCD can apply a body force to each MPCD particle as a function of position. -The external force should be compatible with the chosen `~hoomd.mpcd.geometry.Geometry`. -Global momentum conservation can be broken by adding a solvent 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 solvent through the collision step. Additionally, a thermostat -will likely be required to maintain temperature control in the driven system. +The external force should be compatible with the chosen +:class:`~hoomd.mpcd.geometry.Geometry`. Global momentum conservation can be +broken by adding a solvent 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 solvent through the collision +step. Additionally, a thermostat will likely be required to maintain temperature +control in the driven system. .. invisible-code-block: python From bab7dcfd018afd4cca2d8f359cc03029f22c647b Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Sun, 3 Mar 2024 22:01:38 -0600 Subject: [PATCH 44/69] Add missing code blocks --- hoomd/mpcd/collide.py | 4 ++++ hoomd/mpcd/fill.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 1d31794e64..c84e8fef4a 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -206,6 +206,8 @@ class AndersenThermostat(CollisionMethod): Variable temperature. + .. code-block:: python + at.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) """ @@ -317,6 +319,8 @@ class StochasticRotationDynamics(CollisionMethod): Variable temperature. + .. code-block:: python + srd.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) """ diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py index d6b23499dc..e3a64b07fc 100644 --- a/hoomd/mpcd/fill.py +++ b/hoomd/mpcd/fill.py @@ -73,6 +73,8 @@ class VirtualParticleFiller(Operation): Variable temperature. + .. code-block:: python + filler.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) """ From fa3214dcf879adad09d56d05337c93ef8f17d241 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Fri, 8 Mar 2024 12:53:25 -0600 Subject: [PATCH 45/69] Review documentation for read only attributes --- hoomd/mpcd/collide.py | 16 +++------------- hoomd/mpcd/fill.py | 2 +- hoomd/mpcd/geometry.py | 16 +++++----------- hoomd/mpcd/integrate.py | 2 +- hoomd/mpcd/methods.py | 4 ++-- hoomd/mpcd/stream.py | 19 +++++++------------ 6 files changed, 19 insertions(+), 40 deletions(-) diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index c84e8fef4a..91ebc92bbd 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -99,13 +99,9 @@ class CollisionMethod(Operation): period (int): Number of integration steps between collisions. embedded_particles (hoomd.filter.filter_like): HOOMD particles to include in collision. - .. invisible-code-block: python - - collision_method = hoomd.mpcd.collide.CollisionMethod(period=1) - simulation.operations.integrator.collision_method = collision_method - Attributes: - embedded_particles (hoomd.filter.filter_like): HOOMD particles to include in collision. + embedded_particles (hoomd.filter.filter_like): HOOMD particles to include + in collision (*read only*). These particles are included in per-cell quantities and have their velocities updated along with the MPCD particles. @@ -122,13 +118,7 @@ class CollisionMethod(Operation): will not be correctly transferred to the body. Support for this is planned in future. - .. rubric:: Example: - - .. code-block:: python - - collision_method.embedded_particles = hoomd.filter.All() - - period (int): Number of integration steps between collisions. + period (int): Number of integration steps between collisions (*read only*). 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 diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py index e3a64b07fc..4923126739 100644 --- a/hoomd/mpcd/fill.py +++ b/hoomd/mpcd/fill.py @@ -119,7 +119,7 @@ class GeometryFiller(VirtualParticleFiller): simulation.operations.integrator.virtual_particle_fillers = [filler] Attributes: - geometry (hoomd.mpcd.geometry.Geometry): Surface to fill around. + geometry (hoomd.mpcd.geometry.Geometry): Surface to fill around (*read only*). """ diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py index 60408a6034..bf2cadf838 100644 --- a/hoomd/mpcd/geometry.py +++ b/hoomd/mpcd/geometry.py @@ -38,19 +38,13 @@ class Geometry(_HOOMDBaseObject): Attributes: no_slip (bool): If True, plates have a no-slip boundary condition. - Otherwise, they have a 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. - .. rubric:: Example: - - .. code-block:: python - - geometry.no_slip = True - """ def __init__(self, no_slip): @@ -103,9 +97,9 @@ class ParallelPlates(Geometry): simulation.operations.integrator.streaming_method = stream Attributes: - H (float): Channel half-width. + H (float): Channel half-width (*read only*). - V (float): Wall speed. + V (float): Wall speed (*read only*). `V` will have no effect if `no_slip` is False because the slip surface cannot generate shear stress. @@ -151,9 +145,9 @@ class PlanarPore(Geometry): simulation.operations.integrator.streaming_method = stream Attributes: - H (float): Pore half-width. + H (float): Pore half-width (*read only*). - L (float): Pore half-length. + L (float): Pore half-length (*read only*). """ diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index c12f877137..1a294ccecf 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -207,7 +207,7 @@ def cell_list(self): A `CellList` is automatically created with each `Integrator` using typical defaults of cell size 1 and random grid shifting enabled. - You can change this configuration if desired. + You can change this parameter configuration if desired. """ return self._cell_list diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py index f81039641d..ead9120385 100644 --- a/hoomd/mpcd/methods.py +++ b/hoomd/mpcd/methods.py @@ -76,9 +76,9 @@ class BounceBack(Method): Attributes: filter (hoomd.filter.filter_like): Subset of particles on which to apply - this method. + this method (*read only*). - geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from (*read only*). """ diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index 571150eee5..6bbf54f1be 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -45,12 +45,9 @@ class StreamingMethod(Operation): period (int): Number of integration steps covered by streaming step. solvent_force (SolventForce): Force on solvent. - .. invisible-code-block: python - - streaming_method = hoomd.mpcd.stream.StreamingMethod(period=1) - Attributes: - period (int): Number of integration steps covered by streaming step. + period (int): Number of integration steps covered by streaming step + (*read only*). The MPCD particles will be streamed every time the :attr:`~hoomd.Simulation.timestep` is a multiple of `period`. The @@ -61,14 +58,11 @@ class StreamingMethod(Operation): used if an external force is applied, and more faithful numerical integration is needed. - .. rubric:: Example: - - .. code-block:: python - - streaming_method.period = 1 - solvent_force (SolventForce): Force on solvent. + The `solvent_force` cannot be changed after the `StreamingMethod` is + constructed, but its attributes can be modified. + """ def __init__(self, period, solvent_force=None): @@ -215,7 +209,8 @@ class BounceBack(StreamingMethod): simulation.operations.integrator.streaming_method = stream Attributes: - geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from + (*read only*). """ From 67280f33ee0e11b24983e461ee1f5df81bfc1bc4 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Fri, 8 Mar 2024 13:12:04 -0600 Subject: [PATCH 46/69] Fix failing MPI tests related to check_particles --- hoomd/mpcd/BounceBackNVE.h | 18 ++++++++++++++++-- hoomd/mpcd/BounceBackStreamingMethod.h | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/hoomd/mpcd/BounceBackNVE.h b/hoomd/mpcd/BounceBackNVE.h index 00369c7f41..85a039313d 100644 --- a/hoomd/mpcd/BounceBackNVE.h +++ b/hoomd/mpcd/BounceBackNVE.h @@ -196,6 +196,7 @@ template bool BounceBackNVE::checkParticles() 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]; @@ -204,11 +205,24 @@ template bool BounceBackNVE::checkParticles() const Scalar3 pos = make_scalar3(postype.x, postype.y, postype.z); if (m_geom->isOutside(pos)) { - 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 diff --git a/hoomd/mpcd/BounceBackStreamingMethod.h b/hoomd/mpcd/BounceBackStreamingMethod.h index f5f582be26..6437782607 100644 --- a/hoomd/mpcd/BounceBackStreamingMethod.h +++ b/hoomd/mpcd/BounceBackStreamingMethod.h @@ -173,17 +173,31 @@ bool BounceBackStreamingMethod::checkParticles() 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)) { - 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 From 5bf0fd893bc328b171eac9ef8fe42836190ce26c Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Tue, 12 Mar 2024 21:26:36 -0500 Subject: [PATCH 47/69] Implement suggestions from code review --- hoomd/mpcd/collide.py | 14 +++++++------- hoomd/mpcd/integrate.py | 3 +-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 91ebc92bbd..6efe797728 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -166,18 +166,18 @@ class AndersenThermostat(CollisionMethod): .. code-block:: python - at = hoomd.mpcd.collide.AndersenThermostat(period=1, kT=1.0) - simulation.operations.integrator.collision_method = at + andersen_thermostat = hoomd.mpcd.collide.AndersenThermostat(period=1, kT=1.0) + simulation.operations.integrator.collision_method = andersen_thermostat Collision including embedded particles. .. code-block:: python - at = hoomd.mpcd.collide.AndersenThermostat( + andersen_thermostat = hoomd.mpcd.collide.AndersenThermostat( period=20, kT=1.0, embedded_particles=hoomd.filter.All()) - simulation.operations.integrator.collision_method = at + simulation.operations.integrator.collision_method = andersen_thermostat Attributes: kT (hoomd.variant.variant_like): Temperature of the solvent @@ -192,13 +192,13 @@ class AndersenThermostat(CollisionMethod): .. code-block:: python - at.kT = 1.0 + andersen_thermostat.kT = 1.0 Variable temperature. .. code-block:: python - at.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) + andersen_thermostat.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) """ @@ -238,7 +238,7 @@ class StochasticRotationDynamics(CollisionMethod): embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to include in collision. - This class implements the stochastic rotation dynamics 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 diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index 1a294ccecf..b98f0eb3ee 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -99,8 +99,7 @@ class Integrator(_MDIntegrator): .. code-block:: python dt_md = 0.005 - dt_mpcd = 0.1 - md_steps_per_collision = numpy.round(dt_mpcd / dt_md).astype(int) + md_steps_per_collision = 20 # collision time = 0.1 stream = hoomd.mpcd.stream.Bulk(period=md_steps_per_collision) collide = hoomd.mpcd.collide.StochasticRotationDynamics( From 56795e1f9d02aaa7545a22ba83627f82cd36d4a8 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Fri, 8 Mar 2024 15:17:09 -0600 Subject: [PATCH 48/69] Refactor VirtualParticleFiller to be more general base class --- hoomd/mpcd/CMakeLists.txt | 2 + hoomd/mpcd/ManualVirtualParticleFiller.cc | 61 ++++++++++++++++++ hoomd/mpcd/ManualVirtualParticleFiller.h | 67 ++++++++++++++++++++ hoomd/mpcd/ParallelPlateGeometryFiller.cc | 7 +- hoomd/mpcd/ParallelPlateGeometryFiller.h | 4 +- hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc | 4 +- hoomd/mpcd/ParticleData.cc | 12 +++- hoomd/mpcd/ParticleData.h | 2 +- hoomd/mpcd/PlanarPoreGeometryFiller.cc | 7 +- hoomd/mpcd/PlanarPoreGeometryFiller.h | 4 +- hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc | 4 +- hoomd/mpcd/VirtualParticleFiller.cc | 65 ++++++++++--------- hoomd/mpcd/VirtualParticleFiller.h | 24 +++---- hoomd/mpcd/module.cc | 2 + 14 files changed, 196 insertions(+), 69 deletions(-) create mode 100644 hoomd/mpcd/ManualVirtualParticleFiller.cc create mode 100644 hoomd/mpcd/ManualVirtualParticleFiller.h diff --git a/hoomd/mpcd/CMakeLists.txt b/hoomd/mpcd/CMakeLists.txt index f8a6f3ffe7..4395bbb7bd 100644 --- a/hoomd/mpcd/CMakeLists.txt +++ b/hoomd/mpcd/CMakeLists.txt @@ -12,6 +12,7 @@ set(_mpcd_cc_sources CollisionMethod.cc Communicator.cc Integrator.cc + ManualVirtualParticleFiller.cc ParallelPlateGeometryFiller.cc PlanarPoreGeometryFiller.cc Sorter.cc @@ -34,6 +35,7 @@ set(_mpcd_headers Communicator.h CommunicatorUtilities.h Integrator.h + ManualVirtualParticleFiller.h ParticleData.h ParticleDataSnapshot.h ParticleDataUtilities.h diff --git a/hoomd/mpcd/ManualVirtualParticleFiller.cc b/hoomd/mpcd/ManualVirtualParticleFiller.cc new file mode 100644 index 0000000000..a3a9cfa4c7 --- /dev/null +++ b/hoomd/mpcd/ManualVirtualParticleFiller.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2009-2023 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..1af2501626 --- /dev/null +++ b/hoomd/mpcd/ManualVirtualParticleFiller.h @@ -0,0 +1,67 @@ +// Copyright (c) 2009-2023 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/ParallelPlateGeometryFiller.cc b/hoomd/mpcd/ParallelPlateGeometryFiller.cc index cc11e60126..533a5ebdca 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFiller.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.cc @@ -18,7 +18,7 @@ mpcd::ParallelPlateGeometryFiller::ParallelPlateGeometryFiller( Scalar density, std::shared_ptr T, std::shared_ptr geom) - : mpcd::VirtualParticleFiller(sysdef, type, density, T), m_geom(geom) + : mpcd::ManualVirtualParticleFiller(sysdef, type, density, T), m_geom(geom) { m_exec_conf->msg->notice(5) << "Constructing MPCD ParallelPlateGeometryFiller" << std::endl; } @@ -91,13 +91,12 @@ void mpcd::ParallelPlateGeometryFiller::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::ParallelPlateGeometryFiller, timestep, seed), - hoomd::Counter(tag)); + hoomd::Counter(tag, m_filler_id)); signed char sign = (char)((i >= m_N_lo) - (i < m_N_lo)); if (sign == -1) // bottom { @@ -110,7 +109,7 @@ void mpcd::ParallelPlateGeometryFiller::drawParticles(uint64_t timestep) 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), diff --git a/hoomd/mpcd/ParallelPlateGeometryFiller.h b/hoomd/mpcd/ParallelPlateGeometryFiller.h index b295c590fb..4105e1f7ee 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFiller.h +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.h @@ -13,8 +13,8 @@ #error This header cannot be compiled by nvcc #endif +#include "ManualVirtualParticleFiller.h" #include "ParallelPlateGeometry.h" -#include "VirtualParticleFiller.h" #include @@ -27,7 +27,7 @@ namespace mpcd * 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::VirtualParticleFiller +class PYBIND11_EXPORT ParallelPlateGeometryFiller : public mpcd::ManualVirtualParticleFiller { public: ParallelPlateGeometryFiller(std::shared_ptr sysdef, diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc index 884f4a3d12..307b6af25c 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc @@ -40,8 +40,6 @@ void mpcd::ParallelPlateGeometryFillerGPU::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(); @@ -57,7 +55,7 @@ void mpcd::ParallelPlateGeometryFillerGPU::drawParticles(uint64_t timestep) m_N_lo, m_N_hi, m_first_tag, - first_idx, + m_first_idx, (*m_T)(timestep), timestep, seed, diff --git a/hoomd/mpcd/ParticleData.cc b/hoomd/mpcd/ParticleData.cc index f0f5d56e19..f4db7c2b65 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 85cefc1a92..d79bb5c884 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/PlanarPoreGeometryFiller.cc b/hoomd/mpcd/PlanarPoreGeometryFiller.cc index 05d4de5210..0f82f3c44e 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFiller.cc +++ b/hoomd/mpcd/PlanarPoreGeometryFiller.cc @@ -20,7 +20,7 @@ mpcd::PlanarPoreGeometryFiller::PlanarPoreGeometryFiller( Scalar density, std::shared_ptr T, std::shared_ptr geom) - : mpcd::VirtualParticleFiller(sysdef, type, density, T), m_num_boxes(0), + : 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 PlanarPoreGeometryFiller" << std::endl; @@ -167,13 +167,12 @@ void mpcd::PlanarPoreGeometryFiller::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::PlanarPoreGeometryFiller, timestep, seed), - hoomd::Counter(tag)); + hoomd::Counter(tag, m_filler_id)); // advanced past end of this box range, take the next if (i >= boxlast) @@ -187,7 +186,7 @@ void mpcd::PlanarPoreGeometryFiller::drawParticles(uint64_t timestep) 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), diff --git a/hoomd/mpcd/PlanarPoreGeometryFiller.h b/hoomd/mpcd/PlanarPoreGeometryFiller.h index 4f7e8a80b7..dca5104b21 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFiller.h +++ b/hoomd/mpcd/PlanarPoreGeometryFiller.h @@ -13,8 +13,8 @@ #error This header cannot be compiled by nvcc #endif +#include "ManualVirtualParticleFiller.h" #include "PlanarPoreGeometry.h" -#include "VirtualParticleFiller.h" #include @@ -27,7 +27,7 @@ namespace mpcd * 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 PlanarPoreGeometryFiller : public mpcd::VirtualParticleFiller +class PYBIND11_EXPORT PlanarPoreGeometryFiller : public mpcd::ManualVirtualParticleFiller { public: PlanarPoreGeometryFiller(std::shared_ptr sysdef, diff --git a/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc index 30097a6f0b..1636851217 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc @@ -44,8 +44,6 @@ void mpcd::PlanarPoreGeometryFillerGPU::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(); @@ -60,7 +58,7 @@ void mpcd::PlanarPoreGeometryFillerGPU::drawParticles(uint64_t timestep) m_mpcd_pdata->getMass(), m_type, m_first_tag, - first_idx, + m_first_idx, (*m_T)(timestep), timestep, seed, diff --git a/hoomd/mpcd/VirtualParticleFiller.cc b/hoomd/mpcd/VirtualParticleFiller.cc index d827b2ed6d..9c731cb926 100644 --- a/hoomd/mpcd/VirtualParticleFiller.cc +++ b/hoomd/mpcd/VirtualParticleFiller.cc @@ -10,52 +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, 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_T(T), m_N_fill(0), - m_first_tag(0) + m_mpcd_pdata(m_sysdef->getMPCDParticleData()), m_density(density), m_T(T) { setType(type); - } - -void mpcd::VirtualParticleFiller::fill(uint64_t timestep) - { - if (!m_cl) - { - throw std::runtime_error("Cell list has not been set"); - } - // 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) @@ -78,6 +51,32 @@ 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) + { + MPI_Exscan(&N_fill, + &first_tag, + 1, + MPI_UNSIGNED, + MPI_SUM, + m_exec_conf->getMPICommunicator()); + } +#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; + } + /*! * \param m Python module to export to */ diff --git a/hoomd/mpcd/VirtualParticleFiller.h b/hoomd/mpcd/VirtualParticleFiller.h index 7ac26dcbc6..ee80c41fda 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_ @@ -30,11 +30,7 @@ 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 { @@ -47,8 +43,9 @@ class PYBIND11_EXPORT VirtualParticleFiller : public Autotuned 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; @@ -57,11 +54,13 @@ class PYBIND11_EXPORT VirtualParticleFiller : public Autotuned //! 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(const std::string& type); + //! Get the fill particle temperature std::shared_ptr getTemperature() const { return m_T; @@ -89,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 + unsigned int computeFirstTag(unsigned int N_fill) const; - //! Compute the total number of particles to fill - virtual void computeNumFill() { } - - //! 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/module.cc b/hoomd/mpcd/module.cc index 0b8f867198..3a01d7edc7 100644 --- a/hoomd/mpcd/module.cc +++ b/hoomd/mpcd/module.cc @@ -50,6 +50,7 @@ #endif // virtual particle fillers +#include "ManualVirtualParticleFiller.h" #include "ParallelPlateGeometryFiller.h" #include "PlanarPoreGeometryFiller.h" #include "VirtualParticleFiller.h" @@ -196,6 +197,7 @@ PYBIND11_MODULE(_mpcd, m) #endif // ENABLE_HIP mpcd::detail::export_VirtualParticleFiller(m); + mpcd::detail::export_ManualVirtualParticleFiller(m); mpcd::detail::export_ParallelPlateGeometryFiller(m); mpcd::detail::export_PlanarPoreGeometryFiller(m); #ifdef ENABLE_HIP From 83ebdf4faeb82dfdd9c12e5087393bd4a709b5b9 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Wed, 13 Mar 2024 13:36:40 -0500 Subject: [PATCH 49/69] Reenable code formatting and fix flake8 errors --- .pre-commit-config.yaml | 1 - hoomd/mpcd/__init__.py | 2 +- hoomd/mpcd/collide.py | 19 ++++++--- hoomd/mpcd/fill.py | 8 ++-- hoomd/mpcd/force.py | 27 ++++++------ hoomd/mpcd/geometry.py | 6 ++- hoomd/mpcd/integrate.py | 16 ++++--- hoomd/mpcd/methods.py | 64 ++++++++++++++-------------- hoomd/mpcd/pytest/test_collide.py | 3 +- hoomd/mpcd/pytest/test_fill.py | 4 +- hoomd/mpcd/pytest/test_force.py | 4 +- hoomd/mpcd/pytest/test_geometry.py | 3 +- hoomd/mpcd/pytest/test_integrator.py | 5 +-- hoomd/mpcd/pytest/test_methods.py | 7 +-- hoomd/mpcd/pytest/test_snapshot.py | 8 ++-- hoomd/mpcd/pytest/test_stream.py | 29 ++++++++----- hoomd/mpcd/pytest/test_tune.py | 3 +- hoomd/mpcd/stream.py | 27 ++++++------ hoomd/mpcd/tune.py | 10 +++-- setup.cfg | 2 +- 20 files changed, 141 insertions(+), 107 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d24af169f4..1156351398 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/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index 8828cd9cd9..3126749541 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -1,7 +1,7 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -""" Multiparticle collision dynamics. +"""Multiparticle collision dynamics. Simulating complex fluids and soft matter using conventional molecular dynamics methods (`hoomd.md`) can be computationally demanding due to large disparities diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 6efe797728..4ec7bf417d 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -97,11 +97,12 @@ class CollisionMethod(Operation): Args: period (int): Number of integration steps between collisions. - embedded_particles (hoomd.filter.filter_like): HOOMD particles to include in collision. + 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*). + embedded_particles (hoomd.filter.filter_like): HOOMD particles to + include in collision (*read only*). These particles are included in per-cell quantities and have their velocities updated along with the MPCD particles. @@ -118,10 +119,12 @@ class CollisionMethod(Operation): will not be correctly transferred to the body. Support for this is planned in future. - period (int): Number of integration steps between collisions (*read only*). + period (int): Number of integration steps between collisions + (*read only*). - 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 + 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`. @@ -166,7 +169,9 @@ class AndersenThermostat(CollisionMethod): .. code-block:: python - andersen_thermostat = hoomd.mpcd.collide.AndersenThermostat(period=1, kT=1.0) + andersen_thermostat = hoomd.mpcd.collide.AndersenThermostat( + period=1, + kT=1.0) simulation.operations.integrator.collision_method = andersen_thermostat Collision including embedded particles. diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py index 4923126739..487c84dfe0 100644 --- a/hoomd/mpcd/fill.py +++ b/hoomd/mpcd/fill.py @@ -1,7 +1,7 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -r""" MPCD virtual-particle fillers. +r"""MPCD virtual-particle fillers. Virtual particles are MPCD solvent particles that are added to ensure MPCD collision cells that are sliced by solid boundaries do not become "underfilled". @@ -119,7 +119,8 @@ class GeometryFiller(VirtualParticleFiller): simulation.operations.integrator.virtual_particle_fillers = [filler] Attributes: - geometry (hoomd.mpcd.geometry.Geometry): Surface to fill around (*read only*). + geometry (hoomd.mpcd.geometry.Geometry): Surface to fill around + (*read only*). """ @@ -147,7 +148,8 @@ def _attach_hook(self): 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" + assert class_ is not None, ("Virtual particle filler for geometry" + " not found") self._cpp_obj = class_( sim.state._cpp_sys_def, diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index 4a922d515b..e0fbb342c9 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -1,7 +1,7 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -r""" MPCD solvent forces. +r"""MPCD solvent forces. MPCD can apply a body force to each MPCD particle as a function of position. The external force should be compatible with the chosen @@ -46,10 +46,10 @@ class BlockForce(SolventForce): blocks. half_width (float): Half the width of each block. - The `force` magnitude *F* is applied in the *x* direction on the solvent particles - in blocks defined along the *y* direction by the `half_separation` *H* and - the `half_width` *w*. The force in *x* is :math:`+F` in the upper block, - :math:`-F` in the lower block, and zero otherwise. + The `force` magnitude *F* is applied in the *x* direction on the solvent + particles in blocks defined along the *y* direction by the `half_separation` + *H* and the `half_width` *w*. The force in *x* is :math:`+F` in the upper + block, :math:`-F` in the lower block, and zero otherwise. .. math:: :nowrap: @@ -62,9 +62,9 @@ class BlockForce(SolventForce): \end{cases} \end{equation} - The `BlockForce` can be used to implement the double-parabola method for measuring - viscosity by setting :math:`H = L_y/4` and :math:`w = L_y/4`, where :math:`L_y` is - the size of the simulation box in *y*. + The `BlockForce` can be used to implement the double-parabola method for + measuring viscosity by setting :math:`H = L_y/4` and :math:`w = L_y/4`, + 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 @@ -77,7 +77,10 @@ class BlockForce(SolventForce): .. code-block:: python Ly = simulation.state.box.Ly - force = hoomd.mpcd.force.BlockForce(force=1.0, half_separation=Ly/4, half_width=Ly/4) + force = hoomd.mpcd.force.BlockForce( + force=1.0, + half_separation=Ly/4, + half_width=Ly/4) stream = hoomd.mpcd.stream.Bulk(period=1, solvent_force=force) simulation.operations.integrator.streaming_method = stream @@ -135,9 +138,9 @@ class ConstantForce(SolventForce): The same constant force is applied to all solvent 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. + flow in conjunction with a confined geometry having no-slip boundary + conditions. It is also useful for measuring diffusion coefficients with + nonequilibrium methods. .. rubric:: Example: diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py index bf2cadf838..073bd2bba9 100644 --- a/hoomd/mpcd/geometry.py +++ b/hoomd/mpcd/geometry.py @@ -6,8 +6,10 @@ 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`) +* 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 diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index b98f0eb3ee..29003f9fd0 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -1,6 +1,8 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. +"""Implement MPCD Integrator.""" + import hoomd from hoomd.data.parameterdicts import ParameterDict from hoomd.data import syncedlist @@ -48,7 +50,8 @@ class Integrator(_MDIntegrator): collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method for the MPCD solvent and any embedded particles. - virtual_particle_fillers (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): Solvent + virtual_particle_fillers + (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): Solvent virtual-particle filler(s). solvent_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the @@ -86,7 +89,9 @@ class Integrator(_MDIntegrator): .. code-block:: python stream = hoomd.mpcd.stream.Bulk(period=1) - collide = hoomd.mpcd.collide.StochasticRotationDynamics(period=1, angle=130) + collide = hoomd.mpcd.collide.StochasticRotationDynamics( + period=1, + angle=130) integrator = hoomd.mpcd.Integrator( dt=0.1, streaming_method=stream, @@ -214,9 +219,8 @@ def cell_list(self): @property def virtual_particle_fillers(self): """Sequence[hoomd.mpcd.fill.VirtualParticleFiller]: Solvent - virtual-particle fillers. - - """ + virtual-particle fillers. + """ # noqa: D205,D415 return self._virtual_particle_fillers @virtual_particle_fillers.setter @@ -261,7 +265,7 @@ def _setattr_param(self, attr, value): if value is not None and value._attached: raise ValueError("Cannot attach to multiple integrators.") - # if already attached, change out which is attached, then set parameter + # if already attached, change out and set parameter if self._attached: if cur_value is not None: cur_value._detach() diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py index ead9120385..efa6136393 100644 --- a/hoomd/mpcd/methods.py +++ b/hoomd/mpcd/methods.py @@ -1,7 +1,7 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -r""" MPCD integration methods +r"""MPCD integration methods. Extra integration methods for solutes (MD particles) embedded in an MPCD solvent. These methods are not restricted to MPCD simulations: they can be used @@ -31,54 +31,54 @@ class BounceBack(Method): 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 solvent. The integration scheme is velocity Verlet with bounce-back + A bounce-back method for integrating solutes (MD particles) embedded in an + MPCD solvent. 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, 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. + 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, 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. + 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. + 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(H=3.0) - nve = hoomd.mpcd.methods.BounceBack( - filter=hoomd.filter.All(), - geometry=plates) + plates = hoomd.mpcd.geometry.ParallelPlates(H=3.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*). + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from + (*read only*). """ diff --git a/hoomd/mpcd/pytest/test_collide.py b/hoomd/mpcd/pytest/test_collide.py index 49029aa51f..62b1929fcc 100644 --- a/hoomd/mpcd/pytest/test_collide.py +++ b/hoomd/mpcd/pytest/test_collide.py @@ -1,9 +1,10 @@ # Copyright (c) 2009-2023 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 -import pytest @pytest.fixture diff --git a/hoomd/mpcd/pytest/test_fill.py b/hoomd/mpcd/pytest/test_fill.py index 2e00220402..cc28fbd37e 100644 --- a/hoomd/mpcd/pytest/test_fill.py +++ b/hoomd/mpcd/pytest/test_fill.py @@ -1,10 +1,10 @@ # Copyright (c) 2009-2023 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 -import numpy as np -import pytest @pytest.fixture diff --git a/hoomd/mpcd/pytest/test_force.py b/hoomd/mpcd/pytest/test_force.py index c8f09d79fb..f2c8becec5 100644 --- a/hoomd/mpcd/pytest/test_force.py +++ b/hoomd/mpcd/pytest/test_force.py @@ -1,10 +1,10 @@ # Copyright (c) 2009-2023 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 -import numpy as np -import pytest def test_block_force(simulation_factory): diff --git a/hoomd/mpcd/pytest/test_geometry.py b/hoomd/mpcd/pytest/test_geometry.py index 26dcfa7b42..5c23514079 100644 --- a/hoomd/mpcd/pytest/test_geometry.py +++ b/hoomd/mpcd/pytest/test_geometry.py @@ -1,9 +1,10 @@ # Copyright (c) 2009-2023 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 -import pytest @pytest.fixture diff --git a/hoomd/mpcd/pytest/test_integrator.py b/hoomd/mpcd/pytest/test_integrator.py index 040e6d79a1..0d9cc15a29 100644 --- a/hoomd/mpcd/pytest/test_integrator.py +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -1,12 +1,11 @@ # Copyright (c) 2009-2023 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 -import numpy as np -import pytest - @pytest.fixture def make_simulation(simulation_factory): diff --git a/hoomd/mpcd/pytest/test_methods.py b/hoomd/mpcd/pytest/test_methods.py index 258ea49bdf..35e676a487 100644 --- a/hoomd/mpcd/pytest/test_methods.py +++ b/hoomd/mpcd/pytest/test_methods.py @@ -1,11 +1,12 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -import hoomd -from hoomd.conftest import pickling_check import numpy as np import pytest +import hoomd +from hoomd.conftest import pickling_check + @pytest.fixture def snap(): @@ -103,7 +104,7 @@ def test_step_slip(self, simulation_factory, snap, integrator): snap.particles.velocity, [[1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]]) - # take another step, reflecting the perpendicular motion of second particle + # take another step, reflecting perpendicular motion of second particle sim.run(1) snap = sim.state.get_snapshot() if snap.communicator.rank == 0: diff --git a/hoomd/mpcd/pytest/test_snapshot.py b/hoomd/mpcd/pytest/test_snapshot.py index cd727f6d20..543c39200c 100644 --- a/hoomd/mpcd/pytest/test_snapshot.py +++ b/hoomd/mpcd/pytest/test_snapshot.py @@ -1,9 +1,10 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. +import pytest + import hoomd 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]]) @@ -66,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]]) @@ -134,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 index c8e66fc91a..a6067f12f7 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -1,11 +1,12 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -import hoomd -from hoomd.conftest import pickling_check import numpy as np import pytest +import hoomd +from hoomd.conftest import pickling_check + @pytest.fixture def snap(): @@ -92,7 +93,10 @@ def test_force_attach(self, simulation_factory, snap, cls, init_args, pickling_check(sm) def test_forced_step(self, simulation_factory, snap, cls, init_args): - """Test a step with particle starting in the middle, constant force in +x and -z. + """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 @@ -153,8 +157,9 @@ def test_bulk_step(self, simulation_factory, snap): 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 + # 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) @@ -250,7 +255,7 @@ def test_step_slip(self, simulation_factory, snap): 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 perpendicular motion of second particle + # take another step, reflecting perpendicular motion of second particle sim.run(1) snap = sim.state.get_snapshot() if snap.communicator.rank == 0: @@ -369,12 +374,14 @@ def test_step_noslip(self, simulation_factory, snap): [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 + # 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 + # 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], @@ -438,12 +445,14 @@ def test_step_slip(self, simulation_factory, snap): [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 + # 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 + # 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], diff --git a/hoomd/mpcd/pytest/test_tune.py b/hoomd/mpcd/pytest/test_tune.py index 2660893da9..4be4bef256 100644 --- a/hoomd/mpcd/pytest/test_tune.py +++ b/hoomd/mpcd/pytest/test_tune.py @@ -1,9 +1,10 @@ # Copyright (c) 2009-2023 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 -import pytest @pytest.fixture diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index 6bbf54f1be..aa07f74381 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -1,7 +1,7 @@ # Copyright (c) 2009-2023 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 :class:`.mpcd.Integrator` and @@ -133,7 +133,8 @@ def _attach_hook(self): 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" + assert class_ is not None, ("C++ streaming method could not be" + " determined") self._cpp_obj = class_( sim.state._cpp_sys_def, @@ -165,19 +166,20 @@ class BounceBack(StreamingMethod): geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. solvent_force (SolventForce): Force on solvent. - 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 + 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 solvent 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. + 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 solvent particles, there - are a few important caveats: + 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 (`hoomd.mpcd.methods.BounceBack`). + 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 @@ -195,7 +197,8 @@ class BounceBack(StreamingMethod): stream = hoomd.mpcd.stream.BounceBack( period=1, - geometry=hoomd.mpcd.geometry.ParallelPlates(H=3.0, V=1.0, no_slip=True)) + geometry=hoomd.mpcd.geometry.ParallelPlates( + H=3.0, V=1.0, no_slip=True)) simulation.operations.integrator.streaming_method = stream Pressure driven flow between parallel plates. @@ -292,8 +295,8 @@ def _detach_hook(self): super()._detach_hook() @classmethod - def _register_cpp_class(cls, geometry, module, cpp_class_name): - # we will allow "None" for the force, but we need its class type not its value + 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/tune.py b/hoomd/mpcd/tune.py index 4ab48baefb..8861b0de98 100644 --- a/hoomd/mpcd/tune.py +++ b/hoomd/mpcd/tune.py @@ -33,12 +33,14 @@ class ParticleSorter(TriggeredOperation): 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. + 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. For best performance, the `ParticleSorter` should **not** be added to - `hoomd.Operations.tuners`. Instead, set it in `hoomd.mpcd.Integrator.solvent_sorter`. + `hoomd.Operations.tuners`. Instead, set it in + `hoomd.mpcd.Integrator.solvent_sorter`. Essentially all MPCD systems benefit from sorting, so it is recommended to use one for all simulations! 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, From 92a4aba77c066211f8a6182ad6ebbb48f9398515 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 14 Mar 2024 14:36:05 -0500 Subject: [PATCH 50/69] Fix issues in docs --- hoomd/mpcd/integrate.py | 5 ++--- hoomd/mpcd/methods.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index 29003f9fd0..7712269c19 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -218,9 +218,8 @@ def cell_list(self): @property def virtual_particle_fillers(self): - """Sequence[hoomd.mpcd.fill.VirtualParticleFiller]: Solvent - virtual-particle fillers. - """ # noqa: D205,D415 + """Sequence[hoomd.mpcd.fill.VirtualParticleFiller]: Solvent \ + virtual-particle fillers.""" return self._virtual_particle_fillers @virtual_particle_fillers.setter diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py index efa6136393..9343d2ab0f 100644 --- a/hoomd/mpcd/methods.py +++ b/hoomd/mpcd/methods.py @@ -68,8 +68,8 @@ class BounceBack(Method): .. code-block:: python - plates = hoomd.mpcd.geometry.ParallelPlates(H=3.0) nve = - hoomd.mpcd.methods.BounceBack( + plates = hoomd.mpcd.geometry.ParallelPlates(H=3.0) + nve = hoomd.mpcd.methods.BounceBack( filter=hoomd.filter.All(), geometry=plates) simulation.operations.integrator.methods.append(nve) From fe104de4b6dd54c1cd950b15e7fd17a9d0564aa3 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Fri, 29 Mar 2024 09:52:05 -0500 Subject: [PATCH 51/69] Fix merge error in mpcd CMake file --- hoomd/mpcd/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hoomd/mpcd/CMakeLists.txt b/hoomd/mpcd/CMakeLists.txt index 567d9f21a9..64d3752659 100644 --- a/hoomd/mpcd/CMakeLists.txt +++ b/hoomd/mpcd/CMakeLists.txt @@ -169,7 +169,7 @@ 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} ${_mpcd_cu_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) From 293861b6c14528406dd5b81c82b4cce23d7f06e2 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 8 Apr 2024 22:05:40 -0500 Subject: [PATCH 52/69] Change half length to full length in BlockForce --- hoomd/mpcd/BlockForce.cc | 8 ++---- hoomd/mpcd/BlockForce.h | 48 ++++++++++++++++---------------- hoomd/mpcd/force.py | 36 +++++++++++------------- hoomd/mpcd/pytest/test_force.py | 30 ++++++++++---------- hoomd/mpcd/pytest/test_stream.py | 5 ++-- 5 files changed, 59 insertions(+), 68 deletions(-) diff --git a/hoomd/mpcd/BlockForce.cc b/hoomd/mpcd/BlockForce.cc index 5aeaac3673..6b5d93ac62 100644 --- a/hoomd/mpcd/BlockForce.cc +++ b/hoomd/mpcd/BlockForce.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! @@ -20,10 +20,8 @@ void export_BlockForce(pybind11::module& m) pybind11::class_>(m, "BlockForce") .def(pybind11::init()) .def_property("force", &BlockForce::getForce, &BlockForce::setForce) - .def_property("half_separation", - &BlockForce::getHalfSeparation, - &BlockForce::setHalfSeparation) - .def_property("half_width", &BlockForce::getHalfWidth, &BlockForce::setHalfWidth); + .def_property("separation", &BlockForce::getSeparation, &BlockForce::setSeparation) + .def_property("width", &BlockForce::getWidth, &BlockForce::setWidth); } } // end namespace detail diff --git a/hoomd/mpcd/BlockForce.h b/hoomd/mpcd/BlockForce.h index bc32f5186c..88ea77ea17 100644 --- a/hoomd/mpcd/BlockForce.h +++ b/hoomd/mpcd/BlockForce.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! @@ -52,13 +52,13 @@ class __attribute__((visibility("default"))) BlockForce //! Constructor /*! * \param F Force on all particles. - * \param H Half-width between block regions. - * \param w Half-width of blocks. + * \param separation Separation between centers of blocks. + * \param width Width of each block. */ - HOSTDEVICE BlockForce(Scalar F, Scalar H, Scalar w) : m_F(F) + HOSTDEVICE BlockForce(Scalar F, Scalar separation, Scalar width) : m_F(F) { - m_H_plus_w = H + w; - m_H_minus_w = H - w; + m_H_plus_w = 0.5 * (separation + width); + m_H_minus_w = 0.5 * (separation - width); } //! Force evaluation method @@ -86,32 +86,32 @@ class __attribute__((visibility("default"))) BlockForce m_F = F; } - //! Get the half separation distance between blocks - Scalar getHalfSeparation() const + //! Get the separation distance between block centers + Scalar getSeparation() const { - return 0.5 * (m_H_plus_w + m_H_minus_w); + return (m_H_plus_w + m_H_minus_w); } - //! Set the half separation distance between blocks - void setHalfSeparation(Scalar H) + //! Set the separation distance between block centers + void setSeparation(Scalar H) { - const Scalar w = getHalfWidth(); - m_H_plus_w = H + w; - m_H_minus_w = H - w; + const Scalar w = getWidth(); + m_H_plus_w = 0.5 * (H + w); + m_H_minus_w = 0.5 * (H - w); } - //! Get the block half width - Scalar getHalfWidth() const + //! Get the block width + Scalar getWidth() const { - return 0.5 * (m_H_plus_w - m_H_minus_w); + return (m_H_plus_w - m_H_minus_w); } - //! Set the block half width - void setHalfWidth(Scalar w) + //! Set the block width + void setWidth(Scalar w) { - const Scalar H = getHalfSeparation(); - m_H_plus_w = H + w; - m_H_minus_w = H - w; + const Scalar H = getSeparation(); + m_H_plus_w = 0.5 * (H + w); + m_H_minus_w = 0.5 * (H - w); } #ifndef __HIPCC__ @@ -124,8 +124,8 @@ class __attribute__((visibility("default"))) BlockForce 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 + Scalar m_H_plus_w; //!< Upper bound on upper block + Scalar m_H_minus_w; //!< Lower bound on upper block }; #ifndef __HIPCC__ diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index 5b4b3da909..df655fb923 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -42,14 +42,13 @@ class BlockForce(SolventForce): Args: force (float): Magnitude of the force in *x* per particle. - half_separation (float): Half the distance between the centers of the - blocks. - half_width (float): Half the width of each block. + separation (float): Distance between the centers of the blocks. + width (float): Width of each block. The `force` magnitude *F* is applied in the *x* direction on the solvent - particles in blocks defined along the *y* direction by the `half_separation` - *H* and the `half_width` *w*. The force in *x* is :math:`+F` in the upper - block, :math:`-F` in the lower block, and zero otherwise. + 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: @@ -63,7 +62,7 @@ class BlockForce(SolventForce): \end{equation} The `BlockForce` can be used to implement the double-parabola method for - measuring viscosity by setting :math:`H = L_y/4` and :math:`w = L_y/4`, + 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: @@ -78,9 +77,7 @@ class BlockForce(SolventForce): Ly = simulation.state.box.Ly force = hoomd.mpcd.force.BlockForce( - force=1.0, - half_separation=Ly/4, - half_width=Ly/4) + force=1.0, separation=Ly/2, width=Ly/2) stream = hoomd.mpcd.stream.Bulk(period=1, solvent_force=force) simulation.operations.integrator.streaming_method = stream @@ -93,40 +90,39 @@ class BlockForce(SolventForce): force.force = 1.0 - half_separation (float): Half the distance between the centers of the - blocks. + separation (float): Ddistance between the centers of the blocks. .. rubric:: Example: .. code-block:: python Ly = simulation.state.box.Ly - force.half_separation = Ly / 4 + force.separation = Ly / 2 - half_width (float): Half the width of each block. + width (float): Width of each block. .. rubric:: Example: .. code-block:: python Ly = simulation.state.box.Ly - force.half_width = Ly / 4 + force.width = Ly / 2 """ - def __init__(self, force, half_separation=None, half_width=None): + def __init__(self, force, separation=None, width=None): super().__init__() param_dict = ParameterDict( force=float(force), - half_separation=float(half_separation), - half_width=float(half_width), + separation=float(separation), + width=float(width), ) self._param_dict.update(param_dict) def _attach_hook(self): - self._cpp_obj = _mpcd.BlockForce(self.force, self.half_separation, - self.half_width) + self._cpp_obj = _mpcd.BlockForce(self.force, self.separation, + self.width) super()._attach_hook() diff --git a/hoomd/mpcd/pytest/test_force.py b/hoomd/mpcd/pytest/test_force.py index f2c8becec5..ecb5dfe75d 100644 --- a/hoomd/mpcd/pytest/test_force.py +++ b/hoomd/mpcd/pytest/test_force.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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 @@ -9,36 +9,34 @@ def test_block_force(simulation_factory): # make the force - force = hoomd.mpcd.force.BlockForce(force=2.0, - half_separation=3.0, - half_width=0.5) + force = hoomd.mpcd.force.BlockForce(force=2.0, separation=6.0, width=1.0) assert force.force == 2.0 - assert force.half_separation == 3.0 - assert force.half_width == 0.5 + assert force.separation == 6.0 + assert force.width == 1.0 pickling_check(force) # try changing the values force.force = 1.0 - force.half_separation = 2.0 - force.half_width = 0.25 + force.separation = 4.0 + force.width = 0.5 assert force.force == 1.0 - assert force.half_separation == 2.0 - assert force.half_width == 0.25 + 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.half_separation == 2.0 - assert force.half_width == 0.25 + assert force.separation == 4.0 + assert force.width == 0.5 # change values while attached force.force = 2.0 - force.half_separation = 3.0 - force.half_width = 0.5 + force.separation = 6.0 + force.width = 1.0 assert force.force == 2.0 - assert force.half_separation == 3.0 - assert force.half_width == 0.5 + assert force.separation == 6.0 + assert force.width == 1.0 pickling_check(force) diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index a6067f12f7..57e4c1e22f 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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 @@ -70,8 +70,7 @@ def test_pickling(self, simulation_factory, snap, cls, init_args): "force", [ None, - hoomd.mpcd.force.BlockForce( - force=2.0, half_separation=3.0, half_width=0.5), + 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), ], From 3c4afbb5096e5b9053ff44b8e2e08d1eaa2680d8 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 8 Apr 2024 22:31:45 -0500 Subject: [PATCH 53/69] Use more descriptive variables for ParallelPlates --- hoomd/mpcd/ParallelPlateGeometry.h | 23 +++++++---- hoomd/mpcd/ParallelPlateGeometryFiller.cc | 2 +- hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu | 2 +- hoomd/mpcd/StreamingGeometry.cc | 4 +- hoomd/mpcd/fill.py | 4 +- hoomd/mpcd/geometry.py | 41 +++++++++++--------- hoomd/mpcd/integrate.py | 2 +- hoomd/mpcd/methods.py | 4 +- hoomd/mpcd/pytest/test_fill.py | 4 +- hoomd/mpcd/pytest/test_geometry.py | 26 +++++++------ hoomd/mpcd/pytest/test_integrator.py | 4 +- hoomd/mpcd/pytest/test_methods.py | 10 ++--- hoomd/mpcd/pytest/test_stream.py | 15 ++++--- hoomd/mpcd/stream.py | 5 ++- 14 files changed, 83 insertions(+), 63 deletions(-) diff --git a/hoomd/mpcd/ParallelPlateGeometry.h b/hoomd/mpcd/ParallelPlateGeometry.h index 2eca4006d5..b27ae75505 100644 --- a/hoomd/mpcd/ParallelPlateGeometry.h +++ b/hoomd/mpcd/ParallelPlateGeometry.h @@ -54,12 +54,12 @@ class __attribute__((visibility("default"))) ParallelPlateGeometry public: //! Constructor /*! - * \param H Channel half-width - * \param V Velocity of the wall + * \param separation Distance between plates + * \param speed Speed of the wall * \param no_slip Boundary condition at the wall (slip or no-slip) */ - HOSTDEVICE ParallelPlateGeometry(Scalar H, Scalar V, bool no_slip) - : m_H(H), m_V(V), m_no_slip(no_slip) + HOSTDEVICE ParallelPlateGeometry(Scalar separation, Scalar speed, bool no_slip) + : m_H(Scalar(0.5) * separation), m_V(speed), m_no_slip(no_slip) { } @@ -143,11 +143,20 @@ class __attribute__((visibility("default"))) ParallelPlateGeometry return m_H; } - //! Get the wall velocity + //! Get distance between plates /*! - * \returns Wall velocity + * \returns Distance between plates */ - HOSTDEVICE Scalar getVelocity() const + HOSTDEVICE Scalar getSeparation() const + { + return 2 * m_H; + } + + //! Get the wall speed + /*! + * \returns Wall speed + */ + HOSTDEVICE Scalar getSpeed() const { return m_V; } diff --git a/hoomd/mpcd/ParallelPlateGeometryFiller.cc b/hoomd/mpcd/ParallelPlateGeometryFiller.cc index a22ec9a115..345f6bd9f0 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFiller.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.cc @@ -121,7 +121,7 @@ void mpcd::ParallelPlateGeometryFiller::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)); diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu index 8fb8ad22bc..7db438616c 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu @@ -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)); diff --git a/hoomd/mpcd/StreamingGeometry.cc b/hoomd/mpcd/StreamingGeometry.cc index b3060ddb89..07a21de2d0 100644 --- a/hoomd/mpcd/StreamingGeometry.cc +++ b/hoomd/mpcd/StreamingGeometry.cc @@ -21,8 +21,8 @@ void export_ParallelPlateGeometry(pybind11::module& m) m, ParallelPlateGeometry::getName().c_str()) .def(pybind11::init()) - .def_property_readonly("H", &ParallelPlateGeometry::getH) - .def_property_readonly("V", &ParallelPlateGeometry::getVelocity) + .def_property_readonly("separation", &ParallelPlateGeometry::getSeparation) + .def_property_readonly("speed", &ParallelPlateGeometry::getSpeed) .def_property_readonly("no_slip", &ParallelPlateGeometry::getNoSlip); } diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py index 487c84dfe0..f524e35653 100644 --- a/hoomd/mpcd/fill.py +++ b/hoomd/mpcd/fill.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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. @@ -110,7 +110,7 @@ class GeometryFiller(VirtualParticleFiller): .. code-block:: python - plates = hoomd.mpcd.geometry.ParallelPlates(H=3.0) + plates = hoomd.mpcd.geometry.ParallelPlates(separation=6.0) filler = hoomd.mpcd.fill.GeometryFiller( type="A", density=5.0, diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py index 073bd2bba9..cc8b767911 100644 --- a/hoomd/mpcd/geometry.py +++ b/hoomd/mpcd/geometry.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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. @@ -60,17 +60,17 @@ class ParallelPlates(Geometry): r"""Parallel-plate channel. Args: - H (float): Channel half-width. - V (float): Wall speed. + 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`, so the total channel width is :math:`2H`. The plates 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. + `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: @@ -78,7 +78,7 @@ class ParallelPlates(Geometry): .. code-block:: python - plates = hoomd.mpcd.geometry.ParallelPlates(H=3.0) + plates = hoomd.mpcd.geometry.ParallelPlates(separation=6.0) stream = hoomd.mpcd.stream.BounceBack(period=1, geometry=plates) simulation.operations.integrator.streaming_method = stream @@ -86,7 +86,8 @@ class ParallelPlates(Geometry): .. code-block:: python - plates = hoomd.mpcd.geometry.ParallelPlates(H=3.0, no_slip=False) + 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 @@ -94,30 +95,32 @@ class ParallelPlates(Geometry): .. code-block:: python - plates = hoomd.mpcd.geometry.ParallelPlates(H=3.0, V=1.0, no_slip=True) + 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: - H (float): Channel half-width (*read only*). + separation (float): Distance between plates (*read only*). - V (float): Wall speed (*read only*). + speed (float): Wall speed (*read only*). - `V` will have no effect if `no_slip` is False because the slip + `speed` will have no effect if `no_slip` is False because the slip surface cannot generate shear stress. """ - def __init__(self, H, V=0.0, no_slip=True): + def __init__(self, separation, speed=0.0, no_slip=True): super().__init__(no_slip) param_dict = ParameterDict( - H=float(H), - V=float(V), + separation=float(separation), + speed=float(speed), ) self._param_dict.update(param_dict) def _attach_hook(self): - self._cpp_obj = _mpcd.ParallelPlates(self.H, self.V, self.no_slip) + self._cpp_obj = _mpcd.ParallelPlates(self.separation, self.speed, + self.no_slip) super()._attach_hook() diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index b1f7246128..13e71b78ec 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -126,7 +126,7 @@ class Integrator(_MDIntegrator): .. code-block:: python - plates = hoomd.mpcd.geometry.ParallelPlates(H=3.0) + plates = hoomd.mpcd.geometry.ParallelPlates(separation=6.0) stream = hoomd.mpcd.stream.BounceBack(period=1, geometry=plates) collide = hoomd.mpcd.collide.StochasticRotationDynamics( period=1, diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py index 9343d2ab0f..5ba09cc795 100644 --- a/hoomd/mpcd/methods.py +++ b/hoomd/mpcd/methods.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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. @@ -68,7 +68,7 @@ class BounceBack(Method): .. code-block:: python - plates = hoomd.mpcd.geometry.ParallelPlates(H=3.0) + 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) diff --git a/hoomd/mpcd/pytest/test_fill.py b/hoomd/mpcd/pytest/test_fill.py index cc28fbd37e..5ecf38a7ff 100644 --- a/hoomd/mpcd/pytest/test_fill.py +++ b/hoomd/mpcd/pytest/test_fill.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. import pytest @@ -23,7 +23,7 @@ def snap(): "cls, init_args", [ (hoomd.mpcd.geometry.ParallelPlates, { - "H": 4.0 + "separation": 8.0 }), (hoomd.mpcd.geometry.PlanarPore, { "H": 4.0, diff --git a/hoomd/mpcd/pytest/test_geometry.py b/hoomd/mpcd/pytest/test_geometry.py index 5c23514079..dd9cc0d403 100644 --- a/hoomd/mpcd/pytest/test_geometry.py +++ b/hoomd/mpcd/pytest/test_geometry.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. import pytest @@ -22,31 +22,33 @@ def snap(): class TestParallelPlates: def test_default_init(self, simulation_factory, snap): - geom = hoomd.mpcd.geometry.ParallelPlates(H=4.0) - assert geom.H == 4.0 - assert geom.V == 0.0 + 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.H == 4.0 - assert geom.V == 0.0 + 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(H=5.0, V=1.0, no_slip=False) - assert geom.H == 5.0 - assert geom.V == 1.0 + 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.H == 5.0 - assert geom.V == 1.0 + 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(H=4.0) + geom = hoomd.mpcd.geometry.ParallelPlates(separation=8.0) pickling_check(geom) sim = simulation_factory(snap) diff --git a/hoomd/mpcd/pytest/test_integrator.py b/hoomd/mpcd/pytest/test_integrator.py index 0d9cc15a29..a86a2ead91 100644 --- a/hoomd/mpcd/pytest/test_integrator.py +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. import pytest @@ -85,7 +85,7 @@ def test_streaming_method(make_simulation): def test_virtual_particle_fillers(make_simulation): sim = make_simulation() - geom = hoomd.mpcd.geometry.ParallelPlates(H=4.0) + geom = hoomd.mpcd.geometry.ParallelPlates(separation=8.0) filler = hoomd.mpcd.fill.GeometryFiller( type="A", density=5.0, diff --git a/hoomd/mpcd/pytest/test_methods.py b/hoomd/mpcd/pytest/test_methods.py index 35e676a487..1cf4373f8b 100644 --- a/hoomd/mpcd/pytest/test_methods.py +++ b/hoomd/mpcd/pytest/test_methods.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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 @@ -25,7 +25,7 @@ def snap(): def integrator(): bb = hoomd.mpcd.methods.BounceBack( filter=hoomd.filter.All(), - geometry=hoomd.mpcd.geometry.ParallelPlates(H=4)) + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=8.0)) ig = hoomd.mpcd.Integrator(dt=0.1, methods=[bb]) return ig @@ -116,7 +116,7 @@ def test_step_slip(self, simulation_factory, snap, integrator): def test_step_moving_wall(self, simulation_factory, snap, integrator): integrator.dt = 0.3 - integrator.methods[0].geometry.V = 1.0 + integrator.methods[0].geometry.speed = 1.0 if snap.communicator.rank == 0: snap.particles.velocity[1] = [-2.0, -1.0, -1.0] @@ -156,7 +156,7 @@ def test_accel(self, simulation_factory, snap, integrator): def test_check_particles(self, simulation_factory, snap, integrator, H, expected_result): """Test box validation raises an error on run.""" - integrator.methods[0].geometry.H = H + integrator.methods[0].geometry.separation = 2 * H sim = simulation_factory(snap) sim.operations.integrator = integrator @@ -168,7 +168,7 @@ 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(H=4)) + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=8.0)) integrator = hoomd.md.Integrator(dt=0.1, methods=[bb]) sim = simulation_factory(snap) diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index 57e4c1e22f..a65b019669 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -29,7 +29,7 @@ def snap(): { "geometry": hoomd.mpcd.geometry.ParallelPlates( - H=4.0, V=0.0, no_slip=True), + separation=8.0, speed=0.0, no_slip=True), }, ), ( @@ -192,7 +192,8 @@ def test_step_noslip(self, simulation_factory, snap): 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(H=4)) + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=8.0)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -232,7 +233,8 @@ def test_step_slip(self, simulation_factory, snap): sim = simulation_factory(snap) sm = hoomd.mpcd.stream.BounceBack( period=1, - geometry=hoomd.mpcd.geometry.ParallelPlates(H=4, no_slip=False)) + 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 @@ -280,7 +282,9 @@ def test_step_moving_wall(self, simulation_factory, snap): sim = simulation_factory(snap) sm = hoomd.mpcd.stream.BounceBack( period=1, - geometry=hoomd.mpcd.geometry.ParallelPlates(H=4, V=1, no_slip=True), + 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 @@ -301,7 +305,8 @@ def test_check_solvent_particles(self, simulation_factory, snap, H, snap.mpcd.position[0] = [0, 3.85, 0] sim = simulation_factory(snap) sm = hoomd.mpcd.stream.BounceBack( - period=1, geometry=hoomd.mpcd.geometry.ParallelPlates(H=H)) + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=2 * H)) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index cc42396d27..b9c03d4a62 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -198,7 +198,7 @@ class BounceBack(StreamingMethod): stream = hoomd.mpcd.stream.BounceBack( period=1, geometry=hoomd.mpcd.geometry.ParallelPlates( - H=3.0, V=1.0, no_slip=True)) + separation=6.0, speed=1.0, no_slip=True)) simulation.operations.integrator.streaming_method = stream Pressure driven flow between parallel plates. @@ -207,7 +207,8 @@ class BounceBack(StreamingMethod): stream = hoomd.mpcd.stream.BounceBack( period=1, - geometry=hoomd.mpcd.geometry.ParallelPlates(H=3.0, no_slip=True), + geometry=hoomd.mpcd.geometry.ParallelPlates( + separation=6.0, no_slip=True), solvent_force=hoomd.mpcd.force.ConstantForce((1, 0, 0))) simulation.operations.integrator.streaming_method = stream From 595462c3f99d9ef3f8f5e734592d276c10fa309d Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 8 Apr 2024 22:43:45 -0500 Subject: [PATCH 54/69] Use more descriptive variables for PlanarPore --- hoomd/mpcd/PlanarPoreGeometry.h | 26 ++++++++++++++++++++++---- hoomd/mpcd/StreamingGeometry.cc | 4 ++-- hoomd/mpcd/geometry.py | 21 +++++++++++---------- hoomd/mpcd/pytest/test_fill.py | 4 ++-- hoomd/mpcd/pytest/test_geometry.py | 24 +++++++++++++----------- hoomd/mpcd/pytest/test_stream.py | 20 ++++++++++++++------ 6 files changed, 64 insertions(+), 35 deletions(-) diff --git a/hoomd/mpcd/PlanarPoreGeometry.h b/hoomd/mpcd/PlanarPoreGeometry.h index 16d4fe11b8..c0a821d196 100644 --- a/hoomd/mpcd/PlanarPoreGeometry.h +++ b/hoomd/mpcd/PlanarPoreGeometry.h @@ -45,12 +45,12 @@ class __attribute__((visibility("default"))) PlanarPoreGeometry public: //! Constructor /*! - * \param H Channel half-width - * \param L Pore half-length + * \param separation Distance between pore walls + * \param length Pore length * \param no_slip Boundary condition at the wall (slip or no-slip) */ - HOSTDEVICE PlanarPoreGeometry(Scalar H, Scalar L, bool no_slip) - : m_H(H), m_L(L), m_no_slip(no_slip) + 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) { } @@ -177,6 +177,15 @@ class __attribute__((visibility("default"))) PlanarPoreGeometry return m_H; } + //! Get distance between pore walls + /*! + * \returns Distance between pore walls + */ + HOSTDEVICE Scalar getSeparation() const + { + return Scalar(2.0) * m_H; + } + //! Get pore half length /*! * \returns Pore half length @@ -186,6 +195,15 @@ class __attribute__((visibility("default"))) PlanarPoreGeometry 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 diff --git a/hoomd/mpcd/StreamingGeometry.cc b/hoomd/mpcd/StreamingGeometry.cc index 07a21de2d0..12f2de22ac 100644 --- a/hoomd/mpcd/StreamingGeometry.cc +++ b/hoomd/mpcd/StreamingGeometry.cc @@ -32,8 +32,8 @@ void export_PlanarPoreGeometry(pybind11::module& m) m, PlanarPoreGeometry::getName().c_str()) .def(pybind11::init()) - .def_property_readonly("H", &PlanarPoreGeometry::getH) - .def_property_readonly("L", &PlanarPoreGeometry::getL) + .def_property_readonly("separation", &PlanarPoreGeometry::getSeparation) + .def_property_readonly("length", &PlanarPoreGeometry::getLength) .def_property_readonly("no_slip", &PlanarPoreGeometry::getNoSlip); } diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py index cc8b767911..5ff12d30b8 100644 --- a/hoomd/mpcd/geometry.py +++ b/hoomd/mpcd/geometry.py @@ -128,14 +128,14 @@ class PlanarPore(Geometry): r"""Pore with parallel plate opening. Args: - H (float): Pore half-width. - L (float): Pore half-length. + 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 + :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 @@ -145,26 +145,27 @@ class PlanarPore(Geometry): .. code-block:: python - pore = hoomd.mpcd.geometry.PlanarPore(H=3.0, L=2.0) + 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: - H (float): Pore half-width (*read only*). + separation (float): Distance between pore walls (*read only*). - L (float): Pore half-length (*read only*). + length (float): Pore length (*read only*). """ - def __init__(self, H, L, no_slip=True): + def __init__(self, separation, length, no_slip=True): super().__init__(no_slip) param_dict = ParameterDict( - H=float(H), - L=float(L), + separation=float(separation), + length=float(length), ) self._param_dict.update(param_dict) def _attach_hook(self): - self._cpp_obj = _mpcd.PlanarPore(self.H, self.L, self.no_slip) + self._cpp_obj = _mpcd.PlanarPore(self.separation, self.length, + self.no_slip) super()._attach_hook() diff --git a/hoomd/mpcd/pytest/test_fill.py b/hoomd/mpcd/pytest/test_fill.py index 5ecf38a7ff..c968f34a70 100644 --- a/hoomd/mpcd/pytest/test_fill.py +++ b/hoomd/mpcd/pytest/test_fill.py @@ -26,8 +26,8 @@ def snap(): "separation": 8.0 }), (hoomd.mpcd.geometry.PlanarPore, { - "H": 4.0, - "L": 5.0 + "separation": 8.0, + "length": 10.0 }), ], ids=["ParallelPlates", "PlanarPore"], diff --git a/hoomd/mpcd/pytest/test_geometry.py b/hoomd/mpcd/pytest/test_geometry.py index dd9cc0d403..4982def9a8 100644 --- a/hoomd/mpcd/pytest/test_geometry.py +++ b/hoomd/mpcd/pytest/test_geometry.py @@ -59,31 +59,33 @@ def test_pickling(self, simulation_factory, snap): class TestPlanarPore: def test_default_init(self, simulation_factory, snap): - geom = hoomd.mpcd.geometry.PlanarPore(H=4.0, L=5.0) - assert geom.H == 4.0 - assert geom.L == 5.0 + 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.H == 4.0 - assert geom.L == 5.0 + 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(H=5.0, L=4.0, no_slip=False) - assert geom.H == 5.0 - assert geom.L == 4.0 + 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.H == 5.0 - assert geom.L == 4.0 + 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(H=4.0, L=5.0) + geom = hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=10.0) pickling_check(geom) sim = simulation_factory(snap) diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index a65b019669..c526bf4ce4 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -36,7 +36,8 @@ def snap(): hoomd.mpcd.stream.BounceBack, { "geometry": - hoomd.mpcd.geometry.PlanarPore(H=4.0, L=3.0, no_slip=True) + hoomd.mpcd.geometry.PlanarPore( + separation=8.0, length=6.0, no_slip=True) }, ), ], @@ -346,7 +347,9 @@ def test_step_noslip(self, simulation_factory, snap): sim = simulation_factory(snap) sm = hoomd.mpcd.stream.BounceBack( period=1, - geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=3, no_slip=True)) + 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 @@ -417,7 +420,9 @@ def test_step_slip(self, simulation_factory, snap): sim = simulation_factory(snap) sm = hoomd.mpcd.stream.BounceBack( period=1, - geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=3, no_slip=False)) + 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 @@ -491,15 +496,18 @@ def test_check_solvent_particles(self, simulation_factory, snap): sim.operations.integrator = ig ig.streaming_method = hoomd.mpcd.stream.BounceBack( - period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=3)) + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=6.0)) sim.run(0) assert ig.streaming_method.check_solvent_particles() ig.streaming_method = hoomd.mpcd.stream.BounceBack( - period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=3.8, L=3)) + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(separation=7.6, length=6.0)) sim.run(0) assert not ig.streaming_method.check_solvent_particles() ig.streaming_method = hoomd.mpcd.stream.BounceBack( - period=1, geometry=hoomd.mpcd.geometry.PlanarPore(H=4, L=3.5)) + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=7.0)) assert not ig.streaming_method.check_solvent_particles() From 834032186fc3a464447097467b7554dfc2de0ca9 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 8 Apr 2024 22:52:09 -0500 Subject: [PATCH 55/69] Warn for unset seed --- hoomd/mpcd/collide.py | 6 ++++++ hoomd/mpcd/fill.py | 1 + 2 files changed, 7 insertions(+) diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 14f23145aa..413a7b3578 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -81,6 +81,8 @@ def __init__(self, cell_size, shift=True): def _attach_hook(self): sim = self._simulation + sim._warn_if_seed_unset() + if isinstance(sim.device, hoomd.device.GPU): cpp_class = _mpcd.CellListGPU else: @@ -216,6 +218,8 @@ def __init__(self, period, kT, embedded_particles=None): def _attach_hook(self): sim = self._simulation + sim._warn_if_seed_unset() + if isinstance(sim.device, hoomd.device.GPU): cpp_class = _mpcd.ATCollisionMethodGPU else: @@ -334,6 +338,8 @@ def __init__(self, period, angle, kT=None, embedded_particles=None): def _attach_hook(self): sim = self._simulation + sim._warn_if_seed_unset() + if isinstance(sim.device, hoomd.device.GPU): cpp_class = _mpcd.SRDCollisionMethodGPU else: diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py index f524e35653..70b862005a 100644 --- a/hoomd/mpcd/fill.py +++ b/hoomd/mpcd/fill.py @@ -135,6 +135,7 @@ def __init__(self, type, density, kT, geometry): def _attach_hook(self): sim = self._simulation + sim._warn_if_seed_unset() self.geometry._attach(sim) From 7ef80f05ae9a2aa2ca40b0ad6570b2204bdbbcfa Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 8 Apr 2024 22:57:35 -0500 Subject: [PATCH 56/69] Update compiled unit tests --- hoomd/mpcd/test/force_test.cc | 4 ++-- hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc | 2 +- hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc | 2 +- hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc | 2 +- hoomd/mpcd/test/planar_pore_geometry_filler_test.cc | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hoomd/mpcd/test/force_test.cc b/hoomd/mpcd/test/force_test.cc index c185956b17..4c98fc9645 100644 --- a/hoomd/mpcd/test/force_test.cc +++ b/hoomd/mpcd/test/force_test.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. #include @@ -33,7 +33,7 @@ void test_force(std::shared_ptr force, //! Test block force UP_TEST(block_force) { - auto force = std::make_shared(2, 3, 0.2); + 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), diff --git a/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc b/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc index 7a7d7fd668..06fea35d73 100644 --- a/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc +++ b/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc @@ -40,7 +40,7 @@ void parallel_plate_fill_mpi_test(std::shared_ptr exec_c UP_ASSERT_EQUAL(pdata->getNVirtualGlobal(), 0); // create slit channel with half width 5 - auto slit = std::make_shared(5.0, 0.0, true); + 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, "B", 2.0, kT, slit); diff --git a/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc index 4cac195c3a..6753de682d 100644 --- a/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc +++ b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc @@ -31,7 +31,7 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec UP_ASSERT_EQUAL(pdata->getNVirtual(), 0); // create slit channel with half width 5 - auto slit = std::make_shared(5.0, 1.0, true); + 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, "B", 2.0, kT, slit); diff --git a/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc b/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc index c230f0d6b7..3e22e297a8 100644 --- a/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc +++ b/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc @@ -39,7 +39,7 @@ template void planar_pore_fill_mpi_test(std::shared_ptrgetNVirtualGlobal(), 0); // create slit channel with half width 5 and half length 8 - auto slit = std::make_shared(5.0, 8.0, true); + 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, "A", 2.0, kT, slit); diff --git a/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc b/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc index d75d41a692..bb3b9a6aa9 100644 --- a/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc +++ b/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc @@ -31,7 +31,7 @@ void planar_pore_fill_basic_test(std::shared_ptr exec_co 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, true); + 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 From ae437d06f2b08f92dbe44249c64f7af22886835a Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 8 Apr 2024 22:57:49 -0500 Subject: [PATCH 57/69] Use Scalar on constants --- hoomd/mpcd/BlockForce.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hoomd/mpcd/BlockForce.h b/hoomd/mpcd/BlockForce.h index 88ea77ea17..17532521f4 100644 --- a/hoomd/mpcd/BlockForce.h +++ b/hoomd/mpcd/BlockForce.h @@ -57,8 +57,8 @@ class __attribute__((visibility("default"))) BlockForce */ HOSTDEVICE BlockForce(Scalar F, Scalar separation, Scalar width) : m_F(F) { - m_H_plus_w = 0.5 * (separation + width); - m_H_minus_w = 0.5 * (separation - width); + m_H_plus_w = Scalar(0.5) * (separation + width); + m_H_minus_w = Scalar(0.5) * (separation - width); } //! Force evaluation method @@ -96,8 +96,8 @@ class __attribute__((visibility("default"))) BlockForce void setSeparation(Scalar H) { const Scalar w = getWidth(); - m_H_plus_w = 0.5 * (H + w); - m_H_minus_w = 0.5 * (H - w); + m_H_plus_w = Scalar(0.5) * (H + w); + m_H_minus_w = Scalar(0.5) * (H - w); } //! Get the block width @@ -110,8 +110,8 @@ class __attribute__((visibility("default"))) BlockForce void setWidth(Scalar w) { const Scalar H = getSeparation(); - m_H_plus_w = 0.5 * (H + w); - m_H_minus_w = 0.5 * (H - w); + m_H_plus_w = Scalar(0.5) * (H + w); + m_H_minus_w = Scalar(0.5) * (H - w); } #ifndef __HIPCC__ From b885608f95c441ddd6c00253d2055e428c7eea29 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 8 Apr 2024 23:06:28 -0500 Subject: [PATCH 58/69] Make parallel plate filler test more stringent --- .../test/parallel_plate_geometry_filler_test.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc index 6753de682d..42b687c618 100644 --- a/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc +++ b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc @@ -69,9 +69,9 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec UP_ASSERT_EQUAL(__scalar_as_int(h_pos.data[i].w), 1); const Scalar y = h_pos.data[i].y; - if (y < Scalar(-5.0)) + if (y >= Scalar(-7.0) && y < Scalar(-5.0)) ++N_lo; - else if (y >= 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,9 +98,9 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec UP_ASSERT_EQUAL(h_tag.data[i], i); const Scalar y = h_pos.data[i].y; - if (y < Scalar(-5.0)) + if (y >= Scalar(-7.0) && y < Scalar(-5.0)) ++N_lo; - else if (y >= 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,9 +122,9 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec for (unsigned int i = pdata->getN(); i < pdata->getN() + pdata->getNVirtual(); ++i) { const Scalar y = h_pos.data[i].y; - if (y < Scalar(-5.0)) + if (y >= Scalar(-5.5) && y < Scalar(-5.0)) ++N_lo; - else if (y >= Scalar(5.0)) + else if (y >= Scalar(5.0) && y < Scalar(5.5)) ++N_hi; } UP_ASSERT_EQUAL(N_lo, 2 * (20 * 20 / 2)); @@ -154,13 +154,13 @@ void parallel_plate_fill_basic_test(std::shared_ptr exec 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 (y < 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 (y >= 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)); From f208b1ff7c86d99a26ac4f169a1474b94fc5558a Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 8 Apr 2024 23:19:16 -0500 Subject: [PATCH 59/69] Fix license headers with pre-commit --- hoomd/mpcd/BounceBackStreamingMethod.cc.inc | 2 +- hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc | 2 +- hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc | 2 +- hoomd/mpcd/BulkStreamingMethod.cc.inc | 2 +- hoomd/mpcd/BulkStreamingMethod.h | 2 +- hoomd/mpcd/BulkStreamingMethodGPU.cc.inc | 2 +- hoomd/mpcd/BulkStreamingMethodGPU.cu.inc | 2 +- hoomd/mpcd/BulkStreamingMethodGPU.h | 2 +- hoomd/mpcd/ConstantForce.cc | 2 +- hoomd/mpcd/ConstantForce.h | 2 +- hoomd/mpcd/ManualVirtualParticleFiller.cc | 2 +- hoomd/mpcd/ManualVirtualParticleFiller.h | 2 +- hoomd/mpcd/NoForce.cc | 2 +- hoomd/mpcd/NoForce.h | 2 +- hoomd/mpcd/ParallelPlateGeometryFiller.h | 2 +- hoomd/mpcd/ParallelPlateGeometryFillerGPU.h | 2 +- hoomd/mpcd/SineForce.cc | 2 +- hoomd/mpcd/SineForce.h | 2 +- hoomd/mpcd/tune.py | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/hoomd/mpcd/BounceBackStreamingMethod.cc.inc b/hoomd/mpcd/BounceBackStreamingMethod.cc.inc index e9f7c49b3a..4fa6f83200 100644 --- a/hoomd/mpcd/BounceBackStreamingMethod.cc.inc +++ b/hoomd/mpcd/BounceBackStreamingMethod.cc.inc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc b/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc index 8b559e6db3..874b3c787f 100644 --- a/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc b/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc index 2b561ad87b..a762f1f95e 100644 --- a/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/BulkStreamingMethod.cc.inc b/hoomd/mpcd/BulkStreamingMethod.cc.inc index ded2b404ff..3cb773ca0a 100644 --- a/hoomd/mpcd/BulkStreamingMethod.cc.inc +++ b/hoomd/mpcd/BulkStreamingMethod.cc.inc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/BulkStreamingMethod.h b/hoomd/mpcd/BulkStreamingMethod.h index a9495e4424..491b5ffc1b 100644 --- a/hoomd/mpcd/BulkStreamingMethod.h +++ b/hoomd/mpcd/BulkStreamingMethod.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc b/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc index d051fe82bc..05aac04603 100644 --- a/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc b/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc index 03e2d2b530..26e1edb739 100644 --- a/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.h b/hoomd/mpcd/BulkStreamingMethodGPU.h index 571bd4e6a3..de8e00bb8f 100644 --- a/hoomd/mpcd/BulkStreamingMethodGPU.h +++ b/hoomd/mpcd/BulkStreamingMethodGPU.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/ConstantForce.cc b/hoomd/mpcd/ConstantForce.cc index b62254d71c..3c4de8f6ac 100644 --- a/hoomd/mpcd/ConstantForce.cc +++ b/hoomd/mpcd/ConstantForce.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/ConstantForce.h b/hoomd/mpcd/ConstantForce.h index b35d30c32a..1b922f3712 100644 --- a/hoomd/mpcd/ConstantForce.h +++ b/hoomd/mpcd/ConstantForce.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/ManualVirtualParticleFiller.cc b/hoomd/mpcd/ManualVirtualParticleFiller.cc index a3a9cfa4c7..0e08852f8d 100644 --- a/hoomd/mpcd/ManualVirtualParticleFiller.cc +++ b/hoomd/mpcd/ManualVirtualParticleFiller.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/ManualVirtualParticleFiller.h b/hoomd/mpcd/ManualVirtualParticleFiller.h index 1af2501626..129d6f7d43 100644 --- a/hoomd/mpcd/ManualVirtualParticleFiller.h +++ b/hoomd/mpcd/ManualVirtualParticleFiller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/NoForce.cc b/hoomd/mpcd/NoForce.cc index 78ba69c986..3c6e45bf9d 100644 --- a/hoomd/mpcd/NoForce.cc +++ b/hoomd/mpcd/NoForce.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/NoForce.h b/hoomd/mpcd/NoForce.h index a98024d00d..da56bc7f35 100644 --- a/hoomd/mpcd/NoForce.h +++ b/hoomd/mpcd/NoForce.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/ParallelPlateGeometryFiller.h b/hoomd/mpcd/ParallelPlateGeometryFiller.h index 4105e1f7ee..33de1cf898 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFiller.h +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h index befef60ac3..56b095e266 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/SineForce.cc b/hoomd/mpcd/SineForce.cc index b172f84ddd..9be2631d49 100644 --- a/hoomd/mpcd/SineForce.cc +++ b/hoomd/mpcd/SineForce.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/SineForce.h b/hoomd/mpcd/SineForce.h index 6f21ea7e98..8407b0d7d6 100644 --- a/hoomd/mpcd/SineForce.h +++ b/hoomd/mpcd/SineForce.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! diff --git a/hoomd/mpcd/tune.py b/hoomd/mpcd/tune.py index 8861b0de98..f6b7eb8ceb 100644 --- a/hoomd/mpcd/tune.py +++ b/hoomd/mpcd/tune.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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. From 3ab15a3ced576c62795037c833731783671fa413 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 8 Apr 2024 23:23:08 -0500 Subject: [PATCH 60/69] Fix additional license headers with pre-commit --- hoomd/mpcd/pytest/test_collide.py | 2 +- hoomd/mpcd/pytest/test_tune.py | 2 +- sphinx-doc/module-mpcd-collide.rst | 2 +- sphinx-doc/module-mpcd-fill.rst | 2 +- sphinx-doc/module-mpcd-force.rst | 2 +- sphinx-doc/module-mpcd-geometry.rst | 2 +- sphinx-doc/module-mpcd-methods.rst | 2 +- sphinx-doc/module-mpcd-stream.rst | 2 +- sphinx-doc/module-mpcd-tune.rst | 2 +- sphinx-doc/package-mpcd.rst | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/hoomd/mpcd/pytest/test_collide.py b/hoomd/mpcd/pytest/test_collide.py index 62b1929fcc..0db7c21266 100644 --- a/hoomd/mpcd/pytest/test_collide.py +++ b/hoomd/mpcd/pytest/test_collide.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. import pytest diff --git a/hoomd/mpcd/pytest/test_tune.py b/hoomd/mpcd/pytest/test_tune.py index 4be4bef256..f8e9c2bea7 100644 --- a/hoomd/mpcd/pytest/test_tune.py +++ b/hoomd/mpcd/pytest/test_tune.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. import pytest diff --git a/sphinx-doc/module-mpcd-collide.rst b/sphinx-doc/module-mpcd-collide.rst index db4991f2be..5f603a9c6c 100644 --- a/sphinx-doc/module-mpcd-collide.rst +++ b/sphinx-doc/module-mpcd-collide.rst @@ -1,4 +1,4 @@ -.. Copyright (c) 2009-2023 The Regents of the University of Michigan. +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. .. Part of HOOMD-blue, released under the BSD 3-Clause License. mpcd.collide diff --git a/sphinx-doc/module-mpcd-fill.rst b/sphinx-doc/module-mpcd-fill.rst index 4f334ccf52..66ad40a738 100644 --- a/sphinx-doc/module-mpcd-fill.rst +++ b/sphinx-doc/module-mpcd-fill.rst @@ -1,4 +1,4 @@ -.. Copyright (c) 2009-2023 The Regents of the University of Michigan. +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. .. Part of HOOMD-blue, released under the BSD 3-Clause License. mpcd.fill diff --git a/sphinx-doc/module-mpcd-force.rst b/sphinx-doc/module-mpcd-force.rst index a929ec61f8..1dc14c6c65 100644 --- a/sphinx-doc/module-mpcd-force.rst +++ b/sphinx-doc/module-mpcd-force.rst @@ -1,4 +1,4 @@ -.. Copyright (c) 2009-2023 The Regents of the University of Michigan. +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. .. Part of HOOMD-blue, released under the BSD 3-Clause License. mpcd.force diff --git a/sphinx-doc/module-mpcd-geometry.rst b/sphinx-doc/module-mpcd-geometry.rst index c76c9bde52..a188706292 100644 --- a/sphinx-doc/module-mpcd-geometry.rst +++ b/sphinx-doc/module-mpcd-geometry.rst @@ -1,4 +1,4 @@ -.. Copyright (c) 2009-2023 The Regents of the University of Michigan. +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. .. Part of HOOMD-blue, released under the BSD 3-Clause License. mpcd.geometry diff --git a/sphinx-doc/module-mpcd-methods.rst b/sphinx-doc/module-mpcd-methods.rst index 1850e8a03f..7aa17b0f42 100644 --- a/sphinx-doc/module-mpcd-methods.rst +++ b/sphinx-doc/module-mpcd-methods.rst @@ -1,4 +1,4 @@ -.. Copyright (c) 2009-2023 The Regents of the University of Michigan. +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. .. Part of HOOMD-blue, released under the BSD 3-Clause License. mpcd.methods diff --git a/sphinx-doc/module-mpcd-stream.rst b/sphinx-doc/module-mpcd-stream.rst index 6f6d9e87c2..91f4d62e6b 100644 --- a/sphinx-doc/module-mpcd-stream.rst +++ b/sphinx-doc/module-mpcd-stream.rst @@ -1,4 +1,4 @@ -.. Copyright (c) 2009-2023 The Regents of the University of Michigan. +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. .. Part of HOOMD-blue, released under the BSD 3-Clause License. mpcd.stream diff --git a/sphinx-doc/module-mpcd-tune.rst b/sphinx-doc/module-mpcd-tune.rst index f90246dc42..fd735043a7 100644 --- a/sphinx-doc/module-mpcd-tune.rst +++ b/sphinx-doc/module-mpcd-tune.rst @@ -1,4 +1,4 @@ -.. Copyright (c) 2009-2023 The Regents of the University of Michigan. +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. .. Part of HOOMD-blue, released under the BSD 3-Clause License. mpcd.tune diff --git a/sphinx-doc/package-mpcd.rst b/sphinx-doc/package-mpcd.rst index c55658e807..beb6fdff26 100644 --- a/sphinx-doc/package-mpcd.rst +++ b/sphinx-doc/package-mpcd.rst @@ -1,4 +1,4 @@ -.. Copyright (c) 2009-2023 The Regents of the University of Michigan. +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. .. Part of HOOMD-blue, released under the BSD 3-Clause License. hoomd.mpcd From 4306d7a6be469148c6be2618b4a94c0cab386f59 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 11 Apr 2024 08:57:12 -0500 Subject: [PATCH 61/69] Prefer MPCD particles to solvent in API and docs --- hoomd/mpcd/BounceBackStreamingMethod.h | 5 +- hoomd/mpcd/BulkStreamingMethod.h | 2 +- hoomd/mpcd/Integrator.cc | 4 +- hoomd/mpcd/__init__.py | 26 +++---- hoomd/mpcd/collide.py | 14 ++-- hoomd/mpcd/fill.py | 8 +-- hoomd/mpcd/force.py | 34 +++++----- hoomd/mpcd/integrate.py | 47 +++++++------ hoomd/mpcd/methods.py | 6 +- hoomd/mpcd/pytest/test_integrator.py | 28 ++++---- hoomd/mpcd/pytest/test_stream.py | 40 ++++++----- hoomd/mpcd/pytest/test_tune.py | 6 +- hoomd/mpcd/stream.py | 94 +++++++++++++------------- hoomd/mpcd/tune.py | 6 +- 14 files changed, 166 insertions(+), 154 deletions(-) diff --git a/hoomd/mpcd/BounceBackStreamingMethod.h b/hoomd/mpcd/BounceBackStreamingMethod.h index 017fb83640..a589e61322 100644 --- a/hoomd/mpcd/BounceBackStreamingMethod.h +++ b/hoomd/mpcd/BounceBackStreamingMethod.h @@ -222,10 +222,9 @@ template void export_BounceBackStreamingMethod(pybi std::shared_ptr>()) .def_property_readonly("geometry", &mpcd::BounceBackStreamingMethod::getGeometry) - .def_property_readonly("solvent_force", + .def_property_readonly("mpcd_particle_force", &mpcd::BounceBackStreamingMethod::getForce) - .def("check_solvent_particles", - &BounceBackStreamingMethod::checkParticles); + .def("check_mpcd_particles", &BounceBackStreamingMethod::checkParticles); } } // end namespace detail } // end namespace mpcd diff --git a/hoomd/mpcd/BulkStreamingMethod.h b/hoomd/mpcd/BulkStreamingMethod.h index 491b5ffc1b..2019d24daf 100644 --- a/hoomd/mpcd/BulkStreamingMethod.h +++ b/hoomd/mpcd/BulkStreamingMethod.h @@ -60,7 +60,7 @@ template void export_BulkStreamingMethod(pybind11::module& m) unsigned int, int, std::shared_ptr>()) - .def_property_readonly("solvent_force", &mpcd::BulkStreamingMethod::getForce); + .def_property_readonly("mpcd_particle_force", &mpcd::BulkStreamingMethod::getForce); } } // end namespace detail diff --git a/hoomd/mpcd/Integrator.cc b/hoomd/mpcd/Integrator.cc index 7a4f17cc93..a579b8acee 100644 --- a/hoomd/mpcd/Integrator.cc +++ b/hoomd/mpcd/Integrator.cc @@ -183,7 +183,9 @@ void mpcd::detail::export_Integrator(pybind11::module& m) .def_property("streaming_method", &mpcd::Integrator::getStreamingMethod, &mpcd::Integrator::setStreamingMethod) - .def_property("solvent_sorter", &mpcd::Integrator::getSorter, &mpcd::Integrator::setSorter) + .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/__init__.py b/hoomd/mpcd/__init__.py index 2517a7ea83..c46a133ffb 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -14,13 +14,13 @@ .. 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. 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 solvent properties, determine the transport +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 @@ -29,14 +29,14 @@ * :class:`~hoomd.mpcd.collide.StochasticRotationDynamics` * :class:`~hoomd.mpcd.collide.AndersenThermostat` -Solute particles can be coupled to the solvent 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 -solvent introduces both hydrodynamic interactions and a heat bath that acts as -a thermostat. +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 solvent can additionally be coupled to solid boundaries (with no-slip or -slip boundary conditions) during the streaming step. +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) `_. @@ -47,12 +47,12 @@ MPCD is intended to be used as an add-on to the standard MD methods in `hoomd.md`. Getting started can look like: -1. Initialize the solvent through `Snapshot.mpcd`. You can include any solute - particles in the snapshot as well. +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 +4. Choose the collision rule from :mod:`.mpcd.collide`. Couple the solute to the collision step. 5. Run your simulation! diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 413a7b3578..32fc2c5662 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -8,8 +8,8 @@ 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 and solvent -properties determine the transport coefficients. +hydrodynamic interactions, and the choice of collision rule determines the +transport coefficients. .. invisible-code-block: python @@ -149,7 +149,7 @@ class AndersenThermostat(CollisionMethod): Args: period (int): Number of integration steps between collisions. - kT (hoomd.variant.variant_like): Temperature of the solvent + kT (hoomd.variant.variant_like): Temperature of the thermostat :math:`[\mathrm{energy}]`. embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to include in collision. @@ -167,7 +167,7 @@ class AndersenThermostat(CollisionMethod): .. rubric:: Examples: - Solvent collision. + Collision for only MPCD particles. .. code-block:: python @@ -187,7 +187,7 @@ class AndersenThermostat(CollisionMethod): simulation.operations.integrator.collision_method = andersen_thermostat Attributes: - kT (hoomd.variant.variant_like): Temperature of the solvent + kT (hoomd.variant.variant_like): Temperature of the thermostat :math:`[\mathrm{energy}]`. This temperature determines the distribution used to generate the @@ -268,14 +268,14 @@ class StochasticRotationDynamics(CollisionMethod): .. rubric:: Examples: - Standard solvent collision. + Collision of MPCD particles. .. code-block:: python srd = hoomd.mpcd.collide.StochasticRotationDynamics(period=1, angle=130) simulation.operations.integrator.collision_method = srd - Solvent collision with thermostat. + Collision of MPCD particles with thermostat. .. code-block:: python diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py index 70b862005a..512d016bfa 100644 --- a/hoomd/mpcd/fill.py +++ b/hoomd/mpcd/fill.py @@ -3,10 +3,10 @@ r"""MPCD virtual-particle fillers. -Virtual particles are MPCD solvent particles that are added to ensure MPCD +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 solvent +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. @@ -149,8 +149,8 @@ def _attach_hook(self): 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") + assert class_ is not None, ("Virtual particle filler for geometry " + "not found") self._cpp_obj = class_( sim.state._cpp_sys_def, diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index df655fb923..e2454f7634 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -1,17 +1,17 @@ # Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -r"""MPCD solvent forces. +r"""MPCD particle body forces. 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 solvent force, so care should be chosen that the entire model +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 solvent through the collision -step. Additionally, a thermostat will likely be required to maintain temperature -control in the driven system. +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. .. invisible-code-block: python @@ -26,10 +26,10 @@ from hoomd.operation import _HOOMDBaseObject -class SolventForce(_HOOMDBaseObject): - """Solvent force. +class BodyForce(_HOOMDBaseObject): + """Body force. - The `SolventForce` is a body force applied to each solvent particle. This + The `BodyForce` is a body force applied to each MPCD particle. This class should not be instantiated directly. It exists for type checking. """ @@ -37,7 +37,7 @@ class should not be instantiated directly. It exists for type checking. pass -class BlockForce(SolventForce): +class BlockForce(BodyForce): r"""Block force. Args: @@ -45,7 +45,7 @@ class BlockForce(SolventForce): separation (float): Distance between the centers of the blocks. width (float): Width of each block. - The `force` magnitude *F* is applied in the *x* direction on the solvent + 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. @@ -78,7 +78,7 @@ class BlockForce(SolventForce): 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, solvent_force=force) + stream = hoomd.mpcd.stream.Bulk(period=1, mpcd_particle_force=force) simulation.operations.integrator.streaming_method = stream Attributes: @@ -126,13 +126,13 @@ def _attach_hook(self): super()._attach_hook() -class ConstantForce(SolventForce): +class ConstantForce(BodyForce): r"""Constant force. Args: force (`tuple` [`float`, `float`, `float`]): Force vector per particle. - The same constant force is applied to all solvent particles, independently + 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 @@ -143,7 +143,7 @@ class ConstantForce(SolventForce): .. code-block:: python force = hoomd.mpcd.force.ConstantForce((1.0, 0, 0)) - stream = hoomd.mpcd.stream.Bulk(period=1, solvent_force=force) + stream = hoomd.mpcd.stream.Bulk(period=1, mpcd_particle_force=force) simulation.operations.integrator.streaming_method = stream Attributes: @@ -170,7 +170,7 @@ def _attach_hook(self): super()._attach_hook() -class SineForce(SolventForce): +class SineForce(BodyForce): r"""Sine force. Args: @@ -178,7 +178,7 @@ class SineForce(SolventForce): wavenumber (float): Wavenumber for the sinusoid. `SineForce` applies a force with amplitude *F* in *x* that is sinusoidally - varying in *y* with wavenumber *k* to all solvent particles: + varying in *y* with wavenumber *k* to all MPCD particles: .. math:: @@ -198,7 +198,7 @@ class SineForce(SolventForce): force = hoomd.mpcd.force.SineForce( amplitude=1.0, wavenumber=2 * numpy.pi / Ly) - stream = hoomd.mpcd.stream.Bulk(period=1, solvent_force=force) + stream = hoomd.mpcd.stream.Bulk(period=1, mpcd_particle_force=force) simulation.operations.integrator.streaming_method = stream Attributes: diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index 13e71b78ec..5c8413bdc5 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -45,17 +45,17 @@ class Integrator(_MDIntegrator): arbitrary computations during the half-step of the integration. streaming_method (hoomd.mpcd.stream.StreamingMethod): Streaming method - for the MPCD solvent. + for the MPCD particles. collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method - for the MPCD solvent and any embedded particles. + for the MPCD particles and any embedded particles. virtual_particle_fillers - (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): Solvent + (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): MPCD virtual-particle filler(s). - solvent_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the - MPCD particles. + 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. @@ -84,7 +84,7 @@ class Integrator(_MDIntegrator): simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) - MPCD integrator for pure solvent. + Integrator for only MPCD particles. .. code-block:: python @@ -96,7 +96,7 @@ class Integrator(_MDIntegrator): dt=0.1, streaming_method=stream, collision_method=collide, - solvent_sorter=hoomd.mpcd.tune.ParticleSorter(trigger=20)) + mpcd_particle_sorter=hoomd.mpcd.tune.ParticleSorter(trigger=20)) simulation.operations.integrator = integrator MPCD integrator with solutes. @@ -119,7 +119,9 @@ class Integrator(_MDIntegrator): methods=[solute_method], streaming_method=stream, collision_method=collide, - solvent_sorter=hoomd.mpcd.tune.ParticleSorter(trigger=20*md_steps_per_collision)) + mpcd_particle_sorter=hoomd.mpcd.tune.ParticleSorter( + trigger=20*md_steps_per_collision) + ) simulation.operations.integrator = integrator MPCD integrator with virtual particle filler. @@ -143,18 +145,18 @@ class Integrator(_MDIntegrator): streaming_method=stream, collision_method=collide, virtual_particle_fillers=[filler], - solvent_sorter=hoomd.mpcd.tune.ParticleSorter(trigger=20)) + 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 solvent and any embedded particles. + for the MPCD particles and any embedded particles. - solvent_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner for sorting the - MPCD particles (recommended). + 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 solvent. + for the MPCD particles. """ @@ -170,7 +172,7 @@ def __init__( streaming_method=None, collision_method=None, virtual_particle_fillers=None, - solvent_sorter=None, + mpcd_particle_sorter=None, ): super().__init__( dt, @@ -195,13 +197,13 @@ def __init__( param_dict = ParameterDict( streaming_method=OnlyTypes(StreamingMethod, allow_none=True), collision_method=OnlyTypes(CollisionMethod, allow_none=True), - solvent_sorter=OnlyTypes(ParticleSorter, allow_none=True), + mpcd_particle_sorter=OnlyTypes(ParticleSorter, allow_none=True), ) param_dict.update( dict( streaming_method=streaming_method, collision_method=collision_method, - solvent_sorter=solvent_sorter, + mpcd_particle_sorter=mpcd_particle_sorter, )) self._param_dict.update(param_dict) @@ -218,7 +220,7 @@ def cell_list(self): @property def virtual_particle_fillers(self): - """Sequence[hoomd.mpcd.fill.VirtualParticleFiller]: Solvent \ + """Sequence[hoomd.mpcd.fill.VirtualParticleFiller]: MPCD \ virtual-particle fillers.""" return self._virtual_particle_fillers @@ -232,8 +234,8 @@ def _attach_hook(self): self.streaming_method._attach(self._simulation) if self.collision_method is not None: self.collision_method._attach(self._simulation) - if self.solvent_sorter is not None: - self.solvent_sorter._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) @@ -250,13 +252,14 @@ def _detach_hook(self): self.streaming_method._detach() if self.collision_method is not None: self.collision_method._detach() - if self.solvent_sorter is not None: - self.solvent_sorter._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", "solvent_sorter"): + if attr in ("streaming_method", "collision_method", + "mpcd_particle_sorter"): cur_value = getattr(self, attr) if value is cur_value: return diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py index 5ba09cc795..8f8708b22e 100644 --- a/hoomd/mpcd/methods.py +++ b/hoomd/mpcd/methods.py @@ -4,7 +4,7 @@ r"""MPCD integration methods. Extra integration methods for solutes (MD particles) embedded in an MPCD -solvent. These methods are not restricted to MPCD simulations: they can be used +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. @@ -32,7 +32,7 @@ class BounceBack(Method): geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. A bounce-back method for integrating solutes (MD particles) embedded in an - MPCD solvent. The integration scheme is velocity Verlet with bounce-back + 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 @@ -45,7 +45,7 @@ class BounceBack(Method): 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, or an error will be raised at runtime. + 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 diff --git a/hoomd/mpcd/pytest/test_integrator.py b/hoomd/mpcd/pytest/test_integrator.py index a86a2ead91..c760a3b743 100644 --- a/hoomd/mpcd/pytest/test_integrator.py +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -129,28 +129,28 @@ def test_virtual_particle_fillers(make_simulation): assert len(ig.virtual_particle_fillers) == 0 -def test_solvent_sorter(make_simulation): +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, solvent_sorter=sorter) + ig = hoomd.mpcd.Integrator(dt=0.1, mpcd_particle_sorter=sorter) sim.operations.integrator = ig - assert ig.solvent_sorter is sorter + assert ig.mpcd_particle_sorter is sorter sim.run(0) - assert ig.solvent_sorter is sorter + assert ig.mpcd_particle_sorter is sorter # clear out by setter - ig.solvent_sorter = None - assert ig.solvent_sorter is None + ig.mpcd_particle_sorter = None + assert ig.mpcd_particle_sorter is None sim.run(0) - assert ig.solvent_sorter is None + assert ig.mpcd_particle_sorter is None # assign by setter - ig.solvent_sorter = sorter - assert ig.solvent_sorter is sorter + ig.mpcd_particle_sorter = sorter + assert ig.mpcd_particle_sorter is sorter sim.run(0) - assert ig.solvent_sorter is sorter + assert ig.mpcd_particle_sorter is sorter def test_attach_and_detach(make_simulation): @@ -165,17 +165,17 @@ def test_attach_and_detach(make_simulation): assert ig.streaming_method is None assert ig.collision_method is None assert len(ig.virtual_particle_fillers) == 0 - assert ig.solvent_sorter is None + 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.solvent_sorter = hoomd.mpcd.tune.ParticleSorter(trigger=1) + 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.solvent_sorter._attached + assert ig.mpcd_particle_sorter._attached # detach with everything sim.operations._unschedule() @@ -183,7 +183,7 @@ def test_attach_and_detach(make_simulation): assert not ig.cell_list._attached assert not ig.streaming_method._attached assert not ig.collision_method._attached - assert not ig.solvent_sorter._attached + assert not ig.mpcd_particle_sorter._attached def test_pickling(make_simulation): diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py index c526bf4ce4..55b09b9990 100644 --- a/hoomd/mpcd/pytest/test_stream.py +++ b/hoomd/mpcd/pytest/test_stream.py @@ -80,8 +80,8 @@ def test_pickling(self, simulation_factory, snap, cls, init_args): 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, solvent_force=force) - assert sm.solvent_force is force + sm = cls(period=5, **init_args, mpcd_particle_force=force) + assert sm.mpcd_particle_force is force pickling_check(sm) sim = simulation_factory(snap) @@ -89,7 +89,7 @@ def test_force_attach(self, simulation_factory, snap, cls, init_args, streaming_method=sm) sim.run(0) - assert sm.solvent_force is force + assert sm.mpcd_particle_force is force pickling_check(sm) def test_forced_step(self, simulation_factory, snap, cls, init_args): @@ -109,7 +109,7 @@ def test_forced_step(self, simulation_factory, snap, cls, init_args): sim = simulation_factory(snap) sm = cls(period=1, **init_args, - solvent_force=hoomd.mpcd.force.ConstantForce((1, 0, -1))) + mpcd_particle_force=hoomd.mpcd.force.ConstantForce((1, 0, -1))) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -235,7 +235,8 @@ def test_step_slip(self, simulation_factory, snap): sm = hoomd.mpcd.stream.BounceBack( period=1, geometry=hoomd.mpcd.geometry.ParallelPlates(separation=8.0, - no_slip=False)) + no_slip=False), + ) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -300,8 +301,8 @@ def test_step_moving_wall(self, simulation_factory, snap): 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_solvent_particles(self, simulation_factory, snap, H, - expected_result): + 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) @@ -312,7 +313,7 @@ def test_check_solvent_particles(self, simulation_factory, snap, H, sim.operations.integrator = ig sim.run(0) - assert sm.check_solvent_particles() is expected_result + assert sm.check_mpcd_particles() is expected_result class TestPlanarPore: @@ -349,7 +350,8 @@ def test_step_noslip(self, simulation_factory, snap): period=1, geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=6.0, - no_slip=True)) + no_slip=True), + ) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -422,7 +424,8 @@ def test_step_slip(self, simulation_factory, snap): period=1, geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=6.0, - no_slip=False)) + no_slip=False), + ) ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) sim.operations.integrator = ig @@ -488,7 +491,7 @@ def test_step_slip(self, simulation_factory, snap): np.testing.assert_array_almost_equal(snap.mpcd.position[7], [3.18, -4.17, 0]) - def test_check_solvent_particles(self, simulation_factory, snap): + 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) @@ -497,17 +500,20 @@ def test_check_solvent_particles(self, simulation_factory, snap): ig.streaming_method = hoomd.mpcd.stream.BounceBack( period=1, - geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=6.0)) + geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=6.0), + ) sim.run(0) - assert ig.streaming_method.check_solvent_particles() + 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)) + geometry=hoomd.mpcd.geometry.PlanarPore(separation=7.6, length=6.0), + ) sim.run(0) - assert not ig.streaming_method.check_solvent_particles() + 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_solvent_particles() + 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 index f8e9c2bea7..ddec40026d 100644 --- a/hoomd/mpcd/pytest/test_tune.py +++ b/hoomd/mpcd/pytest/test_tune.py @@ -31,7 +31,7 @@ def test_create(self, simulation_factory, snap): sorter.trigger = trigger assert sorter.trigger is trigger - ig = hoomd.mpcd.Integrator(dt=0.02, solvent_sorter=sorter) + ig = hoomd.mpcd.Integrator(dt=0.02, mpcd_particle_sorter=sorter) sim.operations.integrator = ig sim.run(0) assert sorter.trigger is trigger @@ -44,7 +44,7 @@ def test_pickling(self, simulation_factory, snap): pickling_check(sorter) sim = simulation_factory(snap) - sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, - solvent_sorter=sorter) + 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 b9c03d4a62..f5e58b68bf 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -33,7 +33,7 @@ from hoomd.data.parameterdicts import ParameterDict from hoomd.data.typeconverter import OnlyTypes from hoomd.mpcd import _mpcd -from hoomd.mpcd.force import SolventForce +from hoomd.mpcd.force import BodyForce from hoomd.mpcd.geometry import Geometry from hoomd.operation import Operation @@ -43,7 +43,7 @@ class StreamingMethod(Operation): Args: period (int): Number of integration steps covered by streaming step. - solvent_force (SolventForce): Force on solvent. + mpcd_particle_force (BodyForce): Force on MPCD particles. Attributes: period (int): Number of integration steps covered by streaming step @@ -58,20 +58,22 @@ class StreamingMethod(Operation): used if an external force is applied, and more faithful numerical integration is needed. - solvent_force (SolventForce): Force on solvent. + mpcd_particle_force (BodyForce): Body force on MPCD particles. - The `solvent_force` cannot be changed after the `StreamingMethod` is - constructed, but its attributes can be modified. + The `mpcd_particle_force` cannot be changed after the + `StreamingMethod` is constructed, but its attributes can be + modified. """ - def __init__(self, period, solvent_force=None): + def __init__(self, period, mpcd_particle_force=None): super().__init__() - param_dict = ParameterDict(period=int(period), - solvent_force=OnlyTypes(SolventForce, - allow_none=True)) - param_dict["solvent_force"] = solvent_force + 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) @@ -80,7 +82,7 @@ class Bulk(StreamingMethod): Args: period (int): Number of integration steps covered by streaming step. - solvent_force (SolventForce): Force on solvent. + mpcd_particle_force (BodyForce): Body force on MPCD particles. `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 @@ -101,7 +103,7 @@ class Bulk(StreamingMethod): stream = hoomd.mpcd.stream.Bulk( period=1, - solvent_force=hoomd.mpcd.force.ConstantForce((1, 0, 0))) + mpcd_particle_force=hoomd.mpcd.force.ConstantForce((1, 0, 0))) simulation.operations.integrator.streaming_method = stream """ @@ -109,19 +111,19 @@ class Bulk(StreamingMethod): def _attach_hook(self): sim = self._simulation - # attach and use solvent force if present - if self.solvent_force is not None: - self.solvent_force._attach(sim) - solvent_force = self.solvent_force._cpp_obj + # 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: - solvent_force = None + mpcd_particle_force = None # try to find force in map, otherwise use default - force_type = type(self.solvent_force) + force_type = type(self.mpcd_particle_force) try: class_info = self._cpp_class_map[force_type] except KeyError: - if self.solvent_force is not None: + if self.mpcd_particle_force is not None: force_name = force_type.__name__ else: force_name = "NoForce" @@ -133,22 +135,22 @@ def _attach_hook(self): 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") + 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, - solvent_force, + mpcd_particle_force, ) super()._attach_hook() def _detach_hook(self): - if self.solvent_force is not None: - self.solvent_force._detach() + if self.mpcd_particle_force is not None: + self.mpcd_particle_force._detach() super()._detach_hook() _cpp_class_map = {} @@ -164,16 +166,16 @@ class BounceBack(StreamingMethod): Args: period (int): Number of integration steps covered by streaming step. geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. - solvent_force (SolventForce): Force on solvent. + 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 solvent particles from boundary surfaces using specular reflections + 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 solvent particles, + 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 @@ -187,7 +189,7 @@ class BounceBack(StreamingMethod): 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. An error will be raised otherwise. + "inside" the geometry. .. rubric:: Examples: @@ -209,7 +211,7 @@ class BounceBack(StreamingMethod): period=1, geometry=hoomd.mpcd.geometry.ParallelPlates( separation=6.0, no_slip=True), - solvent_force=hoomd.mpcd.force.ConstantForce((1, 0, 0))) + mpcd_particle_force=hoomd.mpcd.force.ConstantForce((1, 0, 0))) simulation.operations.integrator.streaming_method = stream Attributes: @@ -220,50 +222,50 @@ class BounceBack(StreamingMethod): _cpp_class_map = {} - def __init__(self, period, geometry, solvent_force=None): - super().__init__(period, solvent_force) + def __init__(self, period, geometry, mpcd_particle_force=None): + super().__init__(period, mpcd_particle_force) param_dict = ParameterDict(geometry=Geometry) param_dict["geometry"] = geometry self._param_dict.update(param_dict) - def check_solvent_particles(self): - """Check if solvent particles are inside `geometry`. + def check_mpcd_particles(self): + """Check if MPCD particles are inside `geometry`. This method can only be called after this object is attached to a simulation. Returns: - True if all solvent particles are inside `geometry`. + True if all MPCD particles are inside `geometry`. .. rubric:: Examples: .. code-block:: python - assert stream.check_solvent_particles() + assert stream.check_mpcd_particles() """ - return self._cpp_obj.check_solvent_particles() + return self._cpp_obj.check_mpcd_particles() def _attach_hook(self): sim = self._simulation self.geometry._attach(sim) - # attach and use solvent force if present - if self.solvent_force is not None: - self.solvent_force._attach(sim) - solvent_force = self.solvent_force._cpp_obj + # 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: - solvent_force = None + mpcd_particle_force = None # try to find force in map, otherwise use default geom_type = type(self.geometry) - force_type = type(self.solvent_force) + force_type = type(self.mpcd_particle_force) try: class_info = self._cpp_class_map[geom_type, force_type] except KeyError: - if self.solvent_force is not None: + if self.mpcd_particle_force is not None: force_name = force_type.__name__ else: force_name = "NoForce" @@ -284,15 +286,15 @@ def _attach_hook(self): self.period, 0, self.geometry._cpp_obj, - solvent_force, + mpcd_particle_force, ) super()._attach_hook() def _detach_hook(self): self.geometry._detach() - if self.solvent_force is not None: - self.solvent_force._detach() + if self.mpcd_particle_force is not None: + self.mpcd_particle_force._detach() super()._detach_hook() @classmethod diff --git a/hoomd/mpcd/tune.py b/hoomd/mpcd/tune.py index f6b7eb8ceb..74d5e84d7f 100644 --- a/hoomd/mpcd/tune.py +++ b/hoomd/mpcd/tune.py @@ -38,9 +38,9 @@ class ParticleSorter(TriggeredOperation): builds. Typically, using a small multiple (tens) of the collision period works best. - For best performance, the `ParticleSorter` should **not** be added to + To achieve the best performance, the `ParticleSorter` is not added to `hoomd.Operations.tuners`. Instead, set it in - `hoomd.mpcd.Integrator.solvent_sorter`. + `hoomd.mpcd.Integrator.mpcd_particle_sorter`. Essentially all MPCD systems benefit from sorting, so it is recommended to use one for all simulations! @@ -50,7 +50,7 @@ class ParticleSorter(TriggeredOperation): .. code-block:: python sorter = hoomd.mpcd.tune.ParticleSorter(trigger=20) - simulation.operations.integrator.solvent_sorter = sorter + simulation.operations.integrator.mpcd_particle_sorter = sorter Attributes: trigger (hoomd.trigger.Trigger): Number of integration steps From 82aee0186becc54ee268a7e34bb75c5b93d002b7 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Mon, 15 Apr 2024 22:11:40 -0500 Subject: [PATCH 62/69] Apply fixes from code review --- hoomd/mpcd/force.py | 2 +- sphinx-doc/module-mpcd-force.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index e2454f7634..0ab1842a4e 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -90,7 +90,7 @@ class BlockForce(BodyForce): force.force = 1.0 - separation (float): Ddistance between the centers of the blocks. + separation (float): Distance between the centers of the blocks. .. rubric:: Example: diff --git a/sphinx-doc/module-mpcd-force.rst b/sphinx-doc/module-mpcd-force.rst index 1dc14c6c65..c6ca9a8abd 100644 --- a/sphinx-doc/module-mpcd-force.rst +++ b/sphinx-doc/module-mpcd-force.rst @@ -11,7 +11,7 @@ mpcd.force .. autosummary:: :nosignatures: - SolventForce + BodyForce BlockForce ConstantForce SineForce @@ -19,8 +19,8 @@ mpcd.force .. rubric:: Details .. automodule:: hoomd.mpcd.force - :synopsis: Solvent forces. - :members: SolventForce, + :synopsis: Body forces on MPCD particles. + :members: BodyForce, BlockForce, ConstantForce, SineForce From eee6849d2041fcc72dce698ce680d11c71872dd2 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Tue, 7 May 2024 10:36:02 -0500 Subject: [PATCH 63/69] Add MPCD migration guide --- sphinx-doc/migrating.rst | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) 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 ^^^^^^^^^ From af043066b03a9eb008ea0a370db14738953417bc Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Tue, 7 May 2024 22:12:53 -0500 Subject: [PATCH 64/69] Fix pre-commit failures in mpcd module --- hoomd/mpcd/BlockForce.h | 2 +- hoomd/mpcd/BulkStreamingMethod.h | 12 ++++++------ hoomd/mpcd/BulkStreamingMethodGPU.h | 12 ++++++------ hoomd/mpcd/ConstantForce.h | 2 +- hoomd/mpcd/ManualVirtualParticleFiller.h | 6 +++--- hoomd/mpcd/NoForce.h | 2 +- hoomd/mpcd/ParallelPlateGeometryFiller.h | 6 +++--- hoomd/mpcd/ParallelPlateGeometryFillerGPU.h | 6 +++--- hoomd/mpcd/SineForce.h | 2 +- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/hoomd/mpcd/BlockForce.h b/hoomd/mpcd/BlockForce.h index 17532521f4..043a49c955 100644 --- a/hoomd/mpcd/BlockForce.h +++ b/hoomd/mpcd/BlockForce.h @@ -132,7 +132,7 @@ class __attribute__((visibility("default"))) BlockForce namespace detail { void export_BlockForce(pybind11::module& m); - } // end namespace detail + } // end namespace detail #endif // __HIPCC__ } // end namespace mpcd diff --git a/hoomd/mpcd/BulkStreamingMethod.h b/hoomd/mpcd/BulkStreamingMethod.h index 2019d24daf..be75cd2f8f 100644 --- a/hoomd/mpcd/BulkStreamingMethod.h +++ b/hoomd/mpcd/BulkStreamingMethod.h @@ -33,12 +33,12 @@ class PYBIND11_EXPORT BulkStreamingMethod int phase, std::shared_ptr force) : mpcd::BounceBackStreamingMethod( - sysdef, - cur_timestep, - period, - phase, - std::make_shared(), - force) + sysdef, + cur_timestep, + period, + phase, + std::make_shared(), + force) { } }; diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.h b/hoomd/mpcd/BulkStreamingMethodGPU.h index de8e00bb8f..4e7b8b6f37 100644 --- a/hoomd/mpcd/BulkStreamingMethodGPU.h +++ b/hoomd/mpcd/BulkStreamingMethodGPU.h @@ -33,12 +33,12 @@ class PYBIND11_EXPORT BulkStreamingMethodGPU int phase, std::shared_ptr force) : mpcd::BounceBackStreamingMethodGPU( - sysdef, - cur_timestep, - period, - phase, - std::make_shared(), - force) + sysdef, + cur_timestep, + period, + phase, + std::make_shared(), + force) { } }; diff --git a/hoomd/mpcd/ConstantForce.h b/hoomd/mpcd/ConstantForce.h index 1b922f3712..a1338eaf63 100644 --- a/hoomd/mpcd/ConstantForce.h +++ b/hoomd/mpcd/ConstantForce.h @@ -77,7 +77,7 @@ class __attribute__((visibility("default"))) ConstantForce namespace detail { void export_ConstantForce(pybind11::module& m); - } // end namespace detail + } // end namespace detail #endif // __HIPCC__ } // end namespace mpcd diff --git a/hoomd/mpcd/ManualVirtualParticleFiller.h b/hoomd/mpcd/ManualVirtualParticleFiller.h index 129d6f7d43..0edac792bb 100644 --- a/hoomd/mpcd/ManualVirtualParticleFiller.h +++ b/hoomd/mpcd/ManualVirtualParticleFiller.h @@ -61,7 +61,7 @@ namespace detail { //! Export the ManualVirtualParticleFiller to python void export_ManualVirtualParticleFiller(pybind11::module& m); - } // end namespace detail - } // end namespace mpcd - } // end namespace hoomd + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd #endif // MPCD_MANUAL_VIRTUAL_PARTICLE_FILLER_H_ diff --git a/hoomd/mpcd/NoForce.h b/hoomd/mpcd/NoForce.h index da56bc7f35..447823fe96 100644 --- a/hoomd/mpcd/NoForce.h +++ b/hoomd/mpcd/NoForce.h @@ -54,7 +54,7 @@ class NoForce namespace detail { void export_NoForce(pybind11::module& m); - } // end namespace detail + } // end namespace detail #endif // __HIPCC__ } // end namespace mpcd diff --git a/hoomd/mpcd/ParallelPlateGeometryFiller.h b/hoomd/mpcd/ParallelPlateGeometryFiller.h index 33de1cf898..8e91388ee3 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFiller.h +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.h @@ -66,7 +66,7 @@ namespace detail { //! Export ParallelPlateGeometryFiller to python void export_ParallelPlateGeometryFiller(pybind11::module& m); - } // end namespace detail - } // end namespace mpcd - } // end namespace hoomd + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd #endif // MPCD_PARALLEL_PLATE_GEOMETRY_FILLER_H_ diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h index 56b095e266..929e547f18 100644 --- a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h @@ -44,7 +44,7 @@ namespace detail { //! Export ParallelPlateGeometryFillerGPU to python void export_ParallelPlateGeometryFillerGPU(pybind11::module& m); - } // end namespace detail - } // end namespace mpcd - } // end namespace hoomd + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd #endif // MPCD_PARALLEL_PLATE_GEOMETRY_FILLER_GPU_H_ diff --git a/hoomd/mpcd/SineForce.h b/hoomd/mpcd/SineForce.h index 8407b0d7d6..db4ff8aa48 100644 --- a/hoomd/mpcd/SineForce.h +++ b/hoomd/mpcd/SineForce.h @@ -103,7 +103,7 @@ class __attribute__((visibility("default"))) SineForce namespace detail { void export_SineForce(pybind11::module& m); - } // end namespace detail + } // end namespace detail #endif // __HIPCC__ } // end namespace mpcd From ee38623bb708a907d0f23cc8592e6a3c3ddf3b34 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Tue, 7 May 2024 22:28:29 -0500 Subject: [PATCH 65/69] Fix merge error --- hoomd/mpcd/PlanarPoreGeometryFiller.h | 4 ++-- hoomd/mpcd/PlanarPoreGeometryFillerGPU.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hoomd/mpcd/PlanarPoreGeometryFiller.h b/hoomd/mpcd/PlanarPoreGeometryFiller.h index d18d9ea956..22fca93734 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFiller.h +++ b/hoomd/mpcd/PlanarPoreGeometryFiller.h @@ -74,8 +74,8 @@ class PYBIND11_EXPORT PlanarPoreGeometryFiller : public mpcd::ManualVirtualParti 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/PlanarPoreGeometryFillerGPU.h b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.h index 61f08528ad..0084f4a779 100644 --- a/hoomd/mpcd/PlanarPoreGeometryFillerGPU.h +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.h @@ -42,8 +42,8 @@ class PYBIND11_EXPORT PlanarPoreGeometryFillerGPU : public mpcd::PlanarPoreGeome 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 From 4b631378ea4818063c8b16f446737c597ba0b158 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Wed, 15 May 2024 13:44:18 -0500 Subject: [PATCH 66/69] Add Yashraj Wani to credits --- sphinx-doc/credits.rst | 1 + 1 file changed, 1 insertion(+) 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 From fb1f10a784901ced8a5b9db1ebbe597aea30ce5e Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 16 May 2024 11:44:14 -0500 Subject: [PATCH 67/69] Update build instructions for MPCD --- BUILDING.rst | 1 + CMakeLists.txt | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/BUILDING.rst b/BUILDING.rst index 6cb4461edb..637a72dd5c 100644 --- a/BUILDING.rst +++ b/BUILDING.rst @@ -221,6 +221,7 @@ 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 (default: ``on``). - ``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..6dfcbbadcd 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" on) # 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}]") From d328e79134ba8240df90967dbf361d4bd6066c38 Mon Sep 17 00:00:00 2001 From: Michael Howard Date: Thu, 16 May 2024 12:07:53 -0500 Subject: [PATCH 68/69] Revise BUILD_MPCD option --- BUILDING.rst | 3 ++- CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/BUILDING.rst b/BUILDING.rst index 637a72dd5c..2bdbb11b42 100644 --- a/BUILDING.rst +++ b/BUILDING.rst @@ -221,7 +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 (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 6dfcbbadcd..5e2ddffbec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,7 +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" 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) From 751dc8b1d7d07bdf5dcabcbf160811b79c12bded Mon Sep 17 00:00:00 2001 From: "Joshua A. Anderson" Date: Wed, 29 May 2024 10:11:20 -0400 Subject: [PATCH 69/69] Remove remaining references to GPU polymorphism. --- hoomd/test/CMakeLists.txt | 4 --- hoomd/test/test_gpu_polymorph.cuh | 54 ------------------------------- 2 files changed, 58 deletions(-) delete mode 100644 hoomd/test/test_gpu_polymorph.cuh 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.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_