Closed jngrad closed 2 months ago
The collision detection feature is explained in more details in the user guide section Creating bonds when particles collide. We would like to refactor the feature to make it easier to extend with new methods. @christophlohrmann has a research project where particles, upon collision with a wall, remain bound to the wall by a newly created particle inside the wall. This has applications in bacterial biofilm formation, but could also be used to model soot formation on surfaces, or binding of organic molecules to metallic catalysts. Offline discussions with @RudolfWeeber also lead to the decision to remove the angle bond formation method.
The task can be broken down as follows:
bind_three_particles
collision method
src/core/collision.hpp
src/core/collision.cpp
src/script_interface/collision_detection/CollisionDetection.hpp
src/python/espressomd/collision_detection.py
testsuite/python/collision_detection.py
testsuite/python/collision_detection_interface.py
doc/sphinx/advanced_methods.rst
cmake . -D ESPRESSO_CTEST_ARGS=-j$(nproc)
once to set the project to use all CPU cores during testingmake check_python_skip_long
to run the testsuite and make sure the method is no longer usedCollision_parameters
into multiple structs CollisionOff
, CollisionBindCenters
, CollisionBindVS
, CollisionGlueToSurf
, containing only the subset of parameters that are needed (the compiler will notify you if a parameter is missing), and split the Collision_parameters::initialize()
method accordinglycollision_params
from Collision_parameters
to a std::variant<CollisionOff,CollisionBindCenters,CollisionBindVS,CollisionGlueToSurf>
std::visit(visitor, collision_params)
to execute a collision method
visitor
is an instance of a struct or class that carries out an operation using the underlying object in the collision_params
(it's passed as first argument to the overloaded functions of the visitor)std::variant
and std::visit
rather than the corresponding Boost functionality, because variants are now part of the C++ standard librarysrc/script_interface/lees_edwards/LeesEdwards.hpp
, src/script_interface/lees_edwards/initialize.cpp
and src/python/espressomd/lees_edwards.py
as templatesResults of offline discussion
I think, the final interface should look as follows
center_bond = ...
vs_bond = ...
system.bonded_inter.add(c_bond)
system.bonded_inter.add(vs_bond)
bind_at_point_of_collision = espressomd.collision_detection.BindAtPointOfCollision(
vs_placement=0.5, center_bond=center_bond, vs_bond=vs_bond)
change_types = espressomd.collision_detection.ChangeTypes(from=(0,0), to=(1,1))
bind_centers = espressomd.collision_detection.BindCenters(bond=center_bond)
system.collision_detection.add(
p_types=(0,0), distance=1.1*sigma,
actions=[bind_at_point_of_collision,change_particle_types])
)
or p_types=(-1,-1)
(or None
in Python to match all particle types).
I.e., on the C++ side, this is a
struct CollisionRule {
std::pair<size_t,size_t> particle_types;
double distance;
std::vector<CollisionAction> actions;
}
std::vector<CollisionRule> collision_rules;
struct Collision {
int p_id1;
int p_id2;
std::vector<CollisionAction> actions;
}
std::vector<Collision> collision_queue;
The individual actions should support (on the C++ side)
bool are_bound(Particle const&, Particle const&)
which determines if two particle are already bound. This would typically be done by checking the presence of the center bondbool run(CellStructure&, Particle&, Particle&)
which performs the actionthe detect_collision(CellStructure&, Particle&, Particle& )
called in the hot loop would
collision_rules
are_bound()
function on the pair. If that returns false, queue the pairbool types_match(std::pair<size_t,size_t> target_types, size_t type1, size_t type2)
type1
and type2
match the types in target_types
(which come from the collision_rule
)So intermediate steps could be:
CollisionAction
(bind_centers
, bind_at_point_of_collision
, etc.) as global parameterbind_centers
(center bond), bind_at_point_of_collision
(center bond, vs bond, vs placement), glue to surface (bonds, vs placement, new particle types) into separate structs collision_rules
vector and use it in detect_collision()
This can be merged in steps:
Here are the updated instructions:
CollisionDetection::initialize()
and CollisionDetection::handle_collisions()
and the associated data members into a self-contained struct called MergedProtocol
, of which a shared pointer will be stored in CollisionDetection
as std::shared_ptr<MergedProtocol> m_protocol
src/core/collision.hpp
and src/core/collision.cpp
MergedProtocol
into multiple structs called Off
, BindCenters
, BindVS
, GlueToSurf
, and store them inside a namespace Collision
in a new file src/core/collision/protocols.hpp
; store the CollisionDetection
class inside a namespace Collision
into a new file src/core/collision/collision.hpp
initialize()
and handle_collisions()
methods accordinglyusing ActiveProtocol = std::variant<Off,BindCenters,BindVS,GlueToSurf>
CollisionDetection::m_protocol
from std::shared_ptr<MergedProtocol>
to std::shared_ptr<ActiveProtocol>
std::visit(visitor, m_protocol)
to execute a collision protocol
visitor
is an instance of a struct or class that carries out an operation using the underlying object in the m_protocol
(it's passed as first argument to the overloaded functions of the visitor)std::variant
and std::visit
rather than the corresponding Boost functionality, because variants are now part of the C++ standard librarysrc/core/lees_edwards/lees_edwards.hpp
and src/core/lees_edwards/protocols.hpp
as a guidesrc/script_interface/lees_edwards/LeesEdwards.hpp
, src/script_interface/lees_edwards/initialize.cpp
and src/python/espressomd/lees_edwards.py
as a guide
The collision detection offers several protocols for binding particles together. Some of these protocols rely on specific features, such as
VIRTUAL_SITES
andVIRTUAL_SITES_RELATIVE
. These protocols have different parameters, although with some overlap. For historical reasons, these parameters have been packed into a single global structCollision_parameters
, and there is only one class constructor in C++ and in Cython (thesystem.collision_detection.set_params()
method), where all parameters are mixed.This design decision has several major drawbacks:
dict
or a C++std::map
containing for each protocol the set of parameters that are validdict
, then all parameters that are irrelevant to the currently active protocol are deleted from thedict
__setattr__()
method to make them read-only, since we cannot guarantee that changing one parameter will leave the struct in a valid stateif/else
acting on the enum value that encodes which protocol is currently activeset_params()
with invalid arguments overwrites the global state of the feature with invalid values that throw an exception, but leaves the system in an indeterminate state until the next call ofset_params()
with valid arguments (fixed by fe12f7425d32aa48ad712c6b464f0fb672bb506b)The collision detection interface could be redesigned by splitting the global struct
Collision_parameters
into individual structs, one for each protocol. This would make them completely independent from one another, would significantly reduce the complexity of the C++ script interface class, remove the need to curate the list of valid parameters in astd::map
ordict
, and allow the user to instantiate multiple collision detection objects in the same pypresso script (only one of them would be activate at a time). The bond breakage feature can serve as a blueprint for the new collision detection interface.