ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
core/reaction_methods/ReactionAlgorithm.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2026 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "config/config.hpp"
21
22#include "reaction_methods/ReactionAlgorithm.hpp"
23
24#include "BoxGeometry.hpp"
25#include "Observable_stat.hpp"
27#include "cells.hpp"
28#include "particle_node.hpp"
29#include "system/System.hpp"
30
31#include <utils/Vector.hpp>
32#include <utils/contains.hpp>
33
34#include <boost/mpi/collectives/all_reduce.hpp>
35#include <boost/mpi/collectives/broadcast.hpp>
36#include <boost/mpi/operations.hpp>
37#include <boost/serialization/serialization.hpp>
38
39#include <algorithm>
40#include <cassert>
41#include <cmath>
42#include <functional>
43#include <iterator>
44#include <limits>
45#include <map>
46#include <numbers>
47#include <optional>
48#include <stdexcept>
49#include <string>
50#include <tuple>
51#include <utility>
52#include <vector>
53
54namespace boost::serialization {
55template <typename Archive, typename... Types>
56void serialize(Archive &ar, std::tuple<Types...> &tuple, const unsigned int) {
57 std::apply([&](auto &...item) { ((ar & item), ...); }, tuple);
58}
59} // namespace boost::serialization
60
61namespace ReactionMethods {
62
63/**
64 * Adds a reaction to the reaction system
65 */
67 std::shared_ptr<SingleReaction> const &new_reaction) {
68
69 // make ESPResSo count the particle numbers which take part in the reactions
70 for (int reactant_type : new_reaction->reactant_types)
72 for (int product_type : new_reaction->product_types)
74
76
77 reactions.push_back(new_reaction);
78}
79
80/**
81 * @details This method tries to keep the cell system overhead to a minimum.
82 * Event callbacks are only called once after all particles are updated,
83 * except for particle deletion (the cell structure is still reinitialized
84 * after each deletion).
85 */
87 auto &system = System::get_system();
88 auto const &old_state = get_old_system_state();
89 auto const &box_geo = *system.box_geo;
90 // restore the properties of changed and hidden particles
91 for (auto const &state : {old_state.changed, old_state.hidden}) {
92 for (auto const &[p_id, p_type] : state) {
94 if (auto p = get_local_particle(p_id)) {
95 p->type() = p_type;
96#ifdef ESPRESSO_ELECTROSTATICS
97 p->q() = charges_of_types.at(p_type);
98#endif
99 }
100 }
101 }
102 // delete created particles
103 for (auto const p_id : old_state.created) {
105 }
106 // restore original positions and velocities
107 for (auto const &[p_id, pos, vel] : old_state.moved) {
108 if (auto p = get_local_particle(p_id)) {
109 p->v() = vel;
110 auto folded_pos = pos;
111 auto image_box = Utils::Vector3i{};
112 box_geo.fold_position(folded_pos, image_box);
113 p->pos() = folded_pos;
114 p->image_box() = image_box;
115 }
116 }
117 if (not old_state.moved.empty()) {
118 auto const &system = System::get_system();
119 auto &cell_structure = *system.cell_structure;
120 cell_structure.set_resort_particles(Cells::RESORT_GLOBAL);
121 }
122 system.on_particle_change();
124}
125
126/**
127 * Automatically sets the volume which is used by the reaction ensemble to the
128 * volume of a cuboid box.
129 */
131 auto const &box_geo = *System::get_system().box_geo;
132 volume = box_geo.volume();
133}
134
135/**
136 * Checks whether all particles exist for the provided reaction.
137 */
139 SingleReaction const &current_reaction) const {
140 bool enough_particles = true;
141 for (int i = 0; i < current_reaction.reactant_types.size(); i++) {
142 int current_number =
144 if (current_number < current_reaction.reactant_coefficients[i]) {
145 enough_particles = false;
146 break;
147 }
148 }
149 return enough_particles;
150}
151
154 // create or hide particles of types with corresponding types in reaction
155 auto const n_product_types = reaction.product_types.size();
156 auto const n_reactant_types = reaction.reactant_types.size();
157 auto const get_random_p_id_of_type = [this](int type) {
159 return get_random_p_id(type, random_index);
160 };
161 auto only_local_changes = true;
162 for (int i = 0; i < std::min(n_product_types, n_reactant_types); i++) {
163 auto const n_product_coef = reaction.product_coefficients[i];
164 auto const n_reactant_coef = reaction.reactant_coefficients[i];
165 // change std::min(reactant_coefficients(i),product_coefficients(i)) many
166 // particles of reactant_types(i) to product_types(i)
167 auto const old_type = reaction.reactant_types[i];
168 auto const new_type = reaction.product_types[i];
169#ifdef ESPRESSO_ELECTROSTATICS
171 only_local_changes = false;
172 }
173#endif
174 for (int j = 0; j < std::min(n_product_coef, n_reactant_coef); j++) {
177 if (auto p = get_local_particle(p_id)) {
178 p->type() = new_type;
179#ifdef ESPRESSO_ELECTROSTATICS
180 p->q() = charges_of_types.at(new_type);
181#endif
182 }
183 bookkeeping.changed.emplace_back(p_id, old_type);
184 }
185 // create product_coefficients(i)-reactant_coefficients(i) many product
186 // particles iff product_coefficients(i)-reactant_coefficients(i)>0,
187 // iff product_coefficients(i)-reactant_coefficients(i)<0, hide this number
188 // of reactant particles
190 if (delta_n > 0) {
191 auto const type = reaction.product_types[i];
192 for (int j = 0; j < delta_n; j++) {
193 auto const p_id = create_particle(type);
195 bookkeeping.created.emplace_back(p_id);
196 }
197 only_local_changes = false;
198 } else if (delta_n < 0) {
199 auto const type = reaction.reactant_types[i];
200 for (int j = 0; j < -delta_n; j++) {
201 auto const p_id = get_random_p_id_of_type(type);
202 bookkeeping.hidden.emplace_back(p_id, type);
204 hide_particle(p_id, type);
205 }
206 only_local_changes = false;
207 }
208 }
209 // create or hide particles of types with noncorresponding replacement types
210 for (auto i = std::min(n_product_types, n_reactant_types);
211 i < std::max(n_product_types, n_reactant_types); i++) {
213 // hide superfluous reactant_types particles
214 auto const type = reaction.reactant_types[i];
215 for (int j = 0; j < reaction.reactant_coefficients[i]; j++) {
216 auto const p_id = get_random_p_id_of_type(type);
217 bookkeeping.hidden.emplace_back(p_id, type);
219 hide_particle(p_id, type);
220 }
221 } else {
222 // create additional product_types particles
223 auto const type = reaction.product_types[i];
224 for (int j = 0; j < reaction.product_coefficients[i]; j++) {
225 auto const p_id = create_particle(type);
227 bookkeeping.created.emplace_back(p_id);
228 }
229 }
230 }
231 // determine which fine-grained event to trigger
234 } else {
236 }
237}
238
239std::unordered_map<int, int>
240ReactionAlgorithm::get_particle_numbers(SingleReaction const &reaction) const {
241 std::unordered_map<int, int> particle_numbers;
242 // reactants
243 for (int type : reaction.reactant_types) {
245 }
246 // products
247 for (int type : reaction.product_types) {
249 }
250 return particle_numbers;
251}
252
253std::optional<double>
255 auto &reaction = *reactions[reaction_id];
256 reaction.tried_moves++;
259 // make sure that no incomplete reaction is performed -> only need to
260 // consider rollback of complete reactions
261 return {};
262 }
264 bookkeeping.reaction_id = reaction_id;
265 bookkeeping.old_particle_numbers = get_particle_numbers(reaction);
267 auto E_pot_new = std::numeric_limits<double>::max();
270 }
271 return {E_pot_new};
272}
273
275 int reaction_id, double ln_bf, double E_pot_old, double E_pot_new) {
276 auto constexpr exp_min = -708.4; // for IEEE-compatible double
277 auto const exponent = -(E_pot_new - E_pot_old) / kT;
278 auto const exponential = (exponent < exp_min) ? 0. : std::exp(exponent);
279 auto &reaction = *reactions[reaction_id];
280 reaction.accumulator_potential_energy_difference_exponential(
281 std::vector<double>{exponential});
282 // probability space transformation: the uniform range [0, 1] from U(0, 1)
283 // is equivalent to the exponential range (-inf, 0] from -Exp(1) in log space
284 if (-get_random_logarithmic_number() >= ln_bf) {
285 // reject trial move: restore previous state, energy is unchanged
287 return E_pot_old;
288 }
289 // accept trial move: delete hidden particles and return new system energy
290 for (auto const &[p_id, p_type] : get_old_system_state().hidden) {
292 }
293 reaction.accepted_moves++;
295 return E_pot_new;
296}
297
298/**
299 * Hides a particle from short ranged interactions and from the electrostatic
300 * interaction. Additional hiding from interactions would need to be implemented
301 * here.
302 *
303 * Removing the particle charge and changing its type to a non existing one
304 * deactivates all interactions with other particles, as if the particle was
305 * inexistent (currently only type-based interactions are switched off, as well
306 * as the electrostatic interaction).
307 * This function does not break bonds for simple reactions, as long as there
308 * are no reactions like 2A -->B where one of the reacting A particles occurs
309 * in the polymer (think of bond breakages if the monomer in the polymer gets
310 * deleted in the reaction). This constraint is not of fundamental reason, but
311 * there would be a need for a rule for such "collision" reactions (a reaction
312 * like the one above).
313 */
314void ReactionAlgorithm::hide_particle(int p_id, int p_type) const {
316 if (auto p = get_local_particle(p_id)) {
317 p->type() = non_interacting_type;
318#ifdef ESPRESSO_ELECTROSTATICS
319 p->q() = 0.;
320#endif
321 }
322}
323
324/**
325 * Check if the inserted particle is too close to neighboring particles.
326 */
328
329 /* Check the exclusion radius of the inserted particle */
330 if (exclusion_radius_per_type.contains(p_type)) {
332 return;
333 }
334 }
335
336 auto p1_ptr = get_real_particle(p_id);
337
338 auto const &system = System::get_system();
339 auto const &box_geo = *system.box_geo;
340 auto &cell_structure = *system.cell_structure;
341
342 /* Test whether particle @p p2 lies inside the exclusion range of the
343 * inserted particle located at @p p1_pos. */
344 auto const is_inside_exclusion_range = [&](Utils::Vector3d const &p1_pos,
345 Particle const &p2) {
346 double excluded_distance;
348 not exclusion_radius_per_type.contains(p2.type())) {
350 } else if (exclusion_radius_per_type[p2.type()] == 0.) {
351 return false;
352 } else {
355 }
356 auto const d_min = box_geo.get_mi_vector(p2.pos(), p1_pos).norm();
357 return d_min < excluded_distance;
358 };
359
361 /* Exhaustive O(N) search. The candidate id list is global (every rank
362 * holds the full list), but each real particle is owned by exactly one
363 * rank. We broadcast the position of the inserted particle to every rank,
364 * let each rank distance-test the candidates it owns locally (real,
365 * non-ghost copies, to avoid double-counting ghosts), and OR-reduce the
366 * partial results. This keeps the check correct even when a candidate
367 * sits within exclusion_range of the inserted particle but beyond the
368 * ghost layer of the inserted particle's domain, where get_local_particle
369 * would return nullptr. */
371 /* remove the inserted particle id */
372 std::erase(all_ids, p_id);
373
374 /* broadcast the position of the inserted particle from its owning rank */
375 auto p1_pos = (p1_ptr != nullptr) ? p1_ptr->pos() : Utils::Vector3d{};
376 auto const owner_rank =
377 boost::mpi::all_reduce(m_comm, (p1_ptr != nullptr) ? m_comm.rank() : -1,
378 boost::mpi::maximum<int>());
379 boost::mpi::broadcast(m_comm, p1_pos, owner_rank);
380
381 bool local_touched = false;
382 for (auto const p2_id : all_ids) {
383 auto const p2_ptr = cell_structure.get_local_particle(p2_id);
384 /* only the owning rank (real, non-ghost copy) tests each candidate */
385 if (p2_ptr != nullptr and not p2_ptr->is_ghost() and
387 local_touched = true;
388 break;
389 }
390 }
392 boost::mpi::all_reduce(m_comm, local_touched, std::logical_or<>());
393 return;
394 }
395
396 /* Neighbor search via the cell structure: the neighbor ids returned by
397 * get_short_range_neighbors are local/ghost on the inserted particle's rank
398 * and within the interaction range, so resolving them with
399 * get_local_particle is correct here. */
400 std::vector<int> particle_ids;
401 {
403 mutable_system.on_observable_calc();
404 auto const local_ids =
405 get_short_range_neighbors(mutable_system, p_id, m_max_exclusion_range);
406 assert(p1_ptr == nullptr or !!local_ids);
407 if (local_ids) {
408 particle_ids = std::move(*local_ids);
409 }
410 }
411
412 if (p1_ptr != nullptr) {
413 auto const &p1 = *p1_ptr;
414 /* Check if the inserted particle within the exclusion radius of any other
415 * particle */
416 for (auto const p2_id : particle_ids) {
417 if (auto const p2_ptr = cell_structure.get_local_particle(p2_id)) {
418 if (is_inside_exclusion_range(p1.pos(), *p2_ptr)) {
420 break;
421 }
422 }
423 }
424 if (m_comm.rank() != 0) {
426 }
427 } else if (m_comm.rank() == 0) {
428 m_comm.recv(boost::mpi::any_source, 1,
430 }
431 boost::mpi::broadcast(m_comm, particle_inside_exclusion_range_touched, 0);
432}
433
434/**
435 * Deletes the particle with the given p_id and stores the id if the deletion
436 * created a hole in the particle id range. This method is intended to only
437 * delete unbonded particles since bonds are coupled to ids. This is used to
438 * avoid the id range becoming excessively huge.
439 */
441 if (p_id < 0) {
442 throw std::domain_error("Invalid particle id: " + std::to_string(p_id));
443 }
445 if (p_id == old_max_seen_id) {
446 // last particle, just delete
448 // remove all saved empty p_ids which are greater than the max_seen_particle
449 // this is needed in order to avoid the creation of holes
452 if ((*p_id_iter) >= old_max_seen_id)
454 p_id_iter); // update iterator after container was modified
455 else
456 ++p_id_iter;
457 }
458 } else if (p_id <= old_max_seen_id) {
461 } else {
462 throw std::runtime_error(
463 "Particle id is greater than the max seen particle id");
464 }
465}
466
468 double radius) {
469 auto const &box_geo = *System::get_system().box_geo;
470 if (center_x < 0. or center_x > box_geo.length()[0])
471 throw std::domain_error("center_x is outside the box");
472 if (center_y < 0. or center_y > box_geo.length()[1])
473 throw std::domain_error("center_y is outside the box");
474 if (radius < 0.)
475 throw std::domain_error("radius is invalid");
476 m_cyl_x = center_x;
477 m_cyl_y = center_y;
478 m_cyl_radius = radius;
479 m_reaction_constraint = ReactionConstraint::CYL_Z;
480}
481
483 double slab_end_z) {
484 auto const &box_geo = *System::get_system().box_geo;
485 if (slab_start_z < 0. or slab_start_z > box_geo.length()[2])
486 throw std::domain_error("slab_start_z is outside the box");
487 if (slab_end_z < 0. or slab_end_z > box_geo.length()[2])
488 throw std::domain_error("slab_end_z is outside the box");
490 throw std::domain_error("slab_end_z must be >= slab_start_z");
491 m_slab_start_z = slab_start_z;
492 m_slab_end_z = slab_end_z;
493 m_reaction_constraint = ReactionConstraint::SLAB_Z;
494}
495
496/**
497 * Writes a random position inside the central box into the provided array.
498 */
500 auto const &box_geo = *System::get_system().box_geo;
502
503 if (m_reaction_constraint == ReactionConstraint::CYL_Z) {
504 // see http://mathworld.wolfram.com/DiskPointPicking.html
505 // for uniform disk point picking in cylinder
506 auto const random_radius =
507 m_cyl_radius * std::sqrt(m_uniform_real_distribution(m_generator));
508 auto const random_phi =
509 2. * std::numbers::pi * m_uniform_real_distribution(m_generator);
510 out_pos[0] = m_cyl_x + random_radius * cos(random_phi);
511 out_pos[1] = m_cyl_y + random_radius * sin(random_phi);
512 out_pos[2] = box_geo.length()[2] * m_uniform_real_distribution(m_generator);
513 } else if (m_reaction_constraint == ReactionConstraint::SLAB_Z) {
514 out_pos[0] = box_geo.length()[0] * m_uniform_real_distribution(m_generator);
515 out_pos[1] = box_geo.length()[1] * m_uniform_real_distribution(m_generator);
516 out_pos[2] = m_slab_start_z + (m_slab_end_z - m_slab_start_z) *
517 m_uniform_real_distribution(m_generator);
518 } else {
519 assert(m_reaction_constraint == ReactionConstraint::NONE);
520 out_pos[0] = box_geo.length()[0] * m_uniform_real_distribution(m_generator);
521 out_pos[1] = box_geo.length()[1] * m_uniform_real_distribution(m_generator);
522 out_pos[2] = box_geo.length()[2] * m_uniform_real_distribution(m_generator);
523 }
524 return out_pos;
525}
526
527/**
528 * Creates a particle at the end of the observed particle id range.
529 */
530int ReactionAlgorithm::create_particle(int p_type) {
531 int p_id;
533 auto p_id_iter =
534 std::ranges::min_element(m_empty_p_ids_smaller_than_max_seen_particle);
535 p_id = *p_id_iter;
537 } else {
539 }
540
541 // create random velocity vector according to Maxwell-Boltzmann distribution
542 auto pos = get_random_position_in_box();
543 auto vel = get_random_velocity_vector();
544
546 if (auto p = get_local_particle(p_id)) {
547 p->v() = std::sqrt(kT / p->mass()) * vel;
548 p->type() = p_type;
549#ifdef ESPRESSO_ELECTROSTATICS
550 p->q() = charges_of_types.at(p_type);
551#endif
552 }
554 return p_id;
555}
556
557void ReactionAlgorithm::displacement_mc_move(int type, int n_particles) {
559 // draw particle ids at random without replacement
560 int p_id = -1;
561 std::vector<int> drawn_pids{p_id};
562 for (int i = 0; i < n_particles; i++) {
563 // draw a new particle id
567 }
568 drawn_pids.emplace_back(p_id);
569 // write new position and new velocity
570 typename decltype(ParticleChanges::moved)::value_type old_state;
571 auto const new_pos = get_random_position_in_box();
572 auto vel = get_random_velocity_vector();
573 if (auto p = get_real_particle(p_id)) {
574 old_state = {p_id, p->pos(), p->v()};
575 p->v() = std::sqrt(kT / p->mass()) * vel;
576 if (m_comm.rank() != 0) {
577 m_comm.send(0, 42, old_state);
578 }
579 } else if (m_comm.rank() == 0) {
580 m_comm.recv(boost::mpi::any_source, 42, old_state);
581 }
582 boost::mpi::broadcast(m_comm, old_state, 0);
583 bookkeeping.moved.emplace_back(old_state);
585
588 break;
589 }
590 }
591}
592
594 int n_particles) {
595
596 if (type < 0) {
597 throw std::domain_error("Parameter 'type_mc' must be >= 0");
598 }
599 if (n_particles < 0) {
600 throw std::domain_error(
601 "Parameter 'particle_number_to_be_changed' must be >= 0");
602 }
603
604 if (n_particles == 0) {
605 // reject
606 return false;
607 }
608
611
613 if (n_particles > n_particles_of_type) {
614 // reject
615 return false;
616 }
617
619 displacement_mc_move(type, n_particles);
621 ? std::numeric_limits<double>::max()
623 auto constexpr exp_min = -708.4; // for IEEE-compatible double
624 auto const exponent = -(E_pot_new - E_pot_old) / kT;
625 auto const exponential = (exponent < exp_min) ? 0. : std::exp(exponent);
626
627 // Metropolis algorithm since proposal density is symmetric
628 auto const bf = std::min(1., exponential);
629
630 // // correct for enhanced proposal of small radii by using the
631 // // Metropolis-Hastings algorithm for asymmetric proposal densities
632 // double old_radius =
633 // std::sqrt(std::pow(particle_positions[0][0]-cyl_x,2) +
634 // std::pow(particle_positions[0][1]-cyl_y,2));
635 // double new_radius =
636 // std::sqrt(std::pow(new_pos[0]-cyl_x,2)+std::pow(new_pos[1]-cyl_y,2));
637 // auto const bf = std::min(1.0,
638 // exp(-beta*(E_pot_new-E_pot_old))*new_radius/old_radius);
639
640 // Metropolis-Hastings algorithm for asymmetric proposal density
641 if (m_uniform_real_distribution(m_generator) < bf) {
642 // accept
645 return true;
646 }
647 // reject: restore original particle properties
649 return false;
650}
651
652/**
653 * Cleans the list of empty pids and searches for empty pid in the system
654 */
656 // Clean-up the list of empty pids
658
660 std::ranges::sort(particle_ids);
661 auto pid1 = -1;
662 for (auto pid2 : particle_ids) {
663 for (int pid = pid1 + 1; pid < pid2; ++pid) {
665 }
666 pid1 = pid2;
667 }
668}
669
671 auto &system = System::get_system();
672 auto const &obs = system.calculate_energy();
673 auto const kinetic_energy = obs.kinetic_lin[0] + obs.kinetic_rot[0];
674 auto pot = obs.accumulate(-kinetic_energy);
675 boost::mpi::broadcast(m_comm, pot, 0);
676 return pot;
677}
678
679Particle *ReactionAlgorithm::get_real_particle(int p_id) const {
680 assert(p_id >= 0);
681 auto const &system = System::get_system();
682 auto ptr = system.cell_structure->get_local_particle(p_id);
683 if (ptr != nullptr and ptr->is_ghost()) {
684 ptr = nullptr;
685 }
686 assert(boost::mpi::all_reduce(m_comm, static_cast<int>(ptr != nullptr),
687 std::plus<>()) == 1);
688 return ptr;
689}
690
691Particle *ReactionAlgorithm::get_local_particle(int p_id) const {
692 assert(p_id >= 0);
693 auto const &system = System::get_system();
694 auto ptr = system.cell_structure->get_local_particle(p_id);
695 assert(boost::mpi::all_reduce(
696 m_comm, static_cast<int>(ptr != nullptr and not ptr->is_ghost()),
697 std::plus<>()) == 1);
698 return ptr;
699}
700
701} // namespace ReactionMethods
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.
Definition cells.cpp:118
This file contains everything related to the global cell structure / cell system.
double make_reaction_mc_move_attempt_logarithmic(int reaction_id, double ln_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 logarithmic probability...
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.
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)
int i_random(int maxint)
draws a random integer from the uniform distribution in the range [0,maxint-1]
void set_cyl_constraint(double center_x, double center_y, double radius)
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.
void restore_old_system_state()
Restore last valid system state.
void check_exclusion_range(int p_id, int p_type)
Check if the inserted particle is too close to neighboring particles.
bool all_reactant_particles_exist(SingleReaction const &current_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...
std::vector< std::shared_ptr< SingleReaction > > reactions
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.
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
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.
System & get_system()
bool contains(Range &&rng, T const &value)
Check whether a range contains a value.
Definition contains.hpp:36
void serialize(Archive &ar, std::tuple< T... > &pack, unsigned int const)
Serialize std::tuple.
auto constexpr new_part
auto constexpr any_type
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.
Definition Particle.hpp:436
std::vector< std::tuple< int, Utils::Vector3d, Utils::Vector3d > > moved