From c8107285c2ae57938cc4531670a2460d700c4739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Wed, 28 Aug 2024 19:07:57 +0200 Subject: [PATCH] Rewrite Collision detection feature Co-authored-by: Somesh Kurahatti Co-authored-by: Mariano B --- src/core/collision.cpp | 557 ++++++++++-------- src/core/collision.hpp | 124 ++-- src/core/collision/protocols.hpp | 230 ++++++++ src/core/forces.cpp | 2 +- src/core/system/System.cpp | 2 +- src/python/espressomd/collision_detection.py | 171 ++++-- .../collision_detection/BindCenters.hpp | 75 +++ .../collision_detection/BindVS.hpp | 93 +++ .../CollisionDetection.hpp | 240 ++------ .../collision_detection/GlueToSurf.hpp | 122 ++++ .../collision_detection/Off.hpp | 49 ++ .../collision_detection/Protocol.hpp | 88 +++ .../collision_detection/initialize.cpp | 13 +- .../interactions/BondedInteractions.hpp | 2 +- testsuite/python/bond_breakage.py | 15 +- testsuite/python/collision_detection.py | 44 +- .../python/collision_detection_interface.py | 43 +- testsuite/python/lees_edwards.py | 13 +- testsuite/python/save_checkpoint.py | 4 +- testsuite/python/test_checkpoint.py | 9 +- 20 files changed, 1238 insertions(+), 658 deletions(-) create mode 100644 src/core/collision/protocols.hpp create mode 100644 src/script_interface/collision_detection/BindCenters.hpp create mode 100644 src/script_interface/collision_detection/BindVS.hpp create mode 100644 src/script_interface/collision_detection/GlueToSurf.hpp create mode 100644 src/script_interface/collision_detection/Off.hpp create mode 100644 src/script_interface/collision_detection/Protocol.hpp diff --git a/src/core/collision.cpp b/src/core/collision.cpp index 0070d90100b..886cb57c428 100644 --- a/src/core/collision.cpp +++ b/src/core/collision.cpp @@ -74,105 +74,91 @@ Particle &get_part(CellStructure &cell_structure, int id) { return *p; } } // namespace +namespace Collision { -void CollisionDetection::initialize() { - // If mode is OFF, no further checks - if (mode == CollisionModeType::OFF) { - return; - } +void BindCenters::initialize(BondedInteractionsMap &bonded_ias, + InteractionsNonBonded &nonbonded_ias) { // Validate distance - if (mode != CollisionModeType::OFF) { - if (distance <= 0.) { - throw std::domain_error("Parameter 'distance' must be > 0"); - } - - // Cache square of cutoff - distance_sq = Utils::sqr(distance); + if (distance <= 0.) { + throw std::domain_error("Parameter 'distance' must be > 0"); } - -#ifndef VIRTUAL_SITES_RELATIVE - // The collision modes involving virtual sites also require the creation of a - // bond between the colliding - // If we don't have virtual sites, virtual site binding isn't possible. - if ((mode == CollisionModeType::BIND_VS) or - (mode == CollisionModeType::GLUE_TO_SURF)) { - throw std::runtime_error("collision modes based on virtual sites require " - "the VIRTUAL_SITES_RELATIVE feature"); - } -#endif - -#ifdef VIRTUAL_SITES - // Check vs placement parameter - if (mode == CollisionModeType::BIND_VS) { - if (vs_placement < 0. or vs_placement > 1.) { - throw std::domain_error( - "Parameter 'vs_placement' must be between 0 and 1"); - } - } -#endif - - auto &system = get_system(); - auto &bonded_ias = *system.bonded_ias; - auto &nonbonded_ias = *system.nonbonded_ias; - + // Cache square of cutoff + distance_sq = Utils::sqr(distance); // Check if bond exists - assert(mode != CollisionModeType::BIND_CENTERS or - bonded_ias.contains(bond_centers)); - assert(mode != CollisionModeType::BIND_VS or bonded_ias.contains(bond_vs)); - + assert(bonded_ias.contains(bond_centers)); // If the bond type to bind particle centers is not a pair bond... // Check that the bonds have the right number of partners - if ((mode == CollisionModeType::BIND_CENTERS) and - (number_of_partners(*bonded_ias.at(bond_centers)) != 1)) { + if (number_of_partners(*bonded_ias.at(bond_centers)) != 1) { throw std::runtime_error("The bond type to be used for binding particle " "centers needs to be a pair bond"); } +} + +#ifdef VIRTUAL_SITES_RELATIVE +void BindVS::initialize(BondedInteractionsMap &bonded_ias, + InteractionsNonBonded &nonbonded_ias) { + // Validate distance + if (distance <= 0.) { + throw std::domain_error("Parameter 'distance' must be > 0"); + } + // Cache square of cutoff + distance_sq = Utils::sqr(distance); + + // Check vs placement parameter + if (vs_placement < 0. or vs_placement > 1.) { + throw std::domain_error("Parameter 'vs_placement' must be between 0 and 1"); + } + // Check if bond exists + assert(bonded_ias.contains(bond_vs)); // The bond between the virtual sites can be pair or triple - if ((mode == CollisionModeType::BIND_VS) and - (number_of_partners(*bonded_ias.at(bond_vs)) != 1 and + if ((number_of_partners(*bonded_ias.at(bond_vs)) != 1 and number_of_partners(*bonded_ias.at(bond_vs)) != 2)) { throw std::runtime_error("The bond type to be used for binding virtual " "sites needs to be a pair bond"); } // Create particle types - - if (mode == CollisionModeType::BIND_VS) { - if (vs_particle_type < 0) { - throw std::domain_error("Collision detection particle type for virtual " - "sites needs to be >=0"); - } - nonbonded_ias.make_particle_type_exist(vs_particle_type); + if (part_type_vs < 0) { + throw std::domain_error("Collision detection particle type for virtual " + "sites needs to be >=0"); } + nonbonded_ias.make_particle_type_exist(part_type_vs); +} - if (mode == CollisionModeType::GLUE_TO_SURF) { - if (vs_particle_type < 0) { - throw std::domain_error("Collision detection particle type for virtual " - "sites needs to be >=0"); - } - nonbonded_ias.make_particle_type_exist(vs_particle_type); +void GlueToSurf::initialize(BondedInteractionsMap &bonded_ias, + InteractionsNonBonded &nonbonded_ias) { + // Validate distance + if (distance <= 0.) { + throw std::domain_error("Parameter 'distance' must be > 0"); + } + // Cache square of cutoff + distance_sq = Utils::sqr(distance); - if (part_type_to_be_glued < 0) { - throw std::domain_error("Collision detection particle type to be glued " - "needs to be >=0"); - } - nonbonded_ias.make_particle_type_exist(part_type_to_be_glued); + if (part_type_vs < 0) { + throw std::domain_error("Collision detection particle type for virtual " + "sites needs to be >=0"); + } + nonbonded_ias.make_particle_type_exist(part_type_vs); - if (part_type_to_attach_vs_to < 0) { - throw std::domain_error("Collision detection particle type to attach " - "the virtual site to needs to be >=0"); - } - nonbonded_ias.make_particle_type_exist(part_type_to_attach_vs_to); + if (part_type_to_be_glued < 0) { + throw std::domain_error("Collision detection particle type to be glued " + "needs to be >=0"); + } + nonbonded_ias.make_particle_type_exist(part_type_to_be_glued); - if (part_type_after_glueing < 0) { - throw std::domain_error("Collision detection particle type after gluing " - "needs to be >=0"); - } - nonbonded_ias.make_particle_type_exist(part_type_after_glueing); + if (part_type_to_attach_vs_to < 0) { + throw std::domain_error("Collision detection particle type to attach " + "the virtual site to needs to be >=0"); } + nonbonded_ias.make_particle_type_exist(part_type_to_attach_vs_to); - system.on_short_range_ia_change(); + if (part_type_after_glueing < 0) { + throw std::domain_error("Collision detection particle type after gluing " + "needs to be >=0"); + } + nonbonded_ias.make_particle_type_exist(part_type_after_glueing); +} } /** @brief Calculate position of vs for GLUE_TO_SURFACE mode. @@ -180,7 +166,7 @@ void CollisionDetection::initialize() { */ static auto const &glue_to_surface_calc_vs_pos( Particle const &p1, Particle const &p2, BoxGeometry const &box_geo, - CollisionDetection const &collision_params, Utils::Vector3d &pos) { + Collision::GlueToSurf const &collision_params, Utils::Vector3d &pos) { double ratio; auto const vec21 = box_geo.get_mi_vector(p1.pos(), p2.pos()); auto const dist = vec21.norm(); @@ -204,17 +190,16 @@ static auto const &glue_to_surface_calc_vs_pos( static void bind_at_point_of_collision_calc_vs_pos( Particle const &p1, Particle const &p2, BoxGeometry const &box_geo, - CollisionDetection const &collision_params, Utils::Vector3d &pos1, + Collision::BindVS const &collision_params, Utils::Vector3d &pos1, Utils::Vector3d &pos2) { auto const vec21 = box_geo.get_mi_vector(p1.pos(), p2.pos()); pos1 = p1.pos() - vec21 * collision_params.vs_placement; pos2 = p1.pos() - vec21 * (1. - collision_params.vs_placement); } -#ifdef VIRTUAL_SITES_RELATIVE static void place_vs_and_relate_to_particle( CellStructure &cell_structure, BoxGeometry const &box_geo, - CollisionDetection const &collision_params, double const min_global_cut, + auto const &collision_params, double const min_global_cut, int const current_vs_pid, Utils::Vector3d const &pos, int const relate_to) { Particle new_part; new_part.id() = current_vs_pid; @@ -222,12 +207,12 @@ static void place_vs_and_relate_to_particle( auto p_vs = cell_structure.add_particle(std::move(new_part)); vs_relate_to(*p_vs, get_part(cell_structure, relate_to), box_geo, min_global_cut); - p_vs->type() = collision_params.vs_particle_type; + p_vs->type() = collision_params.part_type_vs; } static void bind_at_poc_create_bond_between_vs( CellStructure &cell_structure, BondedInteractionsMap const &bonded_ias, - CollisionDetection const &collision_params, int const current_vs_pid, + Collision::BindVS const &collision_params, int const current_vs_pid, CollisionPair const &c) { switch (number_of_partners(*bonded_ias.at(collision_params.bond_vs))) { case 1: { @@ -253,7 +238,7 @@ static void bind_at_poc_create_bond_between_vs( static void glue_to_surface_bind_part_to_vs( Particle const *const p1, Particle const *const p2, - int const vs_pid_plus_one, CollisionDetection const &collision_params, + int const vs_pid_plus_one, Collision::GlueToSurf const &collision_params, CellStructure &cell_structure) { // Create bond between the virtual particles const int bondG[] = {vs_pid_plus_one - 1}; @@ -280,186 +265,280 @@ static auto gather_collision_queue(std::vector const &local) { return global; } -void CollisionDetection::handle_collisions(CellStructure &cell_structure) { - // Note that the glue to surface mode adds bonds between the centers - // but does so later in the process. This is needed to guarantee that - // a particle can only be glued once, even if queued twice in a single - // time step - auto const bind_centers = mode != CollisionModeType::OFF and - mode != CollisionModeType::GLUE_TO_SURF; - if (bind_centers) { +void CollisionDetection::handle_collisions(CellStructure &) { + auto &system = get_system(); + if (m_protocol) { + std::visit( + [&system, this](auto &protocol) { + return protocol.handle_collisions(system, + this->local_collision_queue); + }, + *m_protocol); + } + clear_queue(); +} + +void Collision::BindCenters::handle_collisions( + System::System &system, std::vector &local_collision_queue) { + { + auto &cell_structure = *system.cell_structure; for (auto &c : local_collision_queue) { - // put the bond to the non-ghost particle; at least one partner always is + // Ensure that the bond is associated with the non-ghost particle if (cell_structure.get_local_particle(c.pp1)->is_ghost()) { std::swap(c.pp1, c.pp2); } const int bondG[] = {c.pp2}; + // Insert the bond for the non-ghost particle get_part(cell_structure, c.pp1).bonds().insert({bond_centers, bondG}); } } +} #ifdef VIRTUAL_SITES_RELATIVE - auto &system = get_system(); - auto const &box_geo = *system.box_geo; +void Collision::BindVS::handle_collisions( + System::System &system, std::vector &local_collision_queue) { + auto &cell_structure = *system.cell_structure; auto const min_global_cut = system.get_min_global_cut(); - if ((mode == CollisionModeType::BIND_VS) || - (mode == CollisionModeType::GLUE_TO_SURF)) { - // Gather the global collision queue, because only one node has a collision - // across node boundaries in its queue. - // The other node might still have to change particle properties on its - // non-ghost particle - auto global_collision_queue = gather_collision_queue(local_collision_queue); - - // Sync max_seen_part - auto const global_max_seen_particle = boost::mpi::all_reduce( - ::comm_cart, cell_structure.get_max_local_particle_id(), - boost::mpi::maximum()); - - int current_vs_pid = global_max_seen_particle + 1; - - // Iterate over global collision queue - for (auto &c : global_collision_queue) { - - // Get particle pointers - Particle *p1 = cell_structure.get_local_particle(c.pp1); - Particle *p2 = cell_structure.get_local_particle(c.pp2); - - // Only nodes take part in particle creation and binding - // that see both particles - - // If we cannot access both particles, both are ghosts, - // or one is ghost and one is not accessible - // we only increase the counter for the ext id to use based on the - // number of particles created by other nodes - if (((!p1 or p1->is_ghost()) and (!p2 or p2->is_ghost())) or !p1 or !p2) { - // Increase local counters - if (mode == CollisionModeType::BIND_VS) { - current_vs_pid++; + + for (auto &c : local_collision_queue) { + // put the bond to the non-ghost particle; at least one partner always is + if (cell_structure.get_local_particle(c.pp1)->is_ghost()) { + std::swap(c.pp1, c.pp2); + } + + const int bondG[] = {c.pp2}; + + get_part(cell_structure, c.pp1).bonds().insert({bond_centers, bondG}); + } + + // Gather the global collision queue, because only one node has a collision + // across node boundaries in its queue. + // The other node might still have to change particle properties on its + // non-ghost particle + auto global_collision_queue = gather_collision_queue(local_collision_queue); + + // Sync max_seen_part + auto const global_max_seen_particle = boost::mpi::all_reduce( + ::comm_cart, cell_structure.get_max_local_particle_id(), + boost::mpi::maximum()); + + int current_vs_pid = global_max_seen_particle + 1; + + // Iterate over global collision queue + for (auto &c : global_collision_queue) { + + // Get particle pointers + Particle *p1 = cell_structure.get_local_particle(c.pp1); + Particle *p2 = cell_structure.get_local_particle(c.pp2); + + // Only nodes take part in particle creation and binding + // that see both particles + + // If we cannot access both particles, both are ghosts, + // or one is ghost and one is not accessible + // we only increase the counter for the ext id to use based on the + // number of particles created by other nodes + if (((!p1 or p1->is_ghost()) and (!p2 or p2->is_ghost())) or !p1 or !p2) { + // Increase local counters + current_vs_pid += 2; + + } else { // We consider the pair because one particle + // is local to the node and the other is local or ghost + // If we are in the two vs mode + // Virtual site related to first particle in the collision + + Utils::Vector3d pos1, pos2; + + // Enable rotation on the particles to which vs will be attached + p1->set_can_rotate_all_axes(); + p2->set_can_rotate_all_axes(); + + // Positions of the virtual sites + bind_at_point_of_collision_calc_vs_pos(*p1, *p2, *system.box_geo, *this, + pos1, pos2); + + auto handle_particle = [&](Particle *p, Utils::Vector3d const &pos) { + if (not p->is_ghost()) { + place_vs_and_relate_to_particle(cell_structure, *system.box_geo, + *this, min_global_cut, current_vs_pid, + pos, p->id()); + // Particle storage locations may have changed due to + // added particle + p1 = cell_structure.get_local_particle(c.pp1); + p2 = cell_structure.get_local_particle(c.pp2); } - // For glue to surface, we have only one vs - current_vs_pid++; - if (mode == CollisionModeType::GLUE_TO_SURF) { - if (p1 and p1->type() == part_type_to_be_glued) { - p1->type() = part_type_after_glueing; - } - if (p2 and p2->type() == part_type_to_be_glued) { - p2->type() = part_type_after_glueing; - } - } // mode glue to surface - - } else { // We consider the pair because one particle - // is local to the node and the other is local or ghost - // If we are in the two vs mode - // Virtual site related to first particle in the collision - if (mode == CollisionModeType::BIND_VS) { - Utils::Vector3d pos1, pos2; - - // Enable rotation on the particles to which vs will be attached - p1->set_can_rotate_all_axes(); - p2->set_can_rotate_all_axes(); - - // Positions of the virtual sites - bind_at_point_of_collision_calc_vs_pos(*p1, *p2, box_geo, *this, pos1, - pos2); - - auto handle_particle = [&](Particle *p, Utils::Vector3d const &pos) { - if (not p->is_ghost()) { - place_vs_and_relate_to_particle(cell_structure, box_geo, *this, - min_global_cut, current_vs_pid, - pos, p->id()); - // Particle storage locations may have changed due to - // added particle - p1 = cell_structure.get_local_particle(c.pp1); - p2 = cell_structure.get_local_particle(c.pp2); - } - }; - - // place virtual sites on the node where the base particle is not a - // ghost - handle_particle(p1, pos1); - // Increment counter - current_vs_pid++; + }; + + // place virtual sites on the node where the base particle is not a + // ghost + handle_particle(p1, pos1); + // Increment counter + current_vs_pid++; + + handle_particle(p2, pos2); + // Increment counter + current_vs_pid++; + // Create bonds between the vs. + + bind_at_poc_create_bond_between_vs(cell_structure, *system.bonded_ias, + *this, current_vs_pid, c); + } // we considered the pair + } // Loop over all collisions in the queue +#ifdef ADDITIONAL_CHECKS + if (!Utils::Mpi::all_compare(comm_cart, current_vs_pid)) { + throw std::runtime_error("Nodes disagree about current_vs_pid"); + } +#endif - handle_particle(p2, pos2); - // Increment counter + // If any node had a collision, all nodes need to resort + if (not global_collision_queue.empty()) { + system.cell_structure->set_resort_particles(Cells::RESORT_GLOBAL); + system.cell_structure->update_ghosts_and_resort_particle( + Cells::DATA_PART_PROPERTIES | Cells::DATA_PART_BONDS); + system.update_used_propagations(); + } +} + +void Collision::GlueToSurf::handle_collisions( + System::System &system, std::vector &local_collision_queue) { + auto &cell_structure = *system.cell_structure; + auto const min_global_cut = system.get_min_global_cut(); + // Note that the glue to surface mode adds bonds between the centers + // but does so later in the process. This is needed to guarantee that + // a particle can only be glued once, even if queued twice in a single + // time step + + // Gather the global collision queue, because only one node has a collision + // across node boundaries in its queue. + // The other node might still have to change particle properties on its + // non-ghost particle + auto global_collision_queue = gather_collision_queue(local_collision_queue); + + // Sync max_seen_part + auto const global_max_seen_particle = boost::mpi::all_reduce( + ::comm_cart, cell_structure.get_max_local_particle_id(), + boost::mpi::maximum()); + + int current_vs_pid = global_max_seen_particle + 1; + + // Iterate over global collision queue + for (auto &c : global_collision_queue) { + + // Get particle pointers + Particle *p1 = cell_structure.get_local_particle(c.pp1); + Particle *p2 = cell_structure.get_local_particle(c.pp2); + + // Only nodes take part in particle creation and binding + // that see both particles + + // If we cannot access both particles, both are ghosts, + // or one is ghost and one is not accessible + // we only increase the counter for the ext id to use based on the + // number of particles created by other nodes + if (((!p1 or p1->is_ghost()) and (!p2 or p2->is_ghost())) or !p1 or !p2) { + // For glue to surface, we have only one vs + current_vs_pid++; + + if (p1 and p1->type() == part_type_to_be_glued) { + p1->type() = part_type_after_glueing; + } + if (p2 and p2->type() == part_type_to_be_glued) { + p2->type() = part_type_after_glueing; + } + + } else { + // If particles are made inert by a type change on collision: + // We skip the pair if one of the particles has already reacted + // but we still increase the particle counters, as other nodes + // can not always know whether or not a vs is placed + if (part_type_after_glueing != part_type_to_be_glued) { + if ((p1->type() == part_type_after_glueing) || + (p2->type() == part_type_after_glueing)) { current_vs_pid++; - // Create bonds between the vs. - - bind_at_poc_create_bond_between_vs(cell_structure, *system.bonded_ias, - *this, current_vs_pid, c); - } // mode VS - - if (mode == CollisionModeType::GLUE_TO_SURF) { - // If particles are made inert by a type change on collision: - // We skip the pair if one of the particles has already reacted - // but we still increase the particle counters, as other nodes - // can not always know whether or not a vs is placed - if (part_type_after_glueing != part_type_to_be_glued) { - if ((p1->type() == part_type_after_glueing) || - (p2->type() == part_type_after_glueing)) { - current_vs_pid++; - continue; - } - } - - Utils::Vector3d pos; - Particle const &attach_vs_to = - glue_to_surface_calc_vs_pos(*p1, *p2, box_geo, *this, pos); - - // Add a bond between the centers of the colliding particles - // The bond is placed on the node that has p1 - if (!p1->is_ghost()) { - const int bondG[] = {c.pp2}; - get_part(cell_structure, c.pp1) - .bonds() - .insert({bond_centers, bondG}); - } - - // Change type of particle being attached, to make it inert - if (p1->type() == part_type_to_be_glued) { - p1->type() = part_type_after_glueing; - } - if (p2->type() == part_type_to_be_glued) { - p2->type() = part_type_after_glueing; - } - - // Vs placement happens on the node that has p1 - if (!attach_vs_to.is_ghost()) { - place_vs_and_relate_to_particle(cell_structure, box_geo, *this, - min_global_cut, current_vs_pid, pos, - attach_vs_to.id()); - // Particle storage locations may have changed due to - // added particle - p1 = cell_structure.get_local_particle(c.pp1); - p2 = cell_structure.get_local_particle(c.pp2); - current_vs_pid++; - } else { // Just update the books - current_vs_pid++; - } - glue_to_surface_bind_part_to_vs(p1, p2, current_vs_pid, *this, - cell_structure); + continue; } - } // we considered the pair - } // Loop over all collisions in the queue + } + + Utils::Vector3d pos; + Particle const &attach_vs_to = + glue_to_surface_calc_vs_pos(*p1, *p2, *system.box_geo, *this, pos); + + // Add a bond between the centers of the colliding particles + // The bond is placed on the node that has p1 + if (!p1->is_ghost()) { + const int bondG[] = {c.pp2}; + get_part(cell_structure, c.pp1).bonds().insert({bond_centers, bondG}); + } + + // Change type of particle being attached, to make it inert + if (p1->type() == part_type_to_be_glued) { + p1->type() = part_type_after_glueing; + } + if (p2->type() == part_type_to_be_glued) { + p2->type() = part_type_after_glueing; + } + + // Vs placement happens on the node that has p1 + if (!attach_vs_to.is_ghost()) { + place_vs_and_relate_to_particle(cell_structure, *system.box_geo, *this, + min_global_cut, current_vs_pid, pos, + attach_vs_to.id()); + // Particle storage locations may have changed due to + // added particle + p1 = cell_structure.get_local_particle(c.pp1); + p2 = cell_structure.get_local_particle(c.pp2); + current_vs_pid++; + } else { // Just update the books + current_vs_pid++; + } + glue_to_surface_bind_part_to_vs(p1, p2, current_vs_pid, *this, + cell_structure); + + } // we considered the pair + } // Loop over all collisions in the queue #ifdef ADDITIONAL_CHECKS - if (!Utils::Mpi::all_compare(comm_cart, current_vs_pid)) { - throw std::runtime_error("Nodes disagree about current_vs_pid"); - } + if (!Utils::Mpi::all_compare(comm_cart, current_vs_pid)) { + throw std::runtime_error("Nodes disagree about current_vs_pid"); + } #endif - // If any node had a collision, all nodes need to resort - if (not global_collision_queue.empty()) { - cell_structure.set_resort_particles(Cells::RESORT_GLOBAL); - cell_structure.update_ghosts_and_resort_particle( - Cells::DATA_PART_PROPERTIES | Cells::DATA_PART_BONDS); - } + // If any node had a collision, all nodes need to resort + if (not global_collision_queue.empty()) { + cell_structure.set_resort_particles(Cells::RESORT_GLOBAL); + cell_structure.update_ghosts_and_resort_particle( + Cells::DATA_PART_PROPERTIES | Cells::DATA_PART_BONDS); system.update_used_propagations(); - } // are we in one of the vs_based methods + } +} #endif // defined VIRTUAL_SITES_RELATIVE - clear_queue(); +void CollisionDetection::initialize() { + auto &system = get_system(); + auto &bonded_ias = *system.bonded_ias; + auto &nonbonded_ias = *system.nonbonded_ias; + // m_protocol->initialize(bonded_ias, nonbonded_ias); + if (m_protocol) { + std::visit( + [&bonded_ias, &nonbonded_ias](auto &protocol) { + return protocol.initialize(bonded_ias, nonbonded_ias); + }, + *m_protocol); + } + system.on_short_range_ia_change(); +} + +void CollisionDetection::set_protocol( + std::shared_ptr protocol) { + m_protocol = std::move(protocol); + initialize(); +} + +void CollisionDetection::unset_protocol() { + m_protocol = nullptr; + auto &system = get_system(); + system.on_short_range_ia_change(); } #endif // COLLISION_DETECTION diff --git a/src/core/collision.hpp b/src/core/collision.hpp index 0a5545c16e5..de5fea091a2 100644 --- a/src/core/collision.hpp +++ b/src/core/collision.hpp @@ -19,30 +19,17 @@ #pragma once -#include "config/config.hpp" - #include "BondList.hpp" #include "Particle.hpp" #include "cell_system/CellStructure.hpp" +#include "collision/protocols.hpp" +#include "config/config.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include "system/Leaf.hpp" +#include +#include /** @brief Protocols for collision handling. */ -enum class CollisionModeType : int { - /** @brief Deactivate collision detection. */ - OFF = 0, - /** @brief Create bond between centers of colliding particles. */ - BIND_CENTERS = 1, - /** - * @brief Create a bond between the centers of the colliding particles, - * plus two virtual sites at the point of collision and bind them - * together. This prevents the particles from sliding against each - * other. Requires VIRTUAL_SITES_RELATIVE. - */ - BIND_VS = 2, - /** @brief Glue a particle to a specific spot on another particle. */ - GLUE_TO_SURF = 3, -}; /// Data type holding the info about a single collision struct CollisionPair { @@ -51,50 +38,31 @@ struct CollisionPair { }; class CollisionDetection : public System::Leaf { -public: - CollisionDetection() - : mode(CollisionModeType::OFF), distance(0.), distance_sq(0.), - bond_centers(-1), bond_vs(-1) {} - - /// collision protocol - CollisionModeType mode; - /// distance at which particles are bound - double distance; - // Square of distance at which particle are bound - double distance_sq; - - /// bond type used between centers of colliding particles - int bond_centers; - /// bond type used between virtual sites - int bond_vs; - /// particle type for virtual sites created on collision - int vs_particle_type; - - /// For mode "glue to surface": The distance from the particle which is to be - /// glued to the new virtual site - double dist_glued_part_to_vs; - /// For mode "glue to surface": The particle type being glued - int part_type_to_be_glued; - /// For mode "glue to surface": The particle type to which the virtual site is - /// attached - int part_type_to_attach_vs_to; - /// Particle type to which the newly glued particle is converted - int part_type_after_glueing; - /** Placement of virtual sites for MODE_VS. - * 0=on same particle as related to, - * 1=on collision partner, - * 0.5=in the middle between - */ - double vs_placement; +public: // private: TODO make private again! + std::shared_ptr m_protocol; +public: + CollisionDetection() = default; + /** @brief Get currently active Collision protocol. */ + auto get_protocol() const { return m_protocol; } + /** @brief Set a new Collision protocol. */ + void set_protocol(std::shared_ptr protocol); + /** @brief Delete the currently active Collision protocol. */ + void unset_protocol(); /** @brief Validates parameters and creates particle types if needed. */ void initialize(); + auto is_off() const { + return m_protocol == nullptr or + std::get_if(m_protocol.get()) != nullptr; + } + auto cutoff() const { - if (mode != CollisionModeType::OFF) { - return distance; + if (m_protocol == nullptr) { + return INACTIVE_CUTOFF; } - return INACTIVE_CUTOFF; + return std::visit([](auto const &protocol) { return protocol.cutoff(); }, + *m_protocol); } /// Handle queued collisions @@ -105,47 +73,21 @@ class CollisionDetection : public System::Leaf { /** @brief Detect (and queue) a collision between the given particles. */ void detect_collision(Particle const &p1, Particle const &p2, double const dist_sq) { - if (dist_sq > distance_sq) - return; - - // If we are in the glue to surface mode, check that the particles - // are of the right type - if (mode == CollisionModeType::GLUE_TO_SURF) - if (!glue_to_surface_criterion(p1, p2)) - return; - - // Ignore virtual particles - if (p1.is_virtual() or p2.is_virtual()) - return; - - // Check, if there's already a bond between the particles - if (pair_bond_exists_on(p1.bonds(), p2.id(), bond_centers)) - return; - - if (pair_bond_exists_on(p2.bonds(), p1.id(), bond_centers)) - return; - - /* If we're still here, there is no previous bond between the particles, - we have a new collision */ - - // do not create bond between ghost particles - if (p1.is_ghost() and p2.is_ghost()) { - return; + if (m_protocol) { + bool collision_detected = std::visit( + [&p1, &p2, dist_sq](auto const &protocol) { + return protocol.detect_collision(p1, p2, dist_sq); + }, + *m_protocol); + + if (collision_detected) { + local_collision_queue.push_back({p1.id(), p2.id()}); + } } - local_collision_queue.push_back({p1.id(), p2.id()}); } - // private: /// During force calculation, colliding particles are recorded in the queue. /// The queue is processed after force calculation, when it is safe to add /// particles. std::vector local_collision_queue; - - /** @brief Check additional criteria for the glue_to_surface collision mode */ - bool glue_to_surface_criterion(Particle const &p1, Particle const &p2) const { - return (((p1.type() == part_type_to_be_glued) and - (p2.type() == part_type_to_attach_vs_to)) or - ((p2.type() == part_type_to_be_glued) and - (p1.type() == part_type_to_attach_vs_to))); - } }; diff --git a/src/core/collision/protocols.hpp b/src/core/collision/protocols.hpp new file mode 100644 index 00000000000..ef289a400b1 --- /dev/null +++ b/src/core/collision/protocols.hpp @@ -0,0 +1,230 @@ +#pragma once + +#include + +#ifdef COLLISION_DETECTION + +#include "bonded_interactions/bonded_interaction_data.hpp" +#include "nonbonded_interactions/nonbonded_interaction_data.hpp" + +#include + +struct CollisionPair; + +namespace Collision { + +class Off { +public: + Off() = default; + + auto cutoff() const { return INACTIVE_CUTOFF; } + + void initialize(BondedInteractionsMap &, InteractionsNonBonded &) { return; } + + void handle_collisions(System::System &system, std::vector &) { + } + bool detect_collision(Particle const &p1, Particle const &p2, + double const dist_sq) const { + return false; + } +}; + +class BindCenters { + +public: + double distance; + // Square of distance at which particle are bound + double distance_sq; + + /// bond type used between centers of colliding particles + int bond_centers; + + BindCenters(double distance, int bond_centers) + : distance{distance}, distance_sq{distance * distance}, + bond_centers{bond_centers} {} + + void initialize(BondedInteractionsMap &bonded_ias, + InteractionsNonBonded &nonbonded_ias); + + auto cutoff() const { return distance; } + + void handle_collisions(System::System &system, + std::vector &local_collision_queue); + + bool detect_collision(Particle const &p1, Particle const &p2, + double const dist_sq) const { + if (dist_sq > distance_sq) + return false; + + // Ignore virtual particles + if (p1.is_virtual() or p2.is_virtual()) + return false; + + // Check, if there's already a bond between the particles + if (pair_bond_exists_on(p1.bonds(), p2.id(), bond_centers)) + return false; + + if (pair_bond_exists_on(p2.bonds(), p1.id(), bond_centers)) + return false; + + /* If we're still here, there is no previous bond between the particles, + we have a new collision */ + + // do not create bond between ghost particles + if (p1.is_ghost() and p2.is_ghost()) { + return false; + } + return true; + } +}; + +#ifdef VIRTUAL_SITES_RELATIVE +class BindVS { +public: + double distance; + // Square of distance at which particle are bound + double distance_sq; + + /// bond type used between centers of colliding particles + int bond_centers; + /// bond type used between virtual sites + int bond_vs; + /** Placement of virtual sites for MODE_VS. + * 0=on same particle as related to, + * 1=on collision partner, + * 0.5=in the middle between + */ + double vs_placement; + /// particle type for virtual sites created on collision + int part_type_vs; + + BindVS(double distance, int bond_centers, int bond_vs, double vs_placement, + int part_type_vs) + : distance{distance}, distance_sq{distance * distance}, + bond_centers{bond_centers}, bond_vs{bond_vs}, + vs_placement{vs_placement}, part_type_vs{part_type_vs} {} + + void initialize(BondedInteractionsMap &bonded_ias, + InteractionsNonBonded &nonbonded_ias); + + auto cutoff() const { return distance; } + + void handle_collisions(System::System &system, + std::vector &local_collision_queue); + + bool detect_collision(Particle const &p1, Particle const &p2, + double const dist_sq) const { + if (dist_sq > distance_sq) + return false; + + // Ignore virtual particles + if (p1.is_virtual() or p2.is_virtual()) + return false; + + // Check, if there's already a bond between the particles + if (pair_bond_exists_on(p1.bonds(), p2.id(), bond_centers)) + return false; + + if (pair_bond_exists_on(p2.bonds(), p1.id(), bond_centers)) + return false; + + /* If we're still here, there is no previous bond between the particles, + we have a new collision */ + + // do not create bond between ghost particles + if (p1.is_ghost() and p2.is_ghost()) { + return false; + } + return true; + } +}; + +class GlueToSurf { +public: + double distance; + // Square of distance at which particle are bound + double distance_sq; + /// bond type used between centers of colliding particles + int bond_centers; + /// bond type used between virtual sites + int bond_vs; + /// particle type for virtual sites created on collision + int part_type_vs; + /// For mode "glue to surface": The distance from the particle which is to be + /// glued to the new virtual site + double dist_glued_part_to_vs; + /// For mode "glue to surface": The particle type being glued + int part_type_to_be_glued; + /// For mode "glue to surface": The particle type to which the virtual site is + /// attached + int part_type_to_attach_vs_to; + /// Particle type to which the newly glued particle is converted + int part_type_after_glueing; + + GlueToSurf(double distance, int bond_centers, int bond_vs, int part_type_vs, + double dist_glued_part_to_vs, int part_type_to_be_glued, + int part_type_to_attach_vs_to, int part_type_after_glueing) + : distance{distance}, distance_sq{distance * distance}, + bond_centers{bond_centers}, bond_vs{bond_vs}, + part_type_vs{part_type_vs}, + dist_glued_part_to_vs{dist_glued_part_to_vs}, + part_type_to_be_glued{part_type_to_be_glued}, + part_type_to_attach_vs_to{part_type_to_attach_vs_to}, + part_type_after_glueing{part_type_after_glueing} {} + + void initialize(BondedInteractionsMap &bonded_ias, + InteractionsNonBonded &nonbonded_ias); + + auto cutoff() const { return distance; } + + /** @brief Check additional criteria for the glue_to_surface collision mode */ + bool glue_to_surface_criterion(Particle const &p1, Particle const &p2) const { + return (((p1.type() == part_type_to_be_glued) and + (p2.type() == part_type_to_attach_vs_to)) or + ((p2.type() == part_type_to_be_glued) and + (p1.type() == part_type_to_attach_vs_to))); + } + + void handle_collisions(System::System &system, + std::vector &local_collision_queue); + + bool detect_collision(Particle const &p1, Particle const &p2, + double const dist_sq) const { + if (dist_sq > distance_sq) + return false; + + // If we are in the glue to surface mode, check that the particles + // are of the right type + + if (!glue_to_surface_criterion(p1, p2)) + return false; + + // Ignore virtual particles + if (p1.is_virtual() or p2.is_virtual()) + return false; + + // Check, if there's already a bond between the particles + if (pair_bond_exists_on(p1.bonds(), p2.id(), bond_centers)) + return false; + + if (pair_bond_exists_on(p2.bonds(), p1.id(), bond_centers)) + return false; + + /* If we're still here, there is no previous bond between the particles, + we have a new collision */ + + // do not create bond between ghost particles + if (p1.is_ghost() and p2.is_ghost()) { + return false; + } + return true; + } +}; +#endif // VIRTUAL_SITES_RELATIVE +using ActiveProtocol = std::variant; +} // namespace Collision +#endif diff --git a/src/core/forces.cpp b/src/core/forces.cpp index b9881e166c0..11844396031 100644 --- a/src/core/forces.cpp +++ b/src/core/forces.cpp @@ -197,7 +197,7 @@ void System::System::calculate_forces() { coulomb_kernel_ptr, dipoles_kernel_ptr, elc_kernel_ptr); #ifdef COLLISION_DETECTION - if (collision_detection.mode != CollisionModeType::OFF) { + if (not collision_detection.is_off()) { collision_detection.detect_collision(p1, p2, d.dist2); } #endif diff --git a/src/core/system/System.cpp b/src/core/system/System.cpp index 676b8241554..1bb7f8d0797 100644 --- a/src/core/system/System.cpp +++ b/src/core/system/System.cpp @@ -503,7 +503,7 @@ unsigned System::get_global_ghost_flags() const { } #ifdef COLLISION_DETECTION - if (collision_detection->mode != CollisionModeType::OFF) { + if (not collision_detection->is_off()) { data_parts |= Cells::DATA_PART_BONDS; } #endif diff --git a/src/python/espressomd/collision_detection.py b/src/python/espressomd/collision_detection.py index ac90777f507..0f17f81d060 100644 --- a/src/python/espressomd/collision_detection.py +++ b/src/python/espressomd/collision_detection.py @@ -40,71 +40,140 @@ class CollisionDetection(ScriptInterfaceHelper): _so_name = "CollisionDetection::CollisionDetection" _so_features = ("COLLISION_DETECTION",) - # Do not allow setting of individual attributes - def __setattr__(self, *args, **kwargs): - raise Exception( - "Please set all parameters at once via collision_detection.set_params()") + """ + Set the parameters for the collision detection + + See :ref:`Creating bonds when particles collide` for detailed instructions. + + + Parameters + ---------- + mode : :obj:`str`, {"off", "bind_centers", "bind_at_point_of_collision", "glue_to_surface"} + Collision detection mode + + distance : :obj:`float` + Distance below which a pair of particles is considered in the + collision detection + + bond_centers : :obj:`espressomd.interactions.BondedInteraction` + Bond to add between the colliding particles + + bond_vs : :obj:`espressomd.interactions.BondedInteraction` + Bond to add between virtual sites (for modes using virtual sites) + + part_type_vs : :obj:`int` + Particle type of the virtual sites being created on collision + (virtual sites based modes) + + part_type_to_be_glued : :obj:`int` + particle type for ``"glue_to_surface"`` mode. See user guide. + + part_type_to_attach_vs_to : :obj:`int` + particle type for ``"glue_to_surface"`` mode. See user guide. + + part_type_after_glueing : :obj:`int` + particle type for ``"glue_to_surface"`` mode. See user guide. + + distance_glued_particle_to_vs : :obj:`float` + Distance for ``"glue_to_surface"`` mode. See user guide. + + """ + - def set_params(self, **kwargs): - """ - Set the parameters for the collision detection +@script_interface_register +class Off(ScriptInterfaceHelper): + + """Collision protocol resulting in ...""" + + _so_name = "CollisionDetection::Off" + _so_features = ("COLLISION_DETECTION",) + + +@script_interface_register +class BindCenters(ScriptInterfaceHelper): + + """Collision protocol for bind_centers. - See :ref:`Creating bonds when particles collide` for detailed instructions. + Parameters + ---------- + distance : :obj:`float` + "desciption...". + bond_centers : :` ` + "description..". + """ + _so_name = "CollisionDetection::BindCenters" + _so_features = ("COLLISION_DETECTION",) - Parameters - ---------- - mode : :obj:`str`, {"off", "bind_centers", "bind_at_point_of_collision", "glue_to_surface"} - Collision detection mode - distance : :obj:`float` - Distance below which a pair of particles is considered in the - collision detection +@script_interface_register +class BindVS(ScriptInterfaceHelper): + + """ + Collision protocol for . + + Parameters + ---------- + distance : :obj:`float` + Distance below which a pair of particles is considered in the + collision detection + + bond_centers : :obj:`espressomd.interactions.BondedInteraction` + Bond to add between the colliding particles + + bond_vs : :obj:`espressomd.interactions.BondedInteraction` + Bond to add between virtual sites (for modes using virtual sites) + + part_type_vs : :obj:`int` + Particle type of the virtual sites being created on collision + (virtual sites based modes) + + vs_placement : :obj:`float` + controls, where on the line connecting the centers of the colliding + particles, the virtual sites are placed. A value of 0 means that the virtual sites + are placed at the same position as the colliding particles on which they are based. + A value of 0.5 will result in the virtual sites being placed at the mid-point between + the two colliding particles. A value of 1 will result the virtual site associated to + the first colliding particle to be placed at the position of the second colliding particle. + In most cases, 0.5, is a good choice. Then, the bond connecting the virtual sites + should have an equilibrium length of zero - bond_centers : :obj:`espressomd.interactions.BondedInteraction` - Bond to add between the colliding particles + """ + _so_name = "CollisionDetection::BindVS" + _so_features = ("COLLISION_DETECTION", "VIRTUAL_SITES_RELATIVE") - bond_vs : :obj:`espressomd.interactions.BondedInteraction` - Bond to add between virtual sites (for modes using virtual sites) - part_type_vs : :obj:`int` - Particle type of the virtual sites being created on collision - (virtual sites based modes) +@script_interface_register +class GlueToSurf(ScriptInterfaceHelper): - part_type_to_be_glued : :obj:`int` - particle type for ``"glue_to_surface"`` mode. See user guide. + """Collision protocol for glue_to_surf. - part_type_to_attach_vs_to : :obj:`int` - particle type for ``"glue_to_surface"`` mode. See user guide. + Parameters + ---------- + distance : :obj:`float` + description... + bond_centers : :obj:`float` + description... + bond_vs : :obj:`espressomd.interactions.BondedInteraction` + Bond to add between virtual sites (for modes using virtual sites) - part_type_after_glueing : :obj:`int` - particle type for ``"glue_to_surface"`` mode. See user guide. + part_type_vs : :obj:`int` + Particle type of the virtual sites being created on collision + (virtual sites based modes) - distance_glued_particle_to_vs : :obj:`float` - Distance for ``"glue_to_surface"`` mode. See user guide. + part_type_to_be_glued : :obj:`int` + particle type for ``"glue_to_surface"`` mode. See user guide. - """ + part_type_to_attach_vs_to : :obj:`int` + particle type for ``"glue_to_surface"`` mode. See user guide. - if "mode" not in kwargs: - raise ValueError( - "Collision mode must be specified via the 'mode' argument") - self.call_method("set_params", **kwargs) + part_type_after_glueing : :obj:`int` + particle type for ``"glue_to_surface"`` mode. See user guide. - def get_parameter(self, name): - value = super().get_parameter(name) - if name in ["bond_centers", "bond_vs"]: - if value == -1: # Not defined - value = None - else: - value = self.call_method("get_bond_by_id", bond_id=value) - return value + distance_glued_particle_to_vs : :obj:`float` + Distance for ``"glue_to_surface"`` mode. See user guide. - def get_params(self): - """Returns the parameters of the collision detection as dict. - """ - params = {} - mode = super().get_parameter("mode") - for name in self.call_method("params_for_mode", mode=mode): - params[name] = self.get_parameter(name) - return params + """ + _so_name = "CollisionDetection::GlueToSurf" + _so_features = ("COLLISION_DETECTION", "VIRTUAL_SITES_RELATIVE") diff --git a/src/script_interface/collision_detection/BindCenters.hpp b/src/script_interface/collision_detection/BindCenters.hpp new file mode 100644 index 00000000000..8833db41747 --- /dev/null +++ b/src/script_interface/collision_detection/BindCenters.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021-2022 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "Protocol.hpp" + +#include "core/collision/protocols.hpp" + +#include "script_interface/ScriptInterface.hpp" +#include "script_interface/auto_parameters/AutoParameters.hpp" + +#include +#include + +namespace ScriptInterface { +namespace Collision { + +class BindCenters : public Protocol { + using CoreClass = ::Collision::BindCenters; + +public: + BindCenters() { + add_parameters( + {{"bond_centers", + [this](Variant const &v) { + std::get(*m_protocol).bond_centers = find_bond_id(v); + }, + [this]() { + return get_bond_variant_by_id( + std::get(*m_protocol).bond_centers); + }}, + {"_bond_centers", AutoParameter::read_only, + [this]() { return std::get(*m_protocol).bond_centers; }}, + {"distance", + [this](Variant const &v) { + std::get(*m_protocol).distance = get_value(v); + }, + [this]() { return std::get(*m_protocol).distance; }}}); + } + std::shared_ptr<::Collision::ActiveProtocol> protocol() override { + return m_protocol; + } + +private: + std::shared_ptr<::Collision::ActiveProtocol> m_protocol; + +protected: + void do_initialize(VariantMap const ¶ms) override { + m_protocol = std::make_shared<::Collision::ActiveProtocol>( + CoreClass(get_value(params, "distance"), + find_bond_id(params.contains("_bond_centers") + ? params.at("_bond_centers") + : params.at("bond_centers")))); + } +}; + +} // namespace Collision +} // namespace ScriptInterface diff --git a/src/script_interface/collision_detection/BindVS.hpp b/src/script_interface/collision_detection/BindVS.hpp new file mode 100644 index 00000000000..9e4b6eecc34 --- /dev/null +++ b/src/script_interface/collision_detection/BindVS.hpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021-2022 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "Protocol.hpp" + +#include "core/collision/protocols.hpp" + +#include "script_interface/ScriptInterface.hpp" +#include "script_interface/auto_parameters/AutoParameters.hpp" + +#include +#include + +namespace ScriptInterface { +namespace Collision { + +class BindVS : public Protocol { + using CoreClass = ::Collision::BindVS; + +public: + BindVS() { + add_parameters({ + {"bond_centers", + [this](Variant const &v) { + std::get(*m_protocol).bond_centers = find_bond_id(v); + }, + [this]() { + return get_bond_variant_by_id( + std::get(*m_protocol).bond_centers); + }}, + {"distance", + [this](Variant const &v) { + std::get(*m_protocol).distance = get_value(v); + }, + [this]() { return std::get(*m_protocol).distance; }}, + {"bond_vs", + [this](Variant const &v) { + std::get(*m_protocol).bond_vs = find_bond_id(v); + }, + [this]() { + return get_bond_variant_by_id( + std::get(*m_protocol).bond_vs); + }}, + {"vs_placement", + [this](Variant const &v) { + std::get(*m_protocol).vs_placement = get_value(v); + }, + [this]() { return std::get(*m_protocol).vs_placement; }}, + {"part_type_vs", + [this](Variant const &v) { + std::get(*m_protocol).part_type_vs = get_value(v); + }, + [this]() { return std::get(*m_protocol).part_type_vs; }}, + }); + } + std::shared_ptr<::Collision::ActiveProtocol> protocol() override { + return m_protocol; + } + +private: + std::shared_ptr<::Collision::ActiveProtocol> m_protocol; + +protected: + void do_initialize(VariantMap const ¶ms) override { + m_protocol = std::make_shared<::Collision::ActiveProtocol>( + CoreClass(get_value(params, "distance"), + find_bond_id(params.at("bond_centers")), + find_bond_id(params.at("bond_vs")), + get_value(params, "vs_placement"), + get_value(params, "part_type_vs"))); + } +}; + +} // namespace Collision +} // namespace ScriptInterface diff --git a/src/script_interface/collision_detection/CollisionDetection.hpp b/src/script_interface/collision_detection/CollisionDetection.hpp index bb1727fd0d8..9dcd1972205 100644 --- a/src/script_interface/collision_detection/CollisionDetection.hpp +++ b/src/script_interface/collision_detection/CollisionDetection.hpp @@ -20,7 +20,7 @@ */ #pragma once - +#include "Protocol.hpp" #include "config/config.hpp" #ifdef COLLISION_DETECTION @@ -33,6 +33,7 @@ #include "core/bonded_interactions/bonded_interaction_data.hpp" #include "core/collision.hpp" +#include "core/collision/protocols.hpp" #include #include @@ -51,215 +52,62 @@ class CollisionDetection : public AutoParameters { std::shared_ptr<::CollisionDetection> m_handle; std::unique_ptr m_params; - std::weak_ptr m_bonded_ias; - - std::unordered_map const cd_mode_to_name = { - {CollisionModeType::OFF, "off"}, - {CollisionModeType::BIND_CENTERS, "bind_centers"}, - {CollisionModeType::BIND_VS, "bind_at_point_of_collision"}, - {CollisionModeType::GLUE_TO_SURF, "glue_to_surface"}, - }; - std::unordered_map cd_name_to_mode; - std::unordered_map> const cd_mode_to_parameters = { - {CollisionModeType::OFF, {"mode"}}, - {CollisionModeType::BIND_CENTERS, {"mode", "bond_centers", "distance"}}, - {CollisionModeType::BIND_VS, - {"mode", "bond_centers", "bond_vs", "part_type_vs", "distance", - "vs_placement"}}, - {CollisionModeType::GLUE_TO_SURF, - {"mode", "bond_centers", "bond_vs", "part_type_vs", - "part_type_to_be_glued", "part_type_to_attach_vs_to", - "part_type_after_glueing", "distance", - "distance_glued_particle_to_vs"}}, - }; - - auto find_bond_id(Variant const &v) const { - auto &system = get_system(); - if (is_type(v)) { - auto const bond_id = get_value(v); - std::optional retval = std::nullopt; - if (system.bonded_ias->contains(bond_id)) { - retval = bond_id; - } - return retval; - } - auto obj = get_value>(v); - return system.bonded_ias->find_bond_id(obj->bonded_ia()); - } + std::shared_ptr m_protocol; + std::weak_ptr m_bonded_ias; + std::weak_ptr m_so_bonded_ias; public: CollisionDetection() { - for (auto const &kv : cd_mode_to_name) { - cd_name_to_mode[kv.second] = kv.first; - } - add_parameters({ - {"mode", - [this](Variant const &v) { - auto const name = get_value(v); - check_mode_name(name); - m_handle->mode = cd_name_to_mode.at(name); - }, - [this]() { return cd_mode_to_name.at(m_handle->mode); }}, - {"bond_centers", - [this](Variant const &v) { - auto const bond_id = find_bond_id(v); - if (not bond_id) { - throw std::invalid_argument("Bond in parameter 'bond_centers' was " - "not added to the system"); - } - m_handle->bond_centers = bond_id.value(); - }, - [this]() { return m_handle->bond_centers; }}, - {"bond_vs", - [this](Variant const &v) { - auto const bond_id = find_bond_id(v); - if (not bond_id) { - throw std::invalid_argument( - "Bond in parameter 'bond_vs' was not added to the system"); - } - m_handle->bond_vs = bond_id.value(); - }, - [this]() { return m_handle->bond_vs; }}, - {"distance", - [this](Variant const &v) { - m_handle->distance = get_value(v); - }, - [this]() { return m_handle->distance; }}, - {"distance_glued_particle_to_vs", - [this](Variant const &v) { - m_handle->dist_glued_part_to_vs = get_value(v); - }, - [this]() { return m_handle->dist_glued_part_to_vs; }}, - {"vs_placement", - [this](Variant const &v) { - m_handle->vs_placement = get_value(v); - }, - [this]() { return m_handle->vs_placement; }}, - {"part_type_vs", - [this](Variant const &v) { - m_handle->vs_particle_type = get_value(v); - }, - [this]() { return m_handle->vs_particle_type; }}, - {"part_type_to_be_glued", - [this](Variant const &v) { - m_handle->part_type_to_be_glued = get_value(v); - }, - [this]() { return m_handle->part_type_to_be_glued; }}, - {"part_type_to_attach_vs_to", - [this](Variant const &v) { - m_handle->part_type_to_attach_vs_to = get_value(v); - }, - [this]() { return m_handle->part_type_to_attach_vs_to; }}, - {"part_type_after_glueing", - [this](Variant const &v) { - m_handle->part_type_after_glueing = get_value(v); - }, - [this]() { return m_handle->part_type_after_glueing; }}, - }); + add_parameters( + {{"protocol", + [this](Variant const &value) { + if (is_none(value)) { + m_protocol = nullptr; + m_handle->unset_protocol(); + return; + } + auto const m_protocol_backup = m_protocol; + try { + context()->parallel_try_catch([&]() { + m_protocol = get_value< + std::shared_ptr>( + value); + m_protocol->bind_bonded_ias(m_bonded_ias, m_so_bonded_ias); + m_handle->set_protocol(m_protocol->protocol()); + }); + } catch (...) { + m_protocol = m_protocol_backup; + if (m_protocol) { + m_handle->set_protocol(m_protocol->protocol()); + } else { + m_protocol = nullptr; + m_handle->unset_protocol(); + } + throw; + } + }, + [this]() { + if (m_protocol) + return make_variant(m_protocol); + return make_variant(none); + }}}); } void do_construct(VariantMap const ¶ms) override { m_params = std::make_unique(params); - if (params.empty()) { - (*m_params)["mode"] = std::string("off"); - } else { - // Assume we are reloading from a checkpoint file. - // This else branch can be removed once issue #4483 is fixed. - m_params = std::make_unique(); - auto const name = get_value(params, "mode"); - check_mode_name(name); - for (auto const &name : - cd_mode_to_parameters.at(cd_name_to_mode.at(name))) { - (*m_params)[name] = params.at(name); - } - (*m_params)["mode"] = params.at("mode"); - } - } - - Variant do_call_method(const std::string &name, - const VariantMap ¶ms) override { - if (name == "set_params") { - context()->parallel_try_catch([this, ¶ms]() { - auto const backup = std::make_shared<::CollisionDetection>(*m_handle); - auto &system = get_system(); - try { - // check provided parameters - check_input_parameters(params); - // set parameters - for (auto const &kv : params) { - do_set_parameter(get_value(kv.first), kv.second); - } - // sanitize parameters and calculate derived parameters - m_handle->initialize(); - return none; - } catch (...) { - // restore original parameters and re-throw exception - m_handle = system.collision_detection = backup; - m_handle->initialize(); - throw; - } - }); - } - if (name == "params_for_mode") { - auto const name = get_value(params, "mode"); - check_mode_name(name); - auto const mode = cd_name_to_mode.at(name); - return make_vector_of_variants(cd_mode_to_parameters.at(mode)); - } - if (name == "get_bond_by_id") { - if (not context()->is_head_node()) { - return {}; - } - return m_bonded_ias.lock()->call_method("get_bond", params); - } - return none; - } - - void attach(std::weak_ptr bonded_ias) { - m_bonded_ias = bonded_ias; - } - -private: - void check_mode_name(std::string const &name) const { - if (not cd_name_to_mode.contains(name)) { - throw std::invalid_argument("Unknown collision mode '" + name + "'"); - } } void on_bind_system(::System::System &system) override { m_handle = system.collision_detection; m_handle->bind_system(m_system.lock()); - if (not m_params->empty()) { - do_call_method("set_params", *m_params); - } - m_params.reset(); + m_bonded_ias = system.bonded_ias; } - void check_input_parameters(VariantMap const ¶ms) const { - auto const name = get_value(params, "mode"); - check_mode_name(name); - auto const mode = cd_name_to_mode.at(name); - auto const expected_params = cd_mode_to_parameters.at(mode); - auto const expected_param_names = - std::set{expected_params.begin(), expected_params.end()}; - std::set input_parameter_names = {}; - for (auto const &kv : params) { - auto const ¶m_name = kv.first; - if (not expected_param_names.contains(param_name)) { - // throw if parameter is unknown - std::ignore = get_parameter(param_name); - // throw if parameter is known but doesn't match the selected mode - throw std::runtime_error("Parameter '" + param_name + "' is not " + - "required for mode '" + name + "'"); - } - input_parameter_names.insert(param_name); - } - for (auto const ¶m_name : expected_param_names) { - if (not input_parameter_names.contains(param_name)) { - throw std::runtime_error("Parameter '" + param_name + "' is " + - "required for mode '" + name + "'"); - } + void attach(std::weak_ptr bonded_ias) { + m_so_bonded_ias = bonded_ias; + if (m_params) { + AutoParameters::do_construct(*m_params); + m_params.reset(); } } }; diff --git a/src/script_interface/collision_detection/GlueToSurf.hpp b/src/script_interface/collision_detection/GlueToSurf.hpp new file mode 100644 index 00000000000..93b6dcf4027 --- /dev/null +++ b/src/script_interface/collision_detection/GlueToSurf.hpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021-2022 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "Protocol.hpp" + +#include "core/collision/protocols.hpp" + +#include "script_interface/ScriptInterface.hpp" +#include "script_interface/auto_parameters/AutoParameters.hpp" + +#include +#include + +namespace ScriptInterface { +namespace Collision { + +class GlueToSurf : public Protocol { + using CoreClass = ::Collision::GlueToSurf; + +public: + GlueToSurf() { + add_parameters( + {{"bond_centers", + [this](Variant const &v) { + std::get(*m_protocol).bond_centers = find_bond_id(v); + }, + [this]() { + return get_bond_variant_by_id( + std::get(*m_protocol).bond_centers); + }}, + {"distance", + [this](Variant const &v) { + std::get(*m_protocol).distance = get_value(v); + }, + [this]() { return std::get(*m_protocol).distance; }}, + {"bond_vs", + [this](Variant const &v) { + std::get(*m_protocol).bond_vs = find_bond_id(v); + }, + [this]() { + return get_bond_variant_by_id( + std::get(*m_protocol).bond_vs); + }}, + {"part_type_vs", + [this](Variant const &v) { + std::get(*m_protocol).part_type_vs = get_value(v); + }, + [this]() { return std::get(*m_protocol).part_type_vs; }}, + {"distance_glued_particle_to_vs", + [this](Variant const &v) { + std::get(*m_protocol).dist_glued_part_to_vs = + get_value(v); + }, + [this]() { + return std::get(*m_protocol).dist_glued_part_to_vs; + }}, + {"part_type_to_be_glued", + [this](Variant const &v) { + std::get(*m_protocol).part_type_to_be_glued = + get_value(v); + }, + [this]() { + return std::get(*m_protocol).part_type_to_be_glued; + }}, + {"part_type_to_attach_vs_to", + [this](Variant const &v) { + std::get(*m_protocol).part_type_to_attach_vs_to = + get_value(v); + }, + [this]() { + return std::get(*m_protocol).part_type_to_attach_vs_to; + }}, + {"part_type_after_glueing", + [this](Variant const &v) { + std::get(*m_protocol).part_type_after_glueing = + get_value(v); + }, + [this]() { + return std::get(*m_protocol).part_type_after_glueing; + }}}); + } + std::shared_ptr<::Collision::ActiveProtocol> protocol() override { + return m_protocol; + } + +private: + std::shared_ptr<::Collision::ActiveProtocol> m_protocol; + +protected: + void do_initialize(VariantMap const ¶ms) override { + m_protocol = std::make_shared<::Collision::ActiveProtocol>( + CoreClass(get_value(params, "distance"), + find_bond_id(params.at("bond_centers")), + find_bond_id(params.at("bond_vs")), + get_value(params, "part_type_vs"), + get_value(params, "distance_glued_particle_to_vs"), + get_value(params, "part_type_to_be_glued"), + get_value(params, "part_type_to_attach_vs_to"), + get_value(params, "part_type_after_glueing"))); + } +}; + +} // namespace Collision +} // namespace ScriptInterface diff --git a/src/script_interface/collision_detection/Off.hpp b/src/script_interface/collision_detection/Off.hpp new file mode 100644 index 00000000000..3686a4b9c7f --- /dev/null +++ b/src/script_interface/collision_detection/Off.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021-2022 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "Protocol.hpp" +#include "core/collision.hpp" +#include "script_interface/ScriptInterface.hpp" +#include "script_interface/auto_parameters/AutoParameters.hpp" + +#include + +namespace ScriptInterface { +namespace Collision { + +class Off : public Protocol { +public: + Off() + : m_protocol{std::make_shared<::Collision::ActiveProtocol>( + ::Collision::Off())} {} + std::shared_ptr<::Collision::ActiveProtocol> protocol() override { + return m_protocol; + } + +protected: + void do_initialize(VariantMap const &) override {} + +private: + std::shared_ptr<::Collision::ActiveProtocol> m_protocol; +}; + +} // namespace Collision +} // namespace ScriptInterface diff --git a/src/script_interface/collision_detection/Protocol.hpp b/src/script_interface/collision_detection/Protocol.hpp new file mode 100644 index 00000000000..1c6fc34cf8f --- /dev/null +++ b/src/script_interface/collision_detection/Protocol.hpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021-2022 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "script_interface/interactions/BondedInteractions.hpp" + +#include "core/bonded_interactions/bonded_interaction_data.hpp" +#include "core/collision/protocols.hpp" + +#include "script_interface/ScriptInterface.hpp" +#include "script_interface/auto_parameters/AutoParameters.hpp" + +#include + +namespace ScriptInterface { +namespace Collision { + +class Protocol : public AutoParameters { + std::weak_ptr m_bonded_ias; + std::weak_ptr m_so_bonded_ias; + std::unique_ptr m_params; + +public: + virtual std::shared_ptr<::Collision::ActiveProtocol> protocol() = 0; + void bind_bonded_ias( + std::weak_ptr bonded_ias, + std::weak_ptr so_bonded_ias) { + m_bonded_ias = std::move(bonded_ias); + m_so_bonded_ias = std::move(so_bonded_ias); + if (m_params) { + do_initialize(*m_params); + m_params.reset(); + } + } + + void do_construct(VariantMap const ¶ms) override { + m_params = std::make_unique(params); + } + +protected: + auto find_bond_id(Variant const &v) const { + auto bonded_ias = m_bonded_ias.lock(); + if (not bonded_ias) { + throw Exception("This protocol is not bound to a system"); + } + std::optional retval = std::nullopt; + if (is_type(v)) { + auto const bond_id = get_value(v); + if (bonded_ias->contains(bond_id)) { + retval = bond_id; + } + } else { + auto obj = get_value>(v); + retval = bonded_ias->find_bond_id(obj->bonded_ia()); + } + if (not retval) { + throw std::invalid_argument("Bond in parameter list was " + "not added to the system"); + } + return *retval; + } + auto get_bond_variant_by_id(int bond_id) { + auto so_bonded_ias = m_so_bonded_ias.lock(); + assert(so_bonded_ias != nullptr); + return so_bonded_ias->do_call_method("get", {{"key", bond_id}}); + } + virtual void do_initialize(VariantMap const ¶ms) = 0; +}; + +} // namespace Collision +} // namespace ScriptInterface diff --git a/src/script_interface/collision_detection/initialize.cpp b/src/script_interface/collision_detection/initialize.cpp index daa06ed44a2..0d6fb68c3bd 100644 --- a/src/script_interface/collision_detection/initialize.cpp +++ b/src/script_interface/collision_detection/initialize.cpp @@ -18,17 +18,26 @@ */ #include "config/config.hpp" -#include "initialize.hpp" - +#include "BindCenters.hpp" +#include "BindVS.hpp" #include "CollisionDetection.hpp" +#include "GlueToSurf.hpp" +#include "Off.hpp" +#include "initialize.hpp" namespace ScriptInterface { namespace CollisionDetection { void initialize(Utils::Factory *om) { #ifdef COLLISION_DETECTION + om->register_new( "CollisionDetection::CollisionDetection"); + om->register_new("CollisionDetection::Off"); + om->register_new("CollisionDetection::BindCenters"); + om->register_new("CollisionDetection::BindVS"); + om->register_new("CollisionDetection::GlueToSurf"); + #endif } } /* namespace CollisionDetection */ diff --git a/src/script_interface/interactions/BondedInteractions.hpp b/src/script_interface/interactions/BondedInteractions.hpp index ac9f9788757..9bb8ebc73d6 100644 --- a/src/script_interface/interactions/BondedInteractions.hpp +++ b/src/script_interface/interactions/BondedInteractions.hpp @@ -92,7 +92,7 @@ class BondedInteractions : public BondedInteractionsBase_t { m_bonds.erase(key); } -protected: +public: Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { if (name == "get_size") { diff --git a/testsuite/python/bond_breakage.py b/testsuite/python/bond_breakage.py index c0565641d8a..ea123b1541f 100644 --- a/testsuite/python/bond_breakage.py +++ b/testsuite/python/bond_breakage.py @@ -265,6 +265,7 @@ def tearDown(self): self.system.part.clear() self.system.bonded_inter.clear() self.system.thermostat.turn_off() + self.system.min_global_cut = 0.6 @utx.skipIfMissingFeatures(["COLLISION_DETECTION"]) def test_center_bonds(self): @@ -274,16 +275,17 @@ def test_center_bonds(self): crit = 2**(1 / 6) * 2. - self.system.collision_detection.set_params(mode="bind_centers", - distance=2**(1 / 6) * 2.2, bond_centers=harm) + self.system.collision_detection.protocol = espressomd.collision_detection.BindCenters( + distance=2**(1 / 6) * 2.2, bond_centers=harm) self.system.integrator.run(1) - self.system.collision_detection.set_params(mode="off") + self.system.collision_detection.protocol = espressomd.collision_detection.Off() self.system.bond_breakage[harm] = BreakageSpec( breakage_length=crit, action_type="delete_bond") self.system.integrator.run(1) bonds_dist = 0 + self.system.min_global_cut = crit pairs = self.system.cell_system.get_pairs(crit, types=[0]) for pair in pairs: dist = self.system.distance( @@ -307,12 +309,11 @@ def test_vs_bonds(self): crit = 2**(1 / 6) * 1.5 crit_vs = 2**(1 / 6) * 1 / 3 * 1.2 - self.system.collision_detection.set_params(mode="bind_at_point_of_collision", - distance=crit, bond_centers=virt, bond_vs=harm, - part_type_vs=1, vs_placement=1 / 3) + self.system.collision_detection.protocol = espressomd.collision_detection.BindVS( + distance=crit, bond_centers=virt, bond_vs=harm, part_type_vs=1, vs_placement=1 / 3) self.system.integrator.run(1) - self.system.collision_detection.set_params(mode="off") + self.system.collision_detection.protocol = espressomd.collision_detection.Off() self.system.bond_breakage[harm] = BreakageSpec( breakage_length=crit_vs, action_type="revert_bind_at_point_of_collision") self.system.integrator.run(1) diff --git a/testsuite/python/collision_detection.py b/testsuite/python/collision_detection.py index 1f0a2ae2411..6a5b515f751 100644 --- a/testsuite/python/collision_detection.py +++ b/testsuite/python/collision_detection.py @@ -64,7 +64,7 @@ def get_state_set_state_consistency(self): def test_bind_centers(self): system = self.system # Check that it leaves particles alone, when off - system.collision_detection.set_params(mode="off") + system.collision_detection.protocol = espressomd.collision_detection.Off() system.part.clear() p0 = system.part.add(pos=(0, 0, 0), id=0) @@ -74,10 +74,9 @@ def test_bind_centers(self): self.assertEqual(p0.bonds, ()) self.assertEqual(p1.bonds, ()) self.assertEqual(p2.bonds, ()) - # Check that it cannot be activated - system.collision_detection.set_params( - mode="bind_centers", distance=0.11, bond_centers=self.bond_center) + system.collision_detection.protocol = espressomd.collision_detection.BindCenters( + distance=0.11, bond_centers=self.bond_center) self.get_state_set_state_consistency() system.integrator.run(1, recalc_forces=True) bond0 = ((system.bonded_inter[0], 1),) @@ -93,9 +92,10 @@ def test_bind_centers(self): self.assertEqual(p2.bonds, ()) # Check turning it off - system.collision_detection.set_params(mode="off") + system.collision_detection.protocol = espressomd.collision_detection.Off() self.get_state_set_state_consistency() - self.assertEqual(system.collision_detection.mode, "off") + self.assertIsInstance( + self.system.collision_detection.protocol, espressomd.collision_detection.Off) def run_test_bind_at_point_of_collision_for_pos(self, *positions): system = self.system @@ -113,9 +113,8 @@ def run_test_bind_at_point_of_collision_for_pos(self, *positions): # 2 non-virtual + 2 virtual + one that doesn't take part expected_np = 4 * len(positions) + 1 - system.collision_detection.set_params( - mode="bind_at_point_of_collision", bond_centers=self.bond_center, - bond_vs=self.bond_vs, part_type_vs=1, vs_placement=0.4, distance=0.11) + system.collision_detection.protocol = espressomd.collision_detection.BindVS( + bond_centers=self.bond_center, bond_vs=self.bond_vs, part_type_vs=1, vs_placement=0.4, distance=0.11) self.get_state_set_state_consistency() system.integrator.run(1, recalc_forces=True) self.verify_state_after_bind_at_poc(expected_np) @@ -132,7 +131,7 @@ def run_test_bind_at_point_of_collision_for_pos(self, *positions): self.verify_state_after_bind_at_poc(expected_np) def verify_state_after_bind_at_poc(self, expected_np): - if self.system.collision_detection.bond_vs == self.bond_angle_vs: + if self.system.collision_detection.protocol.bond_vs == self.bond_angle_vs: self.verify_state_after_bind_at_poc_triplet(expected_np) else: self.verify_state_after_bind_at_poc_pair(expected_np) @@ -227,7 +226,7 @@ def verify_bind_at_poc(self, p1, p2, vs1, vs2): system = self.system # Check for presence of vs # Check for bond between vs - if self.system.collision_detection.bond_vs == self.bond_angle_vs: + if self.system.collision_detection.protocol.bond_vs == self.bond_angle_vs: bond_p1 = ((self.bond_pair, p2.id), (self.bond_center, p2.id),) bond_p2 = ((self.bond_pair, p1.id), (self.bond_center, p1.id),) self.assertTrue(p1.bonds == bond_p1 or p2.bonds == bond_p2) @@ -265,7 +264,7 @@ def verify_bind_at_poc(self, p1, p2, vs1, vs2): else: dist_centers = p1.pos - p2.pos expected_pos = system.part.by_id(rel_to).pos_folded + \ - system.collision_detection.vs_placement * dist_centers + system.collision_detection.protocol.vs_placement * dist_centers dist = expected_pos - p.pos_folded dist -= np.round(dist / system.box_l) * system.box_l self.assertLess(np.linalg.norm(dist), 1E-12) @@ -311,8 +310,8 @@ def test_bind_at_point_of_collision_triplet(self): # 2 non-virtual + 2 virtual + one that doesn't take part expected_np = 4 * len(positions) + 1 - system.collision_detection.set_params( - mode="bind_at_point_of_collision", bond_centers=self.bond_center, + system.collision_detection.protocol = espressomd.collision_detection.BindVS( + bond_centers=self.bond_center, bond_vs=self.bond_angle_vs, part_type_vs=1, vs_placement=0.4, distance=0.11) self.get_state_set_state_consistency() system.integrator.run(1, recalc_forces=True) @@ -355,13 +354,8 @@ def test_bind_at_point_of_collision_random(self): system.integrator.run(10) # Collision detection - system.collision_detection.set_params( - mode="bind_at_point_of_collision", - distance=0.11, - bond_centers=self.bond_center, - bond_vs=self.bond_vs, - part_type_vs=1, - vs_placement=0.4) + system.collision_detection.protocol = espressomd.collision_detection.BindVS( + bond_centers=self.bond_center, bond_vs=self.bond_vs, part_type_vs=1, vs_placement=0.4, distance=0.11) self.get_state_set_state_consistency() # Integrate lj liquid @@ -436,8 +430,8 @@ def run_test_glue_to_surface_for_pos(self, *positions): # 2 non-virtual + 1 virtual + one that doesn't take part expected_np = 3 * len(positions) + 1 - system.collision_detection.set_params( - mode="glue_to_surface", distance=0.11, + system.collision_detection.protocol = espressomd.collision_detection.GlueToSurf( + distance=0.11, distance_glued_particle_to_vs=0.02, bond_centers=self.bond_center, bond_vs=self.bond_vs, part_type_vs=self.part_type_vs, part_type_to_attach_vs_to=self.part_type_to_attach_vs_to, @@ -595,8 +589,8 @@ def test_glue_to_surface_random(self): system.integrator.run(10) # Collision detection - system.collision_detection.set_params( - mode="glue_to_surface", distance=0.11, + system.collision_detection.protocol = espressomd.collision_detection.GlueToSurf( + distance=0.11, distance_glued_particle_to_vs=0.02, bond_centers=self.bond_center, bond_vs=self.bond_vs, part_type_vs=self.part_type_vs, part_type_to_attach_vs_to=self.part_type_to_attach_vs_to, diff --git a/testsuite/python/collision_detection_interface.py b/testsuite/python/collision_detection_interface.py index 3e2e5dbe7a9..c2b398c1ca3 100644 --- a/testsuite/python/collision_detection_interface.py +++ b/testsuite/python/collision_detection_interface.py @@ -53,35 +53,22 @@ class CollisionDetection(ut.TestCase): "part_type_to_be_glued": 2, "part_type_after_glueing": 3 }, } + classes = {"bind_centers": espressomd.collision_detection.BindCenters, + "bind_at_point_of_collision": espressomd.collision_detection.BindVS, + "glue_to_surface": espressomd.collision_detection.GlueToSurf} def tearDown(self): - self.system.collision_detection.set_params(mode="off") + self.system.collision_detection.protocol = None def test_00_interface_and_defaults(self): - # Is it off by default - self.assertEqual(self.system.collision_detection.mode, "off") - - # Make sure params cannot be set individually - with self.assertRaises(Exception): - self.system.collision_detection.mode = "bind_centers" - - # Verify exception throwing for unknown collision modes - with self.assertRaisesRegex(ValueError, "Unknown collision mode 'unknown'"): - self.system.collision_detection.set_params(mode="unknown") - with self.assertRaisesRegex(ValueError, "Collision mode must be specified via the 'mode' argument"): - self.system.collision_detection.set_params() - + self.assertIsNone(self.system.collision_detection.protocol, None) self.assertIsNone(self.system.collision_detection.call_method("none")) - # That should work - self.system.collision_detection.set_params(mode="off") - self.assertEqual(self.system.collision_detection.mode, "off") - def check_stored_parameters(self, mode, **kwargs): """ Check if collision detection stored parameters match input values. """ - parameters = self.system.collision_detection.get_params() + parameters = self.system.collision_detection.protocol.get_params() parameters_ref = self.valid_coldet_params[mode].copy() parameters_ref.update(kwargs) for key, value_ref in parameters_ref.items(): @@ -96,7 +83,7 @@ def set_coldet(self, mode, **invalid_params): """ params = self.valid_coldet_params.get(mode).copy() params.update(invalid_params) - self.system.collision_detection.set_params(mode=mode, **params) + self.system.collision_detection.protocol = self.classes[mode](**params) def test_bind_centers(self): self.set_coldet("bind_centers", distance=0.5) @@ -104,20 +91,14 @@ def test_bind_centers(self): self.set_coldet("bind_centers", distance=-2.) with self.assertRaisesRegex(ValueError, "Parameter 'distance' must be > 0"): self.set_coldet("bind_centers", distance=0.) - with self.assertRaisesRegex(ValueError, "Bond in parameter 'bond_centers' was not added to the system"): + with self.assertRaisesRegex(ValueError, "Bond in parameter list was not added to the system"): bond = espressomd.interactions.HarmonicBond(k=1., r_0=0.1) self.set_coldet("bind_centers", bond_centers=bond) with self.assertRaisesRegex(RuntimeError, "The bond type to be used for binding particle centers needs to be a pair bond"): self.set_coldet("bind_centers", bond_centers=self.bond_angle) - with self.assertRaisesRegex(RuntimeError, "Unknown parameter 'unknown'"): - self.set_coldet("bind_centers", unknown=1) - with self.assertRaisesRegex(RuntimeError, "Parameter 'part_type_vs' is not required for mode 'bind_centers'"): - self.set_coldet("bind_centers", part_type_vs=1) - with self.assertRaisesRegex(RuntimeError, "Parameter 'distance' is required for mode 'bind_centers'"): - self.system.collision_detection.set_params( - mode="bind_centers", bond_centers=self.bond_harmonic) - with self.assertRaisesRegex(Exception, "Please set all parameters at once via collision_detection.set_params"): - self.system.collision_detection.mode = "bind_at_point_of_collision" + with self.assertRaisesRegex(RuntimeError, "Parameter 'distance' is missing"): + self.system.collision_detection.protocol = espressomd.collision_detection.BindCenters( + bond_centers=self.bond_harmonic) # check if original parameters have been preserved self.check_stored_parameters("bind_centers", distance=0.5) @@ -128,7 +109,7 @@ def test_bind_at_point_of_collision(self): self.set_coldet("bind_at_point_of_collision", vs_placement=-0.01) with self.assertRaisesRegex(ValueError, "Parameter 'vs_placement' must be between 0 and 1"): self.set_coldet("bind_at_point_of_collision", vs_placement=1.01) - with self.assertRaisesRegex(ValueError, "Bond in parameter 'bond_vs' was not added to the system"): + with self.assertRaisesRegex(ValueError, "Bond in parameter list was not added to the system"): bond = espressomd.interactions.HarmonicBond(k=1., r_0=0.1) self.set_coldet("bind_at_point_of_collision", bond_vs=bond) with self.assertRaisesRegex(RuntimeError, "bond type to be used for binding virtual sites needs to be a pair bond"): diff --git a/testsuite/python/lees_edwards.py b/testsuite/python/lees_edwards.py index 4ebdb2dafbc..6a9e5e0caf7 100644 --- a/testsuite/python/lees_edwards.py +++ b/testsuite/python/lees_edwards.py @@ -78,7 +78,7 @@ def tearDown(self): system.bonded_inter.clear() system.lees_edwards.protocol = None if espressomd.has_features("COLLISION_DETECTION"): - system.collision_detection.set_params(mode="off") + system.collision_detection.protocol = espressomd.collision_detection.Off() def test_00_is_none_by_default(self): @@ -616,8 +616,8 @@ def test_le_colldet(self): virt = espressomd.interactions.Virtual() system.bonded_inter.add(virt) - system.collision_detection.set_params( - mode="bind_centers", distance=1., bond_centers=harm) + system.collision_detection.protocol = espressomd.collision_detection.BindCenters( + distance=1., bond_centers=harm) # After two integration steps we should not have a bond, # as the collision detection uses the distant calculation @@ -634,15 +634,14 @@ def test_le_colldet(self): np.testing.assert_array_equal(len(bond_list), 1) system.part.clear() - system.collision_detection.set_params(mode="off") + system.collision_detection.protocol = espressomd.collision_detection.Off() system.time = 0 system.lees_edwards.protocol = espressomd.lees_edwards.LinearShear( shear_velocity=-1.0, initial_pos_offset=0.0) - system.collision_detection.set_params( - mode="bind_at_point_of_collision", distance=1., bond_centers=virt, - bond_vs=harm, part_type_vs=31, vs_placement=1 / 3) + system.collision_detection.protocol = espressomd.collision_detection.BindVS(distance=1., bond_centers=virt, + bond_vs=harm, part_type_vs=31, vs_placement=1 / 3) col_part1 = system.part.add( pos=(2.5, 4.5, 2.5), type=30, fix=[True, True, True]) diff --git a/testsuite/python/save_checkpoint.py b/testsuite/python/save_checkpoint.py index 839687b2f4b..39e8055e637 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -325,8 +325,8 @@ checkpoint.register("particle_force0") checkpoint.register("particle_force1") if espressomd.has_features("COLLISION_DETECTION"): - system.collision_detection.set_params( - mode="bind_centers", distance=0.11, bond_centers=harmonic_bond) + system.collision_detection.protocol = espressomd.collision_detection.BindCenters( + distance=0.11, bond_centers=harmonic_bond) particle_propagation0 = p1.propagation particle_propagation1 = p2.propagation diff --git a/testsuite/python/test_checkpoint.py b/testsuite/python/test_checkpoint.py index 6d096aca7fd..a89b6a7de07 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -920,10 +920,11 @@ def test_comfixed(self): @utx.skipIfMissingFeatures('COLLISION_DETECTION') def test_collision_detection(self): - coldet = system.collision_detection - self.assertEqual(coldet.mode, "bind_centers") - self.assertAlmostEqual(coldet.distance, 0.11, delta=1E-9) - self.assertEqual(coldet.bond_centers, system.bonded_inter[0]) + protocol = system.collision_detection.protocol + self.assertIsInstance( + protocol, espressomd.collision_detection.BindCenters) + self.assertAlmostEqual(protocol.distance, 0.11, delta=1E-9) + self.assertEqual(protocol.bond_centers, system.bonded_inter[0]) @utx.skipIfMissingFeatures('EXCLUSIONS') def test_exclusions(self):