22#include "reaction_methods/ReactionAlgorithm.hpp"
29#include "system/System.hpp"
34#include <boost/mpi/collectives/all_reduce.hpp>
35#include <boost/mpi/collectives/broadcast.hpp>
36#include <boost/serialization/serialization.hpp>
54template <
typename Archive,
typename... Types>
55void serialize(Archive &ar, std::tuple<Types...> &tuple,
const unsigned int) {
56 std::apply([&](
auto &...item) { ((ar & item), ...); }, tuple);
66 std::shared_ptr<SingleReaction>
const &new_reaction) {
69 for (
int reactant_type : new_reaction->reactant_types)
71 for (
int product_type : new_reaction->product_types)
88 auto const &box_geo = *system.box_geo;
90 for (
auto const &state : {old_state.changed, old_state.hidden}) {
91 for (
auto const &[p_id, p_type] : state) {
93 if (
auto p = get_local_particle(p_id)) {
102 for (
auto const p_id : old_state.created) {
106 for (
auto const &[p_id, pos, vel] : old_state.moved) {
107 if (
auto p = get_local_particle(p_id)) {
109 auto folded_pos = pos;
111 box_geo.fold_position(folded_pos, image_box);
112 p->pos() = folded_pos;
113 p->image_box() = image_box;
116 if (not old_state.moved.empty()) {
118 auto &cell_structure = *system.cell_structure;
121 system.on_particle_change();
131 volume = box_geo.volume();
139 bool enough_particles =
true;
140 for (
int i = 0; i < current_reaction.
reactant_types.size(); i++) {
144 enough_particles =
false;
148 return enough_particles;
156 auto const get_random_p_id_of_type = [
this](
int type) {
160 auto only_local_changes =
true;
161 for (
int i = 0; i < std::min(n_product_types, n_reactant_types); i++) {
170 only_local_changes =
false;
173 for (
int j = 0; j < std::min(n_product_coef, n_reactant_coef); j++) {
174 auto const p_id = get_random_p_id_of_type(old_type);
176 if (
auto p = get_local_particle(p_id)) {
177 p->type() = new_type;
182 bookkeeping.
changed.emplace_back(p_id, old_type);
188 auto const delta_n = n_product_coef - n_reactant_coef;
191 for (
int j = 0; j < delta_n; j++) {
192 auto const p_id = create_particle(type);
193 check_exclusion_range(p_id, type);
194 bookkeeping.
created.emplace_back(p_id);
196 only_local_changes =
false;
197 }
else if (delta_n < 0) {
199 for (
int j = 0; j < -delta_n; j++) {
200 auto const p_id = get_random_p_id_of_type(type);
201 bookkeeping.
hidden.emplace_back(p_id, type);
202 check_exclusion_range(p_id, type);
203 hide_particle(p_id, type);
205 only_local_changes =
false;
209 for (
auto i = std::min(n_product_types, n_reactant_types);
210 i < std::max(n_product_types, n_reactant_types); i++) {
211 if (n_product_types < n_reactant_types) {
215 auto const p_id = get_random_p_id_of_type(type);
216 bookkeeping.
hidden.emplace_back(p_id, type);
217 check_exclusion_range(p_id, type);
218 hide_particle(p_id, type);
224 auto const p_id = create_particle(type);
225 check_exclusion_range(p_id, type);
226 bookkeeping.
created.emplace_back(p_id);
231 if (n_product_types == n_reactant_types and only_local_changes) {
238std::unordered_map<int, int>
239ReactionAlgorithm::get_particle_numbers(
SingleReaction const &reaction)
const {
240 std::unordered_map<int, int> particle_numbers;
242 for (
int type : reaction.reactant_types) {
246 for (
int type : reaction.product_types) {
249 return particle_numbers;
254 auto &reaction = *
reactions[reaction_id];
255 reaction.tried_moves++;
263 bookkeeping.reaction_id = reaction_id;
264 bookkeeping.old_particle_numbers = get_particle_numbers(reaction);
266 auto E_pot_new = std::numeric_limits<double>::max();
277 auto const exponential = std::exp(-(E_pot_new - E_pot_old) /
kT);
278 auto &reaction = *
reactions[reaction_id];
279 reaction.accumulator_potential_energy_difference_exponential(
280 std::vector<double>{exponential});
281 if (get_random_uniform_number() >= bf) {
290 reaction.accepted_moves++;
311void ReactionAlgorithm::hide_particle(
int p_id,
int p_type)
const {
313 if (
auto p = get_local_particle(p_id)) {
324void ReactionAlgorithm::check_exclusion_range(
int p_id,
int p_type) {
333 auto p1_ptr = get_real_particle(p_id);
335 std::vector<int> particle_ids;
339 std::erase(all_ids, p_id);
340 particle_ids = all_ids;
343 system.on_observable_calc();
344 auto const local_ids =
346 assert(p1_ptr ==
nullptr or !!local_ids);
348 particle_ids = std::move(*local_ids);
352 if (p1_ptr !=
nullptr) {
355 auto const &box_geo = *system.box_geo;
356 auto &cell_structure = *system.cell_structure;
360 for (
auto const p2_id : particle_ids) {
361 if (
auto const p2_ptr = cell_structure.get_local_particle(p2_id)) {
362 auto const &p2 = *p2_ptr;
363 double excluded_distance;
374 auto const d_min = box_geo.get_mi_vector(p2.pos(), p1.pos()).norm();
376 if (d_min < excluded_distance) {
382 if (m_comm.rank() != 0) {
385 }
else if (m_comm.rank() == 0) {
386 m_comm.recv(boost::mpi::any_source, 1,
400 throw std::domain_error(
"Invalid particle id: " + std::to_string(p_id));
403 if (p_id == old_max_seen_id) {
410 if ((*p_id_iter) >= old_max_seen_id)
416 }
else if (p_id <= old_max_seen_id) {
420 throw std::runtime_error(
421 "Particle id is greater than the max seen particle id");
428 if (center_x < 0. or center_x > box_geo.length()[0])
429 throw std::domain_error(
"center_x is outside the box");
430 if (center_y < 0. or center_y > box_geo.length()[1])
431 throw std::domain_error(
"center_y is outside the box");
433 throw std::domain_error(
"radius is invalid");
436 m_cyl_radius = radius;
437 m_reaction_constraint = ReactionConstraint::CYL_Z;
443 if (slab_start_z < 0. or slab_start_z > box_geo.length()[2])
444 throw std::domain_error(
"slab_start_z is outside the box");
445 if (slab_end_z < 0. or slab_end_z > box_geo.length()[2])
446 throw std::domain_error(
"slab_end_z is outside the box");
447 if (slab_end_z < slab_start_z)
448 throw std::domain_error(
"slab_end_z must be >= slab_start_z");
449 m_slab_start_z = slab_start_z;
450 m_slab_end_z = slab_end_z;
451 m_reaction_constraint = ReactionConstraint::SLAB_Z;
461 if (m_reaction_constraint == ReactionConstraint::CYL_Z) {
464 auto const random_radius =
465 m_cyl_radius * std::sqrt(m_uniform_real_distribution(m_generator));
466 auto const random_phi =
467 2. * std::numbers::pi * m_uniform_real_distribution(m_generator);
468 out_pos[0] = m_cyl_x + random_radius * cos(random_phi);
469 out_pos[1] = m_cyl_y + random_radius * sin(random_phi);
470 out_pos[2] = box_geo.length()[2] * m_uniform_real_distribution(m_generator);
471 }
else if (m_reaction_constraint == ReactionConstraint::SLAB_Z) {
472 out_pos[0] = box_geo.length()[0] * m_uniform_real_distribution(m_generator);
473 out_pos[1] = box_geo.length()[1] * m_uniform_real_distribution(m_generator);
474 out_pos[2] = m_slab_start_z + (m_slab_end_z - m_slab_start_z) *
475 m_uniform_real_distribution(m_generator);
477 assert(m_reaction_constraint == ReactionConstraint::NONE);
478 out_pos[0] = box_geo.length()[0] * m_uniform_real_distribution(m_generator);
479 out_pos[1] = box_geo.length()[1] * m_uniform_real_distribution(m_generator);
480 out_pos[2] = box_geo.length()[2] * m_uniform_real_distribution(m_generator);
488int ReactionAlgorithm::create_particle(
int p_type) {
491 auto p_id_iter = std::min_element(
502 auto vel = get_random_velocity_vector();
505 if (
auto p = get_local_particle(p_id)) {
506 p->v() = std::sqrt(
kT / p->mass()) * vel;
520 std::vector<int> drawn_pids{p_id};
521 for (
int i = 0; i < n_particles; i++) {
527 drawn_pids.emplace_back(p_id);
531 auto vel = get_random_velocity_vector();
532 if (
auto p = get_real_particle(p_id)) {
533 old_state = {p_id, p->pos(), p->v()};
534 p->v() = std::sqrt(
kT / p->mass()) * vel;
535 if (m_comm.rank() != 0) {
536 m_comm.send(0, 42, old_state);
538 }
else if (m_comm.rank() == 0) {
539 m_comm.recv(boost::mpi::any_source, 42, old_state);
541 boost::mpi::broadcast(m_comm, old_state, 0);
542 bookkeeping.moved.emplace_back(old_state);
545 check_exclusion_range(p_id, type);
556 throw std::domain_error(
"Parameter 'type_mc' must be >= 0");
558 if (n_particles < 0) {
559 throw std::domain_error(
560 "Parameter 'particle_number_to_be_changed' must be >= 0");
563 if (n_particles == 0) {
572 if (n_particles > n_particles_of_type) {
580 ? std::numeric_limits<double>::max()
584 auto const bf = std::min(1., std::exp(-(E_pot_new - E_pot_old) /
kT));
597 if (m_uniform_real_distribution(m_generator) < bf) {
616 std::sort(particle_ids.begin(), particle_ids.end());
618 for (
auto pid2 : particle_ids) {
619 for (
int pid = pid1 + 1; pid < pid2; ++pid) {
628 auto const obs = system.calculate_energy();
629 auto pot = obs->accumulate(-obs->kinetic[0]);
630 boost::mpi::broadcast(m_comm, pot, 0);
634Particle *ReactionAlgorithm::get_real_particle(
int p_id)
const {
637 auto ptr = system.cell_structure->get_local_particle(p_id);
638 if (ptr !=
nullptr and ptr->is_ghost()) {
641 assert(boost::mpi::all_reduce(m_comm,
static_cast<int>(ptr !=
nullptr),
642 std::plus<>()) == 1);
646Particle *ReactionAlgorithm::get_local_particle(
int p_id)
const {
649 auto ptr = system.cell_structure->get_local_particle(p_id);
650 assert(boost::mpi::all_reduce(
651 m_comm,
static_cast<int>(ptr !=
nullptr and not ptr->is_ghost()),
652 std::plus<>()) == 1);
Vector implementation and trait types for boost qvm interoperability.
std::optional< std::vector< int > > get_short_range_neighbors(System::System const &system, int const pid, double const distance)
Get ids of particles that are within a certain distance of another particle.
This file contains everything related to the global cell structure / cell system.
void add_reaction(std::shared_ptr< SingleReaction > const &new_reaction)
Adds a reaction to the reaction system.
double calculate_potential_energy() const
Compute the system potential energy.
int m_tried_configurational_MC_moves
void make_reaction_attempt(::ReactionMethods::SingleReaction const &reaction, ParticleChanges &bookkeeping)
Carry out a chemical reaction and save the old system state.
Utils::Vector3d get_random_position_in_box()
Writes a random position inside the central box into the provided array.
void set_slab_constraint(double slab_start_z, double slab_end_z)
bool particle_inside_exclusion_range_touched
int i_random(int maxint)
draws a random integer from the uniform distribution in the range [0,maxint-1]
std::unordered_map< int, double > charges_of_types
void set_cyl_constraint(double center_x, double center_y, double radius)
std::unordered_map< int, double > exclusion_radius_per_type
void setup_bookkeeping_of_empty_pids()
Cleans the list of empty pids and searches for empty pid in the system.
void displacement_mc_move(int type, int n_particles)
Carry out displacement MC moves for particles of a given type.
void update_volume()
Automatically sets the volume which is used by the reaction ensemble to the volume of a cuboid box.
double make_reaction_mc_move_attempt(int reaction_id, double bf, double E_pot_old, double E_pot_new)
Accept or reject a reaction MC move made by create_new_trial_state based on a probability acceptance ...
void restore_old_system_state()
Restore last valid system state.
bool all_reactant_particles_exist(SingleReaction const ¤t_reaction) const
Checks whether all particles exist for the provided reaction.
auto & make_new_system_state()
Open new handle for system state tracking.
void clear_old_system_state()
Clear last valid system state.
void delete_particle(int p_id)
Deletes the particle with the given p_id and stores the id if the deletion created a hole in the part...
int m_accepted_configurational_MC_moves
std::vector< std::shared_ptr< SingleReaction > > reactions
double exclusion_range
Hard sphere radius.
std::optional< double > create_new_trial_state(int reaction_id)
Carry out a reaction MC move and calculate the new potential energy.
bool make_displacement_mc_move_attempt(int type, int n_particles)
Attempt displacement MC moves for particles of a given type.
bool neighbor_search_order_n
auto const & get_old_system_state() const
std::vector< int > m_empty_p_ids_smaller_than_max_seen_particle
void on_particle_change()
Called every time a particle property changes.
void on_particle_local_change()
Called every time a particle local property changes.
std::shared_ptr< BoxGeometry > box_geo
This file contains the defaults for ESPResSo.
bool contains(InputIt first, InputIt last, T const &value)
Check whether an iterator range contains a value.
void serialize(Archive &ar, std::tuple< T... > &pack, unsigned int const)
Serialize std::tuple.
std::vector< int > get_particle_ids_parallel()
void make_new_particle(int p_id, Utils::Vector3d const &pos)
Create a new particle and attach it to a cell.
int number_of_particles_with_type(int type)
void set_particle_pos(int p_id, Utils::Vector3d const &pos)
Move particle to a new position.
void init_type_map(int type)
int get_random_p_id(int type, int random_index_in_type_map)
Find a particle of given type and return its id.
void remove_particle(int p_id)
Remove particle with a given identity.
int get_maximal_particle_id()
Get maximal particle id.
void on_particle_type_change(int p_id, int old_type, int new_type)
Particles creation and deletion.
Struct holding all information for one particle.
std::vector< int > created
std::vector< std::tuple< int, int > > hidden
std::vector< std::tuple< int, int > > changed
std::vector< std::tuple< int, Utils::Vector3d, Utils::Vector3d > > moved
std::vector< int > reactant_types
std::vector< int > product_types
std::vector< int > product_coefficients
std::vector< int > reactant_coefficients