ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
integrate.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2026 The ESPResSo project
3 * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010
4 * Max-Planck-Institute for Polymer Research, Theory Group
5 *
6 * This file is part of ESPResSo.
7 *
8 * ESPResSo is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * ESPResSo is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22/** \file
23 * Molecular dynamics integrator.
24 *
25 * For more information about the integrator
26 * see \ref integrate.hpp "integrate.hpp".
27 */
28
29#include "integrate.hpp"
37
38#include "BoxGeometry.hpp"
39#include "PropagationMode.hpp"
40#include "accumulators/AutoUpdateAccumulators.hpp"
45#include "cells.hpp"
46#include "collision_detection/CollisionDetection.hpp"
47#include "communication.hpp"
48#include "errorhandling.hpp"
50#include "lb/utils.hpp"
53#include "npt.hpp"
54#include "rattle.hpp"
55#include "rotation.hpp"
56#include "signalhandling.hpp"
58#include "system/System.hpp"
60#include "thermostat.hpp"
62#include "virtual_sites/com.hpp"
65
67
68#include <boost/mpi/collectives/all_reduce.hpp>
69
70#ifdef ESPRESSO_CALIPER
71#include "caliper_utils.hpp"
72#endif
73
74#ifdef ESPRESSO_VALGRIND
75#include <callgrind.h>
76#endif
77
78#include <algorithm>
79#include <cassert>
80#include <cmath>
81#include <csignal>
82#include <functional>
83#include <limits>
84#include <sstream>
85#include <stdexcept>
86#include <string>
87#include <utility>
88
89#ifdef ESPRESSO_WALBERLA
90#ifdef ESPRESSO_WALBERLA_STATIC_ASSERT
91#error "waLberla headers should not be visible to the ESPResSo core"
92#endif
93#endif
94
95namespace {
96volatile std::sig_atomic_t ctrl_C = 0;
97} // namespace
98
99namespace LeesEdwards {
100
101/**
102 * @brief Update the Lees-Edwards parameters of the box geometry
103 * for the current simulation time.
104 */
105void LeesEdwards::update_box_params(BoxGeometry &box_geo, double sim_time) {
106 if (box_geo.type() == BoxType::LEES_EDWARDS) {
107 assert(m_protocol != nullptr);
108 box_geo.lees_edwards_update(get_pos_offset(sim_time, *m_protocol),
109 get_shear_velocity(sim_time, *m_protocol));
110 }
111}
112
113void LeesEdwards::set_protocol(std::shared_ptr<ActiveProtocol> protocol) {
114 auto &system = get_system();
115 auto &cell_structure = *system.cell_structure;
116 auto &box_geo = *system.box_geo;
117 box_geo.set_type(BoxType::LEES_EDWARDS);
118 m_protocol = std::move(protocol);
119 update_box_params(box_geo, system.get_sim_time());
120 system.propagation->recalc_forces = true;
121 cell_structure.set_resort_particles(Cells::RESORT_LOCAL);
122}
123
125 auto &system = get_system();
126 auto &cell_structure = *system.cell_structure;
127 auto &box_geo = *system.box_geo;
128 m_protocol = nullptr;
129 box_geo.set_type(BoxType::CUBOID);
130 system.propagation->recalc_forces = true;
131 cell_structure.set_resort_particles(Cells::RESORT_LOCAL);
132}
133
134} // namespace LeesEdwards
135
137 switch (integ_switch) {
140 break;
141 case INTEG_METHOD_NVT:
143 // NOLINTNEXTLINE(bugprone-branch-clone)
144 if ((thermo_switch & THERMO_LB) and (thermo_switch & THERMO_LANGEVIN)) {
146#ifdef ESPRESSO_ROTATION
148#endif
149 } else if (thermo_switch & THERMO_LB) {
151#ifdef ESPRESSO_ROTATION
153#endif
154 } else if (thermo_switch & THERMO_LANGEVIN) {
156#ifdef ESPRESSO_ROTATION
158#endif
159 } else {
161#ifdef ESPRESSO_ROTATION
163#endif
164 }
165 break;
166 }
167#ifdef ESPRESSO_NPT
171 break;
172#endif
173 case INTEG_METHOD_BD:
175#ifdef ESPRESSO_ROTATION
177#endif
178 break;
179#ifdef ESPRESSO_STOKESIAN_DYNAMICS
180 case INTEG_METHOD_SD:
182 break;
183#endif // ESPRESSO_STOKESIAN_DYNAMICS
184 default:
185 throw std::runtime_error("Unknown value for integ_switch");
186 }
187}
188
190 int used_propagations = PropagationMode::NONE;
191 for (auto &p : cell_structure->local_particles()) {
192 used_propagations |= p.propagation();
193 }
194 if (used_propagations & PropagationMode::SYSTEM_DEFAULT) {
195 used_propagations |= propagation->default_propagation;
196 }
197 used_propagations = boost::mpi::all_reduce(::comm_cart, used_propagations,
198 std::bit_or<int>());
199 propagation->used_propagations = used_propagations;
200 propagation->recalc_used_propagations = false;
201}
202
203void System::System::integrator_sanity_checks() const {
204 auto const thermo_switch = thermostat->thermo_switch;
205 if (time_step <= 0.) {
206 runtimeErrorMsg() << "time_step not set";
207 }
208 if (propagation->integ_switch == INTEG_METHOD_STEEPEST_DESCENT) {
209 if (thermo_switch != THERMO_OFF) {
211 << "The steepest descent integrator is incompatible with thermostats";
212 }
213 }
214 if (propagation->integ_switch == INTEG_METHOD_NVT) {
215 if (thermo_switch & (THERMO_NPT_ISO | THERMO_BROWNIAN | THERMO_SD)) {
216 runtimeErrorMsg() << "The VV integrator is incompatible with the "
217 "currently active combination of thermostats";
218 }
219 }
220#ifdef ESPRESSO_NPT
221 if (propagation->used_propagations & PropagationMode::TRANS_LANGEVIN_NPT) {
222 if (thermo_switch != THERMO_NPT_ISO) {
223 runtimeErrorMsg() << "The NpT integrator requires the NpT thermostat";
224 }
225 if (box_geo->type() == BoxType::LEES_EDWARDS) {
226 runtimeErrorMsg() << "The NpT integrator cannot use Lees-Edwards";
227 }
228 try {
229 nptiso->coulomb_dipole_sanity_checks(*this);
230 } catch (std::runtime_error const &err) {
231 runtimeErrorMsg() << err.what();
232 }
233 }
234#endif
235 if (propagation->used_propagations & PropagationMode::TRANS_BROWNIAN) {
236 if (thermo_switch != THERMO_BROWNIAN) {
237 runtimeErrorMsg() << "The BD integrator requires the BD thermostat";
238 }
239 }
240 if (propagation->used_propagations & PropagationMode::TRANS_STOKESIAN) {
241#ifdef ESPRESSO_STOKESIAN_DYNAMICS
242 if (thermo_switch != THERMO_SD) {
243 runtimeErrorMsg() << "The SD integrator requires the SD thermostat";
244 }
245#endif
246 }
247 if (lb.is_solver_set() and (propagation->used_propagations &
250 if (thermostat->lb == nullptr) {
251 runtimeErrorMsg() << "The LB integrator requires the LB thermostat";
252 }
253 }
254 if (bonded_ias->get_n_thermalized_bonds() >= 1 and
255 (thermostat->thermalized_bond == nullptr or
256 (thermo_switch & THERMO_BOND) == 0)) {
258 << "Thermalized bonds require the thermalized_bond thermostat";
259 }
260#ifdef ESPRESSO_BOND_CONSTRAINT
261 if (bonded_ias->get_n_rigid_bonds() >= 1) {
262 if (not propagation->is_inertial()) {
264 << "Rigid bonds (RATTLE) require an inertial integrator "
265 "(VV or symplectic Euler); BD and SD are not supported";
266 }
267 }
268#endif
269
270#ifdef ESPRESSO_STOKESIAN_DYNAMICS
271 if ((propagation->used_propagations & PropagationMode::TRANS_STOKESIAN) and
272 (propagation->default_propagation & PropagationMode::TRANS_STOKESIAN)) {
273 auto pred = PropagationPredicateStokesian(propagation->default_propagation);
274 stokesian_dynamics->sanity_checks(
275 cell_structure->local_particles().filter(pred));
276 }
277#endif // ESPRESSO_STOKESIAN_DYNAMICS
278
279#ifdef ESPRESSO_ROTATION
280 for (auto const &p : cell_structure->local_particles()) {
281 using namespace PropagationMode;
282 if (p.can_rotate() and not p.is_virtual() and
283 (p.propagation() & (SYSTEM_DEFAULT | ROT_EULER | ROT_LANGEVIN |
284 ROT_BROWNIAN | ROT_STOKESIAN)) == 0) {
286 << "Rotating particles must have a rotation propagation mode enabled";
287 break;
288 }
289 }
290#endif // ESPRESSO_ROTATION
291
292#ifdef ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
293#ifdef ESPRESSO_EXTERNAL_FORCES
294 if (propagation->used_propagations &
296 for (auto const &p : cell_structure->local_particles()) {
297 using namespace PropagationMode;
298 if ((p.propagation() & TRANS_VS_CENTER_OF_MASS) and
299 p.has_fixed_coordinates()) {
300 runtimeErrorMsg() << "VS COM particles cannot be fixed in space";
301 break;
302 }
303 }
304 }
305#endif // ESPRESSO_EXTERNAL_FORCES
306#ifdef ESPRESSO_BOND_CONSTRAINT
307 if (bonded_ias->get_n_rigid_bonds()) {
308 using namespace PropagationMode;
309 for (auto const &p : cell_structure->local_particles()) {
310 if (p.propagation() & TRANS_VS_CENTER_OF_MASS) {
311 for (auto const bond : p.bonds()) {
312 if (std::holds_alternative<RigidBond>(
313 *bonded_ias->at(bond.bond_id()))) {
314 runtimeErrorMsg() << "VS COM particles cannot use rigid bonds";
315 break;
316 }
317 }
318 }
319 }
320 }
321#endif // ESPRESSO_BOND_CONSTRAINT
322#endif // ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
323
324#ifdef ESPRESSO_THERMAL_STONER_WOHLFARTH
325 if ((thermo_switch & THERMO_LANGEVIN) == 0) {
326 for (auto const &p : cell_structure->local_particles()) {
327 if (p.stoner_wohlfarth_is_enabled()) {
328 runtimeErrorMsg() << "The thermal Stoner-Wohlfarth model requires the "
329 "Langevin thermostat";
330 break;
331 }
332 }
333 }
334#endif // ESPRESSO_THERMAL_STONER_WOHLFARTH
335}
336
337#ifdef ESPRESSO_WALBERLA
338void walberla_tau_sanity_checks(std::string const &method, double tau,
339 double time_step) {
340 if (time_step <= 0.) {
341 return;
342 }
343 // use float epsilon since tau may be a float
344 auto const eps = static_cast<double>(std::numeric_limits<float>::epsilon());
345 if ((tau - time_step) / (tau + time_step) < -eps)
346 throw std::invalid_argument(method + " tau (" + std::to_string(tau) +
347 ") must be >= MD time_step (" +
348 std::to_string(time_step) + ")");
349 auto const factor = tau / time_step;
350 if (std::fabs(std::round(factor) - factor) / factor > eps)
351 throw std::invalid_argument(method + " tau (" + std::to_string(tau) +
352 ") must be an integer multiple of the "
353 "MD time_step (" +
354 std::to_string(time_step) + "). Factor is " +
355 std::to_string(factor));
356}
357
358void walberla_agrid_sanity_checks(std::string const &method,
359 Utils::Vector3d const &geo_left,
360 Utils::Vector3d const &geo_right,
361 Utils::Vector3d const &lattice_left,
362 Utils::Vector3d const &lattice_right,
363 double agrid) {
364 // waLBerla and ESPResSo must agree on domain decomposition
365 auto const tol = agrid / 1E6;
366 if ((lattice_left - geo_left).norm2() > tol or
367 (lattice_right - geo_right).norm2() > tol) {
368 std::stringstream error_msg;
369 error_msg << "waLBerla and ESPResSo disagree about domain decomposition"
370 << "\nMPI rank " << ::this_node << ": "
371 << "left ESPResSo: [" << geo_left << "], "
372 << "left waLBerla: [" << lattice_left << "]"
373 << "\nMPI rank " << ::this_node << ": "
374 << "right ESPResSo: [" << geo_right << "], "
375 << "right waLBerla: [" << lattice_right << "]"
376 << "\nfor method: " << method;
377 throw std::runtime_error(error_msg.str());
378 }
379}
380#endif // ESPRESSO_WALBERLA
381
383#ifdef ESPRESSO_CALIPER
385#endif
386 auto &cell_structure = *system.cell_structure;
387 auto const offset = LeesEdwards::verlet_list_offset(
388 *system.box_geo, cell_structure.get_le_pos_offset_at_last_resort());
389 if (cell_structure.check_resort_required(offset)) {
390 cell_structure.set_resort_particles(Cells::RESORT_LOCAL);
391 }
392}
393
394/** @brief Calls the hook for propagation kernels before the force calculation
395 * @return whether or not to stop the integration loop early.
396 */
397static bool integrator_step_1(CellStructure &cell_structure,
398 Propagation const &propagation,
399 System::System &system, double time_step) {
400#ifdef ESPRESSO_CALIPER
402#endif
403 // steepest decent
405 return system.steepest_descent->propagate(cell_structure);
406
407 auto const &thermostat = *system.thermostat;
408 auto const kT = thermostat.kT;
409 cell_structure.for_each_local_particle([&](Particle &p) {
410#ifdef ESPRESSO_VIRTUAL_SITES
411 // virtual sites are updated later in the integration loop
412 if (p.is_virtual())
413 return;
414#endif
415 if (propagation.integ_switch == INTEG_METHOD_SYMPLECTIC_EULER) {
416 if (propagation.should_propagate_with(
418 symplectic_euler_propagator_1(p, time_step);
420 symplectic_euler_propagator_1(p, time_step);
421#ifdef ESPRESSO_ROTATION
423 symplectic_euler_rotator_1(p, time_step);
424#endif
426 symplectic_euler_propagator_1(p, time_step);
427#ifdef ESPRESSO_ROTATION
429 symplectic_euler_rotator_1(p, time_step);
430#endif
431 } else {
432 if (propagation.should_propagate_with(
434 velocity_verlet_propagator_1(p, time_step);
436 velocity_verlet_propagator_1(p, time_step);
437#ifdef ESPRESSO_ROTATION
439 velocity_verlet_rotator_1(p, time_step);
440#endif
442 velocity_verlet_propagator_1(p, time_step);
443#ifdef ESPRESSO_ROTATION
445 velocity_verlet_rotator_1(p, time_step);
446#endif
447 }
449 brownian_dynamics_propagator(*thermostat.brownian, p, time_step, kT);
450#ifdef ESPRESSO_ROTATION
452 brownian_dynamics_rotator(*thermostat.brownian, p, time_step, kT);
453#endif
454 });
455
456#ifdef ESPRESSO_NPT
459 auto pred = PropagationPredicateNPT(propagation.default_propagation);
460 if (propagation.integ_switch == INTEG_METHOD_NPT_ISO_AND) {
462 cell_structure.local_particles().filter(pred), *thermostat.npt_iso,
463 time_step, system);
464 } else if (propagation.integ_switch == INTEG_METHOD_NPT_ISO_MTK) {
466 cell_structure.local_particles().filter(pred), *thermostat.npt_iso,
467 time_step, system);
468 }
469 }
470#endif
471
472#ifdef ESPRESSO_STOKESIAN_DYNAMICS
475 auto pred = PropagationPredicateStokesian(propagation.default_propagation);
476 stokesian_dynamics_step_1(cell_structure.local_particles().filter(pred),
477 *system.stokesian_dynamics, *thermostat.stokesian,
478 time_step, kT);
479 }
480#endif // ESPRESSO_STOKESIAN_DYNAMICS
481
482 return false;
483}
484
485/**
486 * @brief Build the per-particle half-kick callable for step_2.
487 *
488 * Returns a lambda that captures @p propagation and @p time_step by reference
489 * and applies the velocity (and torque, if ROTATION is enabled) update for a
490 * single particle. Virtual sites are skipped.
491 *
492 * Shared verbatim by @ref integrator_step_2 (full pass) and
493 * @ref integrator_step_2_filtered (interior / boundary passes): the lambda is
494 * constructed once, then handed to @c for_each_local_particle,
495 * @c for_each_interior_particle, or @c for_each_boundary_particle.
496 *
497 * NPT particles are intentionally absent: the NPT arm must run only on the
498 * ineligible (full-reduce-then-step_2) path and is handled separately inside
499 * @ref integrator_step_2.
500 */
501static auto make_step2_particle_kernel(Propagation const &propagation,
502 double time_step) {
503 return [&propagation, time_step](Particle &p) {
504#ifdef ESPRESSO_VIRTUAL_SITES
505 // virtual sites are updated later in the integration loop
506 if (p.is_virtual())
507 return;
508#endif
509 if (propagation.integ_switch == INTEG_METHOD_SYMPLECTIC_EULER) {
510 if (propagation.should_propagate_with(
512 symplectic_euler_propagator_2(p, time_step);
514 symplectic_euler_propagator_2(p, time_step);
515#ifdef ESPRESSO_ROTATION
517 symplectic_euler_rotator_2(p, time_step);
518#endif
520 symplectic_euler_propagator_2(p, time_step);
521#ifdef ESPRESSO_ROTATION
523 symplectic_euler_rotator_2(p, time_step);
524#endif
525 } else {
526 if (propagation.should_propagate_with(
528 velocity_verlet_propagator_2(p, time_step);
530 velocity_verlet_propagator_2(p, time_step);
531#ifdef ESPRESSO_ROTATION
533 velocity_verlet_rotator_2(p, time_step);
534#endif
536 velocity_verlet_propagator_2(p, time_step);
537#ifdef ESPRESSO_ROTATION
539 velocity_verlet_rotator_2(p, time_step);
540#endif
541 }
542 };
543}
544
545static void integrator_step_2(CellStructure &cell_structure,
546 Propagation const &propagation,
547 [[maybe_unused]] System::System &system,
548 double time_step) {
549#ifdef ESPRESSO_CALIPER
551#endif
553 return;
554
555 cell_structure.for_each_local_particle(
556 make_step2_particle_kernel(propagation, time_step));
557
558#ifdef ESPRESSO_NPT
561 auto pred = PropagationPredicateNPT(propagation.default_propagation);
562 if (propagation.integ_switch == INTEG_METHOD_NPT_ISO_AND) {
564 cell_structure.local_particles().filter(pred), time_step, system);
565 } else if (propagation.integ_switch == INTEG_METHOD_NPT_ISO_MTK) {
567 cell_structure.local_particles().filter(pred), time_step, system);
568 }
569 }
570#endif
571}
572
573/**
574 * @brief Restricted step_2 for the split-phase ghost-force-reduce overlap.
575 *
576 * Called twice per step: once with @p interior_pass = true (while the ghost
577 * reduce is in flight) and once with @p interior_pass = false (after
578 * @ref CellStructure::ghosts_reduce_forces_finish completes). The per-particle
579 * kernel is identical to the one used by @ref integrator_step_2 — shared via
580 * @ref make_step2_particle_kernel.
581 *
582 * NPT is absent here: the eligibility check in calculate_forces() guarantees
583 * TRANS_LANGEVIN_NPT is never active when this path runs.
584 */
585static void integrator_step_2_filtered(CellStructure &cell_structure,
586 Propagation const &propagation,
587 double time_step, bool interior_pass) {
588#ifdef ESPRESSO_CALIPER
590#endif
591 // steepest_descent and NPT are excluded by the eligibility check; both
592 // asserts are belt-and-suspenders cross-checks.
593 assert(propagation.integ_switch != INTEG_METHOD_STEEPEST_DESCENT &&
594 "integrator_step_2_filtered: steepest-descent is ineligible");
595#ifdef ESPRESSO_NPT
596 assert((propagation.used_propagations &
598 "integrator_step_2_filtered: NPT propagation is ineligible");
599#endif
600
601 auto const kernel = make_step2_particle_kernel(propagation, time_step);
602 if (interior_pass) {
603 cell_structure.for_each_interior_particle(kernel);
604 } else {
605 cell_structure.for_each_boundary_particle(kernel);
606 }
607}
608
609int System::System::integrate(int n_steps, int reuse_forces) {
610#ifdef ESPRESSO_CALIPER
612#endif
613 auto &propagation = *this->propagation;
614#ifdef ESPRESSO_VIRTUAL_SITES_RELATIVE
615 auto const has_vs_rel = [&propagation]() {
616 return propagation.used_propagations &
620 };
621#endif
622#ifdef ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
623 auto const has_vs_com = [&propagation]() {
624 return propagation.used_propagations &
626 };
627#endif
628#ifdef ESPRESSO_BOND_CONSTRAINT
629 auto const n_rigid_bonds = bonded_ias->get_n_rigid_bonds();
630#endif
631
632 // Prepare particle structure and run sanity checks of all active algorithms
633 propagation.update_default_propagation(thermostat->thermo_switch);
634 update_used_propagations();
635 on_integration_start();
636
637 // If any method vetoes (e.g. P3M not initialized), immediately bail out
639 return INTEG_ERROR_RUNTIME;
640
641 // Additional preparations for the first integration step
642 if (reuse_forces == INTEG_REUSE_FORCES_NEVER or
643 ((reuse_forces != INTEG_REUSE_FORCES_ALWAYS) and
644 propagation.recalc_forces)) {
645#ifdef ESPRESSO_CALIPER
646 ESPRESSO_CALI_MARK_BEGIN("Initial Force Calculation");
647#endif
648 thermostat->lb_coupling_deactivate();
649
650#ifdef ESPRESSO_VIRTUAL_SITES_RELATIVE
651 if (has_vs_rel()) {
652 vs_relative_update_particles(*cell_structure, *box_geo);
653 }
654#endif
655#ifdef ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
656 if (has_vs_com()) {
657 vs_com_update_particles(*cell_structure, *box_geo);
658 }
659#endif
660
661 // Communication step: distribute ghost positions
662 cell_structure->update_ghosts_and_resort_particle(get_global_ghost_flags());
663
664 calculate_forces();
665
666 // If calculate_forces started a split-phase ghost reduce, finish it now:
667 // the initial-force path has no step_2 to overlap with (n_steps may be 0,
668 // or the forces are for the previous step's record), so we just complete
669 // the reduce immediately.
670 if (cell_structure->has_pending_ghost_reduce()) {
671 cell_structure->ghosts_reduce_forces_finish();
672 }
673
674 if (propagation.integ_switch != INTEG_METHOD_STEEPEST_DESCENT) {
675#ifdef ESPRESSO_ROTATION
676 convert_initial_torques(cell_structure->local_particles());
677#endif
678 }
679
680#ifdef ESPRESSO_CALIPER
681 ESPRESSO_CALI_MARK_END("Initial Force Calculation");
682#endif
683 }
684
685 thermostat->lb_coupling_activate();
686
688 return INTEG_ERROR_RUNTIME;
689
690 // Keep track of the number of Verlet updates (i.e. particle resorts)
691 int n_verlet_updates = 0;
692
693 // Keep track of whether an interrupt signal was caught (only in singleton
694 // mode, since signal handlers are unreliable with more than 1 MPI rank)
695 auto const singleton_mode = comm_cart.size() == 1;
696 auto caught_sigint = false;
697 auto caught_error = false;
698
699 auto lb_active = false;
700 auto ek_active = false;
701 if (propagation.integ_switch != INTEG_METHOD_STEEPEST_DESCENT) {
702 lb_active = lb.is_solver_set();
703 ek_active = ek.is_ready_for_propagation();
704 }
705 auto const calc_md_steps_per_tau = [this](double tau) {
706 return static_cast<int>(std::round(tau / time_step));
707 };
708
709#ifdef ESPRESSO_VALGRIND
710 CALLGRIND_START_INSTRUMENTATION;
711#endif
712 // Integration loop
713#ifdef ESPRESSO_CALIPER
714 EspressoCaliLoop espresso_cali_integration_loop("Integration loop");
715#endif
716 int integrated_steps = 0;
717 for (int step = 0; step < n_steps; step++) {
718#ifdef ESPRESSO_CALIPER
719 auto espresso_cali_integration_iter =
720 espresso_cali_integration_loop.iteration(step);
721#endif
722
723#ifdef ESPRESSO_BOND_CONSTRAINT
724 if (n_rigid_bonds)
725 save_old_position(cell_structure->local_particles(),
726 cell_structure->ghost_particles());
727#endif
728
729 lees_edwards->update_box_params(*box_geo, sim_time);
730 bool early_exit =
731 integrator_step_1(*cell_structure, propagation, *this, time_step);
732 if (early_exit)
733 break;
734
735 sim_time += time_step;
736 if (box_geo->type() == BoxType::LEES_EDWARDS) {
737 auto const kernel = LeesEdwards::Push{*box_geo};
738 cell_structure->for_each_local_particle(
739 [&kernel](Particle &p) { kernel(p); });
740 }
741
742#ifdef ESPRESSO_NPT
743 if (not has_npt_enabled())
744#endif
745 {
747 }
748 // Propagate philox RNG counters
749 thermostat->philox_counter_increment();
750
751#ifdef ESPRESSO_BOND_CONSTRAINT
752 // Correct particle positions that participate in a rigid/constrained bond
753 if (n_rigid_bonds) {
754 correct_position_shake(*cell_structure, *box_geo, *bonded_ias);
755 }
756#endif
757
758#ifdef ESPRESSO_VIRTUAL_SITES_RELATIVE
759 if (has_vs_rel()) {
760#ifdef ESPRESSO_NPT
761 if (has_npt_enabled()) {
762 cell_structure->update_ghosts_and_resort_particle(
764 }
765#endif // ESPRESSO_NPT
766 vs_relative_update_particles(*cell_structure, *box_geo);
767 }
768#endif // ESPRESSO_VIRTUAL_SITES_RELATIVE
769#ifdef ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
770 if (has_vs_com()) {
771#ifdef ESPRESSO_NPT
772 if (has_npt_enabled()) {
773 cell_structure->update_ghosts_and_resort_particle(
775 }
776#endif // ESPRESSO_NPT
777 vs_com_update_particles(*cell_structure, *box_geo);
778 }
779#endif // ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
780
781 if (cell_structure->get_resort_particles() >= Cells::RESORT_LOCAL)
782 n_verlet_updates++;
783
784 // Communication step: distribute ghost positions
785 cell_structure->update_ghosts_and_resort_particle(get_global_ghost_flags());
786
787#ifdef ESPRESSO_THERMAL_STONER_WOHLFARTH
788 integrate_magnetodynamics();
789#endif
790
791 calculate_forces();
792
793#ifdef ESPRESSO_VIRTUAL_SITES_INERTIALESS_TRACERS
794 if (thermostat->lb and
795 (propagation.used_propagations & PropagationMode::TRANS_LB_TRACER)) {
796 // LB-tracer arm is ineligible for the split path; this block only runs
797 // on the blocking path where has_pending_ghost_reduce() is false.
798 assert(not cell_structure->has_pending_ghost_reduce() &&
799 "LB-tracer arm must be inactive on the split-phase path");
800 lb_tracers_add_particle_force_to_fluid(*cell_structure, *box_geo,
801 *local_geo, lb);
802 }
803#endif
804 if (cell_structure->has_pending_ghost_reduce()) {
805 // Split-phase path: interior half-kick runs while the ghost reduce is
806 // in flight; boundary half-kick runs after the reduce finishes.
807 // NPT, BD, steepest-descent, and LB-tracer are ineligible and never
808 // reach this branch (asserted inside integrator_step_2_filtered).
809 //
810 // RAII guard: if the interior pass throws (bad_alloc, Kokkos error, user
811 // callback), finish the pending reduce on unwind so MPI requests are not
812 // left dangling and the next start-assert does not fire.
813 // ESPResSo normally propagates errors via runtimeErrorMsg rather than
814 // exceptions, so this guard fires only in exceptional circumstances; the
815 // normal path calls finish() explicitly and the guard becomes a no-op.
816 struct ReduceGuard {
817 CellStructure *cs;
818 bool active;
819 ~ReduceGuard() {
820 if (active and cs->has_pending_ghost_reduce()) {
821 try {
823 } catch (...) { // NOLINT(bugprone-empty-catch)
824 // The guard only runs during unwind from another exception;
825 // letting a second one escape this (implicitly noexcept)
826 // destructor would call std::terminate. Keep the original.
827 }
828 }
829 }
830 } guard{cell_structure.get(), true};
831
832 integrator_step_2_filtered(*cell_structure, propagation, time_step,
833 /*interior_pass=*/true);
834 cell_structure->ghosts_reduce_forces_finish();
835 guard.active = false; // normal path: finish already called above
836 integrator_step_2_filtered(*cell_structure, propagation, time_step,
837 /*interior_pass=*/false);
838 } else {
839 integrator_step_2(*cell_structure, propagation, *this, time_step);
840 }
841 if (propagation.integ_switch == INTEG_METHOD_BD) {
843 }
844 if (box_geo->type() == BoxType::LEES_EDWARDS) {
845 auto const kernel = LeesEdwards::UpdateOffset{*box_geo};
846 cell_structure->for_each_local_particle(
847 [&kernel](Particle &p) { kernel(p); });
848 }
849#ifdef ESPRESSO_BOND_CONSTRAINT
850 if (n_rigid_bonds) {
851 correct_velocity_shake(*cell_structure, *box_geo, *bonded_ias);
852 }
853#endif
854
855 // propagate one-step functionalities
856 if (propagation.integ_switch != INTEG_METHOD_STEEPEST_DESCENT) {
857 if (lb_active and ek_active) {
858 // assume that they are coupled, which is not necessarily true
859 auto const md_steps_per_lb_step = calc_md_steps_per_tau(lb.get_tau());
860 auto const md_steps_per_ek_step = calc_md_steps_per_tau(ek.get_tau());
861
862 if (md_steps_per_lb_step != md_steps_per_ek_step) {
864 << "LB and EK are active but with different time steps.";
865 }
866
867 assert(lb.is_gpu() == ek.is_gpu());
868 assert(propagation.lb_skipped_md_steps ==
869 propagation.ek_skipped_md_steps);
870
871 propagation.lb_skipped_md_steps += 1;
872 propagation.ek_skipped_md_steps += 1;
873 if (propagation.lb_skipped_md_steps >= md_steps_per_lb_step) {
874 propagation.lb_skipped_md_steps = 0;
875 propagation.ek_skipped_md_steps = 0;
876#ifdef ESPRESSO_CALIPER
877 ESPRESSO_CALI_MARK_BEGIN("lb_propagation");
878#endif
879 lb.propagate();
880 lb.ghost_communication_vel();
881#ifdef ESPRESSO_CALIPER
882 ESPRESSO_CALI_MARK_END("lb_propagation");
883#endif
884#ifdef ESPRESSO_CALIPER
885 ESPRESSO_CALI_MARK_BEGIN("ek_propagation");
886#endif
887 ek.propagate();
888#ifdef ESPRESSO_CALIPER
889 ESPRESSO_CALI_MARK_END("ek_propagation");
890#endif
891 }
892 } else if (lb_active) {
893 auto const md_steps_per_lb_step = calc_md_steps_per_tau(lb.get_tau());
894 propagation.lb_skipped_md_steps += 1;
895 if (propagation.lb_skipped_md_steps >= md_steps_per_lb_step) {
896 propagation.lb_skipped_md_steps = 0;
897#ifdef ESPRESSO_CALIPER
898 ESPRESSO_CALI_MARK_BEGIN("lb_propagation");
899#endif
900 lb.propagate();
901#ifdef ESPRESSO_CALIPER
902 ESPRESSO_CALI_MARK_END("lb_propagation");
903#endif
904 }
905 } else if (ek_active) {
906 auto const md_steps_per_ek_step = calc_md_steps_per_tau(ek.get_tau());
907 propagation.ek_skipped_md_steps += 1;
908 if (propagation.ek_skipped_md_steps >= md_steps_per_ek_step) {
909 propagation.ek_skipped_md_steps = 0;
910#ifdef ESPRESSO_CALIPER
911 ESPRESSO_CALI_MARK_BEGIN("ek_propagation");
912#endif
913 ek.propagate();
914#ifdef ESPRESSO_CALIPER
915 ESPRESSO_CALI_MARK_END("ek_propagation");
916#endif
917 }
918 }
919 if (lb_active and (propagation.used_propagations &
921 thermostat->lb->rng_increment();
922 }
923
924#ifdef ESPRESSO_VIRTUAL_SITES_INERTIALESS_TRACERS
925 if (thermostat->lb and
926 (propagation.used_propagations & PropagationMode::TRANS_LB_TRACER)) {
927#ifdef ESPRESSO_CALIPER
928 ESPRESSO_CALI_MARK_BEGIN("lb_tracers_propagation");
929#endif
930 if (lb_active) {
931 lb.ghost_communication_vel();
932 }
933 lb_tracers_propagate(*cell_structure, lb, time_step);
934#ifdef ESPRESSO_CALIPER
935 ESPRESSO_CALI_MARK_END("lb_tracers_propagation");
936#endif
937 }
938#endif
939
940#ifdef ESPRESSO_COLLISION_DETECTION
941 cell_structure->clear_new_bonds();
942 collision_detection->handle_collisions();
943 cell_structure->rebuild_bond_list();
944#endif
945 bond_breakage->process_queue(*this);
946 }
947
948 integrated_steps++;
949
951 caught_error = true;
952 break;
953 }
954
955 // Check if SIGINT has been caught.
956 if (singleton_mode and ctrl_C == 1) {
957 caught_sigint = true;
958 break;
959 }
960
961 } // for-loop over integration steps
962 if (lb_active) {
963 lb.ghost_communication();
964 }
965 lees_edwards->update_box_params(*box_geo, sim_time);
966 // espresso_cali_integration_loop destructor ends the Caliper loop region.
967
968#ifdef ESPRESSO_VALGRIND
969 CALLGRIND_STOP_INSTRUMENTATION;
970#endif
971
972#ifdef ESPRESSO_VIRTUAL_SITES_RELATIVE
973 if (has_vs_rel()) {
974 vs_relative_update_particles(*cell_structure, *box_geo);
975 }
976#endif
977#ifdef ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
978 if (has_vs_com()) {
979 vs_com_update_particles(*cell_structure, *box_geo);
980 }
981#endif
982
983 // Verlet list statistics
984 cell_structure->update_verlet_stats(n_steps, n_verlet_updates);
985
986#ifdef ESPRESSO_NPT
987 if (has_npt_enabled()) {
988 synchronize_npt_state();
989 }
990#endif
991 if (caught_sigint) {
992 ctrl_C = 0;
993 return INTEG_ERROR_SIGINT;
994 }
995 if (caught_error) {
996 return INTEG_ERROR_RUNTIME;
997 }
998 if (boost::mpi::all_reduce(::comm_cart, not cell_structure->use_verlet_list,
999 std::logical_or<>())) {
1000 cell_structure->use_verlet_list = false;
1001 }
1002 return integrated_steps;
1003}
1004
1005int System::System::integrate_with_signal_handler(int n_steps, int reuse_forces,
1006 bool update_accumulators) {
1007 assert(n_steps >= 0);
1008
1009 // Override the signal handler so that the integrator obeys Ctrl+C
1010 SignalHandler sa(SIGINT, [](int) { ctrl_C = 1; });
1011
1012 /* if skin wasn't set, do an educated guess now */
1013 if (not cell_structure->is_verlet_skin_set()) {
1014 try {
1015 cell_structure->set_verlet_skin_heuristic();
1016 } catch (...) {
1017 if (comm_cart.rank() == 0) {
1018 throw;
1019 }
1020 return INTEG_ERROR_RUNTIME;
1021 }
1022 }
1023
1024 if (not update_accumulators or n_steps == 0) {
1025 return integrate(n_steps, reuse_forces);
1026 }
1027
1028 for (int i = 0; i < n_steps;) {
1029 /* Integrate to either the next accumulator update, or the
1030 * end, depending on what comes first. */
1031 auto const steps =
1032 std::min((n_steps - i), auto_update_accumulators->next_update());
1033
1034 auto const local_retval = integrate(steps, reuse_forces);
1035
1036 // make sure all ranks exit when one rank fails
1037 std::remove_const_t<decltype(local_retval)> global_retval;
1038 boost::mpi::all_reduce(comm_cart, local_retval, global_retval,
1039 std::plus<int>());
1040 if (global_retval < 0) {
1041 return global_retval; // propagate error code
1042 }
1043
1044 reuse_forces = INTEG_REUSE_FORCES_ALWAYS;
1045
1046 (*auto_update_accumulators)(comm_cart, steps);
1047
1048 i += steps;
1049 }
1050
1051 return 0;
1052}
1053
1055 sim_time = value;
1056 propagation->recalc_forces = true;
1057 lees_edwards->update_box_params(*box_geo, sim_time);
1058}
@ LEES_EDWARDS
@ INTEG_METHOD_NPT_ISO_AND
@ INTEG_METHOD_SD
@ INTEG_METHOD_STEEPEST_DESCENT
@ INTEG_METHOD_NVT
@ INTEG_METHOD_SYMPLECTIC_EULER
@ INTEG_METHOD_BD
@ INTEG_METHOD_NPT_ISO_MTK
@ THERMO_SD
@ THERMO_BROWNIAN
@ THERMO_BOND
@ THERMO_LB
@ THERMO_LANGEVIN
@ THERMO_NPT_ISO
@ THERMO_OFF
Data structures for bonded interactions.
Zero-overhead Caliper guards for the inactive (no CALI_CONFIG) case.
#define ESPRESSO_CALI_MARK_END(name)
Guarded CALI_MARK_END — no-op when inactive.
#define ESPRESSO_CALI_MARK_BEGIN(name)
Guarded CALI_MARK_BEGIN — no-op when inactive.
#define ESPRESSO_CALI_MARK_FUNCTION
Guarded drop-in replacement for CALI_CXX_MARK_FUNCTION.
This file contains everything related to the global cell structure / cell system.
void lees_edwards_update(double pos_offset, double shear_velocity)
Update the Lees-Edwards parameters of the box geometry for the current simulation time.
BoxType type() const
Describes a cell structure / cell system.
void for_each_local_particle(ParticleCallback auto &&f, bool parallel=true) const
Run a kernel on all local particles.
void for_each_interior_particle(ParticleCallback auto &&f) const
Run a kernel on interior (non-boundary) local particles only.
bool has_pending_ghost_reduce() const
True when a split-phase force reduction is in flight.
void for_each_boundary_particle(ParticleCallback auto &&f) const
Run a kernel on boundary local particles only.
void ghosts_reduce_forces_finish()
Complete the split-phase ghost force reduction.
ParticleRange local_particles() const
void update_box_params(BoxGeometry &box_geo, double sim_time)
Update the Lees-Edwards parameters of the box geometry for the current simulation time.
void set_protocol(std::shared_ptr< ActiveProtocol > protocol)
Set a new Lees-Edwards protocol.
void unset_protocol()
Delete the currently active Lees-Edwards protocol.
ParticleRangeFiltered< Predicate > filter(Predicate pred) const
int default_propagation
void update_default_propagation(int thermo_switch)
bool should_propagate_with(Particle const &p, int mode) const
int used_propagations
RAII guard for signal handling.
Main system class.
std::shared_ptr< StokesianDynamics > stokesian_dynamics
void update_used_propagations()
Update the global propagation bitmask.
void set_sim_time(double value)
Set sim_time.
int integrate_with_signal_handler(int n_steps, int reuse_forces, bool update_accumulators)
std::shared_ptr< SteepestDescent > steepest_descent
int integrate(int n_steps, int reuse_forces)
Integrate equations of motion.
std::shared_ptr< Thermostat::Thermostat > thermostat
std::shared_ptr< CellStructure > cell_structure
std::shared_ptr< BoxGeometry > box_geo
void vs_com_update_particles(CellStructure &cell_structure, BoxGeometry const &box_geo)
Definition com.cpp:131
boost::mpi::communicator comm_cart
The communicator.
int this_node
The number of this node.
int check_runtime_errors(boost::mpi::communicator const &comm)
Count runtime errors on all nodes.
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
#define runtimeErrorMsg()
static auto make_step2_particle_kernel(Propagation const &propagation, double time_step)
Build the per-particle half-kick callable for step_2.
static bool integrator_step_1(CellStructure &cell_structure, Propagation const &propagation, System::System &system, double time_step)
Calls the hook for propagation kernels before the force calculation.
void walberla_tau_sanity_checks(std::string const &method, double tau, double time_step)
static void resort_particles_if_needed(System::System &system)
static void integrator_step_2_filtered(CellStructure &cell_structure, Propagation const &propagation, double time_step, bool interior_pass)
Restricted step_2 for the split-phase ghost-force-reduce overlap.
static void integrator_step_2(CellStructure &cell_structure, Propagation const &propagation, System::System &system, double time_step)
void walberla_agrid_sanity_checks(std::string const &method, Utils::Vector3d const &geo_left, Utils::Vector3d const &geo_right, Utils::Vector3d const &lattice_left, Utils::Vector3d const &lattice_right, double agrid)
Molecular dynamics integrator.
#define INTEG_ERROR_RUNTIME
Definition integrate.hpp:42
#define INTEG_ERROR_SIGINT
Definition integrate.hpp:43
#define INTEG_REUSE_FORCES_NEVER
recalculate forces unconditionally (mostly used for timing)
Definition integrate.hpp:49
#define INTEG_REUSE_FORCES_ALWAYS
do not recalculate forces (mostly when reading checkpoints with forces)
Definition integrate.hpp:53
void brownian_dynamics_rotator(BrownianThermostat const &brownian, Particle &p, double time_step, double kT)
void brownian_dynamics_propagator(BrownianThermostat const &brownian, Particle &p, double time_step, double kT)
void lb_tracers_propagate(CellStructure &cell_structure, LB::Solver const &lb, double time_step)
void lb_tracers_add_particle_force_to_fluid(CellStructure &cell_structure, BoxGeometry const &box_geo, LocalBox const &local_box, LB::Solver &lb)
@ DATA_PART_PROPERTIES
Particle::p.
Utils::Vector3d verlet_list_offset(BoxGeometry const &box, double pos_offset_at_last_resort)
double get_shear_velocity(double time, ActiveProtocol const &protocol)
Calculation of current velocity.
double get_pos_offset(double time, ActiveProtocol const &protocol)
Definition protocols.hpp:94
volatile std::sig_atomic_t ctrl_C
Definition integrate.cpp:96
Various procedures concerning interactions between particles.
Exports for the NpT code.
void correct_velocity_shake(CellStructure &cs, BoxGeometry const &box_geo, BondedInteractionsMap const &bonded_ias)
Correction of current velocities using RATTLE algorithm.
Definition rattle.cpp:261
void save_old_position(const ParticleRange &particles, const ParticleRange &ghost_particles)
copy current position
Definition rattle.cpp:65
void correct_position_shake(CellStructure &cs, BoxGeometry const &box_geo, BondedInteractionsMap &bonded_ias)
Propagate velocity and position while using SHAKE algorithm for bond constraint.
Definition rattle.cpp:179
RATTLE algorithm ().
void vs_relative_update_particles(CellStructure &cell_structure, BoxGeometry const &box_geo)
Definition relative.cpp:121
void convert_initial_torques(const ParticleRange &particles)
Convert torques to the body-fixed frame before the integration loop.
Definition rotation.cpp:192
This file contains all subroutines required to process rotational motion.
See for the Stokesian dynamics method used here.
void stokesian_dynamics_step_1(ParticleRangeStokesian const &particles, StokesianDynamics const &integrator, StokesianThermostat const &stokesian, double time_step, double kT)
RAII loop wrapper replacing the CALI_CXX_MARK_LOOP_BEGIN / CALI_CXX_MARK_LOOP_ITERATION / CALI_CXX_MA...
EspressoCaliIteration iteration(int iter) const
Return an RAII iteration annotation for the current step.
Struct holding all information for one particle.
Definition Particle.hpp:436
constexpr auto is_virtual() const
Definition Particle.hpp:606
void symplectic_euler_rotator_2(Particle &, double)
void symplectic_euler_rotator_1(Particle &p, double time_step)
void symplectic_euler_propagator_2(Particle &, double)
Final integration step of the Symplectic Euler integrator For symplectic Euler, there is no second st...
void symplectic_euler_propagator_1(Particle &p, double time_step)
Propagate the velocities and positions.
void velocity_verlet_rotator_1(Particle &p, double time_step)
void velocity_verlet_propagator_2(Particle &p, double time_step)
Final integration step of the Velocity Verlet integrator.
void velocity_verlet_propagator_1(Particle &p, double time_step)
Propagate the velocities and positions.
void velocity_verlet_rotator_2(Particle &p, double time_step)
void velocity_verlet_npt_MTK_step_1(ParticleRangeNPT const &particles, IsotropicNptThermostat const &npt_iso, double time_step, System::System &system)
Special propagator for velocity Verlet NpT with the Andersen method.
void velocity_verlet_npt_MTK_step_2(ParticleRangeNPT const &particles, double time_step, System::System &system)
Final integration step of the velocity Verlet NpT integrator with the MTK method.
void velocity_verlet_npt_Andersen_step_1(ParticleRangeNPT const &particles, IsotropicNptThermostat const &npt_iso, double time_step, System::System &system)
Special propagator for velocity Verlet NpT with the Andersen method.
void velocity_verlet_npt_Andersen_step_2(ParticleRangeNPT const &particles, double time_step, System::System &system)
Final integration step of the velocity Verlet NpT integrator with the Andersen method.