diff --git a/doc/sphinx/CMakeLists.txt b/doc/sphinx/CMakeLists.txt index 5d0cc753e52..7dc5dc3a0bb 100644 --- a/doc/sphinx/CMakeLists.txt +++ b/doc/sphinx/CMakeLists.txt @@ -87,6 +87,8 @@ if(SPHINX_FOUND) ${Python_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/samples.py" COMMAND ${SPHINX_API_DOC_EXE} -f -o ${CMAKE_CURRENT_BINARY_DIR} ${SPHINX_PYTHON_DIR} ${EXCLUDE} + COMMAND ${Python_EXECUTABLE} + "${CMAKE_CURRENT_SOURCE_DIR}/autodoc_overrides.py" COMMAND ${SPHINX_EXECUTABLE} -q -W -b html -c "${CMAKE_CURRENT_BINARY_DIR}" -d "${SPHINX_CACHE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" "${SPHINX_HTML_DIR}" diff --git a/doc/sphinx/advanced_methods.rst b/doc/sphinx/advanced_methods.rst index 39a0935b25c..b18b813281d 100644 --- a/doc/sphinx/advanced_methods.rst +++ b/doc/sphinx/advanced_methods.rst @@ -68,7 +68,6 @@ Several modes are available for different types of binding. the point of contact or you can use :class:`espressomd.interactions.Virtual` which acts as a marker, only. The method is setup as follows:: - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() system.collision_detection.set_params( mode="bind_at_point_of_collision", distance=0.1, @@ -116,7 +115,6 @@ Several modes are available for different types of binding. The method is used as follows:: - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() system.collision_detection.set_params( mode="glue_to_surface", distance=0.1, diff --git a/src/script_interface/virtual_sites/CMakeLists.txt b/doc/sphinx/autodoc_overrides.py similarity index 56% rename from src/script_interface/virtual_sites/CMakeLists.txt rename to doc/sphinx/autodoc_overrides.py index c98d7a553e5..9f9885bcbeb 100644 --- a/src/script_interface/virtual_sites/CMakeLists.txt +++ b/doc/sphinx/autodoc_overrides.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2020-2022 The ESPResSo project +# Copyright (C) 2023 The ESPResSo project # # This file is part of ESPResSo. # @@ -17,5 +17,21 @@ # along with this program. If not, see . # -target_sources(espresso_script_interface - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/initialize.cpp) + +def update_file(filepath, function): + with open(filepath, "r+") as f: + content = function(f.read()) + f.seek(0) + f.truncate() + f.write(content) + + +def update_espressomd(content): + token = ".. automodule:: espressomd.propagation" + assert token in content + assert content.count(token) == 1 + content = content.replace(token, token + "\n :member-order: bysource", 1) + return content + + +update_file("espressomd.rst", update_espressomd) diff --git a/doc/sphinx/particles.rst b/doc/sphinx/particles.rst index 36d7a9fa590..0106aa354c6 100644 --- a/doc/sphinx/particles.rst +++ b/doc/sphinx/particles.rst @@ -251,26 +251,8 @@ according to their respective particle type. Before the next integration step, the forces accumulated on a virtual site are distributed back to those particles, from which the virtual site was derived. - -There are different schemes for virtual sites, described in the -following sections. To switch the active scheme, the system -:attr:`~espressomd.system.System.virtual_sites` property can be used:: - - import espressomd - import espressomd.virtual_sites - - system = espressomd.System(box_l=[1, 1, 1]) - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative(have_quaternion=False) - # or - system.virtual_sites = espressomd.virtual_sites.VirtualSitesOff() - -By default, :class:`espressomd.virtual_sites.VirtualSitesOff` is selected. -This means that virtual particles are not touched during integration. -The ``have_quaternion`` parameter determines whether the quaternion of -the virtual particle is updated (useful in combination with the -:attr:`~espressomd.particle_data.ParticleHandle.vs_quat` property of the -virtual particle which defines the orientation of the virtual particle -in the body fixed frame of the related real particle). +There are different schemes for virtual sites, described in the following sections. +The scheme acting on a virtual site is dependent on its propagation mode. .. _Rigid arrangements of particles: @@ -298,19 +280,17 @@ the non-virtual particle rotates, the virtual sites rotates on an orbit around the non-virtual particles center. To use this implementation of virtual sites, activate the feature -``VIRTUAL_SITES_RELATIVE``. Furthermore, an instance of -:class:`~espressomd.virtual_sites.VirtualSitesRelative` has to be set as the -active virtual sites scheme (see above). To set up a virtual site: +``VIRTUAL_SITES_RELATIVE``. Furthermore, particles have to be set up with the +propagation modes :attr:`~espressomd.propagation.Propagation.TRANS_VS_RELATIVE` +and :attr:`~espressomd.propagation.Propagation.ROT_VS_RELATIVE`. #. Place the particle to which the virtual site should be related. It needs to be in the center of mass of the rigid arrangement of particles you create:: import espressomd - import espressomd.virtual_sites system = espressomd.System(box_l=[10., 10., 10.]) - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() p1 = system.part.add(pos=[1., 2., 3.]) #. Place a particle at the desired relative position, make it virtual @@ -320,8 +300,8 @@ active virtual sites scheme (see above). To set up a virtual site: p2 = system.part.add(pos=p1.pos + rel_offset) p2.vs_auto_relate_to(p1) - This will set the :attr:`~espressomd.particle_data.ParticleHandle.virtual` - attribute on particle ``p2`` to ``True``. + The :meth:`~espressomd.particle_data.ParticleHandle.is_virtual` + method on particle ``p2`` will now return ``True``. #. Repeat the previous step with more virtual sites, if desired. @@ -371,22 +351,16 @@ Please note: Inertialess lattice-Boltzmann tracers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:class:`espressomd.virtual_sites.VirtualSitesInertialessTracers` - -When this implementation is selected, the virtual sites follow the motion of a -lattice-Boltzmann fluid (both, CPU and GPU). This is achieved by integrating +Using the propagation mode :attr:`~espressomd.propagation.Propagation.TRANS_LB_TRACER`, +the virtual sites follow the motion of a LB fluid. This is achieved by integrating their position using the fluid velocity at the virtual sites' position. Forces acting on the virtual sites are directly transferred as force density onto the lattice-Boltzmann fluid, making the coupling free of inertia. +Please note that the velocity attribute of the virtual particles +does not carry valid information for this virtual sites scheme. The feature stems from the implementation of the :ref:`Immersed Boundary Method for soft elastic objects`, but can be used independently. -For correct results, the LB thermostat has to be deactivated for virtual sites:: - - system.thermostat.set_lb(kT=0, act_on_virtual=False) - -Please note that the velocity attribute of the virtual particles does not carry valid information for this virtual sites scheme. - .. _Interacting with groups of particles: Interacting with groups of particles @@ -667,24 +641,22 @@ the total system is net force-free at all times. The resulting flow-field can th monopolar contributions in the far field and the slowest-decaying flow-field mode is a dipole. In |es|, the propulsion mechanism can be mimicked by applying a force to the fluid that is equal in magnitude but opposite in sign to the forward force on the swimming particle. -For this, particles can be marked as force appliers as shown in the following example: - -:: +For this, particles can be marked as force appliers as shown in the following example:: - dipole_partcl = system.part.add(pos=[1, 0, 0], - virtual = True, - swimming={'f_swim': swimmer.swimming['f_swim'], - 'is_engine_force_on_fluid': True}) + dipole = system.part.add(pos=[1, 0, 0], + swimming={"f_swim": swimmer.swimming["f_swim"], + "is_engine_force_on_fluid": True}) + dipole.vs_auto_relate_to(swimmer) + dipole.vs_quat = calc_quaternions_from_angles(np.pi, 0.) -This makes the particle not experience any friction or stochastic forces, but apply ``f_swim`` -along its internal orientation (``director``). -To make the force applier follow the swimmer and also update its orientation accordingly, -the :class:`espressomd.virtual_sites.VirtualSitesRelative` mechanism is used. +This makes the dipole particle not experience any friction or stochastic forces, +but apply ``f_swim`` along its internal orientation (``director``) +and follow the swimmer while updating its orientation accordingly. |es| provides a helper function :func:`~espressomd.swimmer_helpers.add_dipole_particle` to set up the virtual particle with the correct distance, relative position and orientation:: import espressomd.swimmer_helpers.add_dipole_particle as add_dip - dipole_partcl = add_dip(system, swimmer, 2., 0, mode="pusher") + dipole = add_dip(system, swimmer, 2., 0, mode="pusher") It creates pushers with the propulsion behind the swimmer and pullers with the propulsion in front of the swimmer. diff --git a/doc/tutorials/active_matter/active_matter.ipynb b/doc/tutorials/active_matter/active_matter.ipynb index 19c6874dffb..e8842d37e27 100644 --- a/doc/tutorials/active_matter/active_matter.ipynb +++ b/doc/tutorials/active_matter/active_matter.ipynb @@ -874,11 +874,7 @@ "outputs": [], "source": [ "import espressomd.lb\n", - "import espressomd.virtual_sites\n", - "import espressomd.swimmer_helpers\n", - "system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative(\n", - " have_quaternion=True\n", - ")" + "import espressomd.swimmer_helpers" ] }, { diff --git a/doc/tutorials/raspberry_electrophoresis/raspberry_electrophoresis.ipynb b/doc/tutorials/raspberry_electrophoresis/raspberry_electrophoresis.ipynb index 01dda0921ee..1d914418550 100644 --- a/doc/tutorials/raspberry_electrophoresis/raspberry_electrophoresis.ipynb +++ b/doc/tutorials/raspberry_electrophoresis/raspberry_electrophoresis.ipynb @@ -71,7 +71,6 @@ "import espressomd.interactions\n", "import espressomd.electrostatics\n", "import espressomd.lb\n", - "import espressomd.virtual_sites\n", "\n", "import sys\n", "import tqdm\n", @@ -335,43 +334,15 @@ "id": "478a2301", "metadata": {}, "source": [ - "Now that the beads are arranged in the shape of a raspberry, the surface beads are made virtual particles\n", + "Now that the beads are arranged in the shape of a raspberry, the surface beads need to be set up as virtual particles\n", "using the `VirtualSitesRelative` scheme. This converts the raspberry to a rigid body\n", - "in which the surface particles follow the translation and rotation of the central particle.\n", + "in which the surface particles follow the translation and rotation of the central particle,\n", + "and transfer the interpolated LB momentum back to the central particle.\n", "Newton's equations of motion are only integrated for the central particle.\n", "It is given an appropriate mass and moment of inertia tensor (note that the inertia tensor\n", "is given in the frame in which it is diagonal.)" ] }, - { - "cell_type": "markdown", - "id": "cb7fc3a7", - "metadata": {}, - "source": [ - "#### Exercise\n", - "Activate the `VirtualSitesRelative` implementation in **ESPResSo**. See the [documentation](https://espressomd.github.io/doc/particles.html#virtual-sites) for help." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fae9dbe4", - "metadata": {}, - "outputs": [], - "source": [ - "# SOLUTION CELL\n", - "# Select the desired implementation for virtual sites\n", - "system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9f256d8e", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, @@ -435,7 +406,7 @@ "# Convert the surface particles to virtual sites related to the central particle\n", "# The id of the central particles is 0, the ids of the surface particles start at 1.\n", "for p in surface_parts:\n", - " p.vs_auto_relate_to(central_part)" + " p.vs_auto_relate_to(central_part, couple_to_lb=True)" ] }, { diff --git a/samples/drude_bmimpf6.py b/samples/drude_bmimpf6.py index 20bb311cb7e..8579c480573 100644 --- a/samples/drude_bmimpf6.py +++ b/samples/drude_bmimpf6.py @@ -34,7 +34,6 @@ import espressomd.electrostatics import espressomd.interactions import espressomd.drude_helpers -import espressomd.virtual_sites import espressomd.visualization required_features = ["LENNARD_JONES", "P3M", "MASS", "ROTATION", @@ -80,7 +79,6 @@ print("\n-->Ion pairs:", n_ionpairs, "Box size:", box_l) system = espressomd.System(box_l=[box_l, box_l, box_l]) -system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() if args.visu: d_scale = 0.988 * 0.5 diff --git a/samples/immersed_boundary/addSoft.py b/samples/immersed_boundary/addSoft.py index e851bed8d45..c0baed64073 100644 --- a/samples/immersed_boundary/addSoft.py +++ b/samples/immersed_boundary/addSoft.py @@ -31,7 +31,7 @@ def AddSoft(system, comX, comY, comZ, k1, k2): X = float(line[0]) + comX Y = float(line[1]) + comY Z = float(line[2]) + comZ - system.part.add(id=i, pos=[X, Y, Z], virtual=True) + system.part.add(id=i, pos=[X, Y, Z]) # triangles import espressomd.interactions diff --git a/samples/immersed_boundary/sampleImmersedBoundary.py b/samples/immersed_boundary/sampleImmersedBoundary.py index 09fa59b2ef5..b8ac18771a1 100644 --- a/samples/immersed_boundary/sampleImmersedBoundary.py +++ b/samples/immersed_boundary/sampleImmersedBoundary.py @@ -26,7 +26,6 @@ import espressomd import espressomd.lb import espressomd.shapes -import espressomd.virtual_sites required_features = ["VIRTUAL_SITES_INERTIALESS_TRACERS", "WALBERLA"] espressomd.assert_features(required_features) @@ -48,7 +47,6 @@ system = espressomd.System(box_l=(20, 20, boxZ)) system.time_step = 1 / 6. system.cell_system.skin = 0.1 -system.virtual_sites = espressomd.virtual_sites.VirtualSitesInertialessTracers() print(f"Parallelization: {system.cell_system.node_grid}") force = 0.001 @@ -79,7 +77,7 @@ agrid=1, density=1, kinematic_viscosity=1, tau=system.time_step, ext_force_density=[force, 0, 0]) system.lb = lbf -system.thermostat.set_lb(LB_fluid=lbf, gamma=1.0, act_on_virtual=False) +system.thermostat.set_lb(LB_fluid=lbf, gamma=1.0) # Setup boundaries wall_shapes = [None] * 2 diff --git a/samples/rigid_body.py b/samples/rigid_body.py index b7b8edcd353..40fc44d5040 100644 --- a/samples/rigid_body.py +++ b/samples/rigid_body.py @@ -26,9 +26,7 @@ import math import numpy as np - import espressomd -import espressomd.virtual_sites import espressomd.rotation required_features = ["VIRTUAL_SITES_RELATIVE", "MASS", "ROTATIONAL_INERTIA"] @@ -36,7 +34,6 @@ system = espressomd.System(box_l=[10.0] * 3) -system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() system.time_step = 0.01 system.thermostat.set_langevin(kT=1.0, gamma=20.0, seed=42) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 53a9a8c039c..6a53208beae 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -39,6 +39,7 @@ add_library( particle_node.cpp polymer.cpp pressure.cpp + propagation.cpp rattle.cpp rotation.cpp Observable_stat.cpp diff --git a/src/core/virtual_sites/VirtualSitesOff.hpp b/src/core/CellParticleIterator.hpp similarity index 72% rename from src/core/virtual_sites/VirtualSitesOff.hpp rename to src/core/CellParticleIterator.hpp index 71bd1e9f71e..b6a5ee9d26d 100644 --- a/src/core/virtual_sites/VirtualSitesOff.hpp +++ b/src/core/CellParticleIterator.hpp @@ -16,15 +16,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef VIRTUAL_SITES_VIRTUAL_SITES_OFF_HPP -#define VIRTUAL_SITES_VIRTUAL_SITES_OFF_HPP -#include "config/config.hpp" -#ifdef VIRTUAL_SITES -#include "VirtualSites.hpp" +#pragma once -/** @brief Do-nothing virtual-sites implementation */ -class VirtualSitesOff : public VirtualSites {}; +#include "ParticleIterator.hpp" +#include "cell_system/Cell.hpp" -#endif -#endif +using CellParticleIterator = ParticleIterator; diff --git a/src/core/Particle.hpp b/src/core/Particle.hpp index 67ee102e4dd..46b9085e043 100644 --- a/src/core/Particle.hpp +++ b/src/core/Particle.hpp @@ -16,12 +16,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef ESPRESSO_CORE_PARTICLE_HPP -#define ESPRESSO_CORE_PARTICLE_HPP + +#pragma once #include "config/config.hpp" #include "BondList.hpp" +#include "PropagationMode.hpp" #include #include @@ -74,13 +75,8 @@ struct ParticleProperties { int mol_id = 0; /** particle type, used for non-bonded interactions. */ int type = 0; - -#ifdef VIRTUAL_SITES - /** is particle virtual */ - bool is_virtual = false; -#else // VIRTUAL_SITES - static constexpr bool is_virtual = false; -#endif // VIRTUAL_SITES + /** which propagation schemes should be applied to the particle **/ + int propagation = PropagationMode::SYSTEM_DEFAULT; #ifdef ROTATION /** Bitfield for the particle axes of rotation. @@ -206,6 +202,8 @@ struct ParticleProperties { ar &identity; ar &mol_id; ar &type; + ar &propagation; + #ifdef MASS ar &mass; #endif @@ -228,12 +226,9 @@ struct ParticleProperties { #ifdef DIPOLE_FIELD_TRACKING ar &dip_fld; #endif -#ifdef VIRTUAL_SITES - ar &is_virtual; #ifdef VIRTUAL_SITES_RELATIVE ar &vs_relative; #endif -#endif // VIRTUAL_SITES #ifdef THERMOSTAT_PER_PARTICLE ar γ @@ -422,6 +417,9 @@ struct Particle { // NOLINT(bugprone-exception-escape) auto const &type() const { return p.type; } auto &type() { return p.type; } + auto const &propagation() const { return p.propagation; } + auto &propagation() { return p.propagation; } + bool operator==(Particle const &rhs) const { return id() == rhs.id(); } bool operator!=(Particle const &rhs) const { return id() != rhs.id(); } @@ -516,17 +514,18 @@ struct Particle { // NOLINT(bugprone-exception-escape) auto &mu_E() { return p.mu_E; } #endif #ifdef VIRTUAL_SITES - auto &virtual_flag() { return p.is_virtual; } - auto const &virtual_flag() const { return p.is_virtual; } - auto is_virtual() const { return p.is_virtual; } - void set_virtual(bool const virt_flag) { p.is_virtual = virt_flag; } + auto is_virtual() const { + return (p.propagation & (PropagationMode::TRANS_VS_RELATIVE | + PropagationMode::ROT_VS_RELATIVE | + PropagationMode::TRANS_LB_TRACER)) != 0; + } +#else + constexpr auto is_virtual() const { return false; } +#endif // VIRTUAL_SITES #ifdef VIRTUAL_SITES_RELATIVE auto const &vs_relative() const { return p.vs_relative; } auto &vs_relative() { return p.vs_relative; } #endif // VIRTUAL_SITES_RELATIVE -#else - constexpr auto is_virtual() const { return p.is_virtual; } -#endif #ifdef THERMOSTAT_PER_PARTICLE auto const &gamma() const { return p.gamma; } auto &gamma() { return p.gamma; } @@ -624,5 +623,3 @@ BOOST_IS_BITWISE_SERIALIZABLE(ParticleRattle) #ifdef VIRTUAL_SITES_RELATIVE BOOST_IS_BITWISE_SERIALIZABLE(decltype(ParticleProperties::vs_relative)) #endif - -#endif diff --git a/src/core/ParticleRange.hpp b/src/core/ParticleRange.hpp index 378ca0418e9..5334098b9df 100644 --- a/src/core/ParticleRange.hpp +++ b/src/core/ParticleRange.hpp @@ -16,19 +16,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef ESPRESSO_SRC_CORE_PARTICLE_RANGE_HPP -#define ESPRESSO_SRC_CORE_PARTICLE_RANGE_HPP +#pragma once + +#include "CellParticleIterator.hpp" #include "Particle.hpp" #include "ParticleIterator.hpp" +#include "PropagationPredicate.hpp" #include "cell_system/Cell.hpp" #include #include -using CellParticleIterator = ParticleIterator; - /** * @brief A range of particles. * @@ -49,8 +49,13 @@ class ParticleRange : public boost::iterator_range { return assert(m_size >= 0), static_cast(m_size); } + template + ParticleRangeFiltered filter(Predicate pred) const { + auto const predicate = PropagationPredicate(pred); + return {boost::make_filter_iterator(predicate, begin(), end()), + boost::make_filter_iterator(predicate, end(), end())}; + } + private: base_type::difference_type mutable m_size = -1; }; - -#endif diff --git a/src/core/PropagationMode.hpp b/src/core/PropagationMode.hpp new file mode 100644 index 00000000000..ff87400f10a --- /dev/null +++ b/src/core/PropagationMode.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 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 + +namespace PropagationMode { +/** + * @brief Flags to create bitmasks for propagation modes. + */ +enum PropagationMode : int { + NONE = 0, + SYSTEM_DEFAULT = 1 << 0, + TRANS_NEWTON = 1 << 1, + TRANS_LANGEVIN = 1 << 2, + TRANS_LANGEVIN_NPT = 1 << 3, + TRANS_VS_RELATIVE = 1 << 4, + TRANS_LB_MOMENTUM_EXCHANGE = 1 << 5, + TRANS_LB_TRACER = 1 << 6, + TRANS_BROWNIAN = 1 << 7, + TRANS_STOKESIAN = 1 << 8, + ROT_EULER = 1 << 10, + ROT_LANGEVIN = 1 << 11, + ROT_VS_RELATIVE = 1 << 12, + ROT_BROWNIAN = 1 << 13, + ROT_STOKESIAN = 1 << 14, +}; +} // namespace PropagationMode + +/** \name Integrator switches */ +enum IntegratorSwitch : int { + INTEG_METHOD_NPT_ISO = 0, + INTEG_METHOD_NVT = 1, + INTEG_METHOD_STEEPEST_DESCENT = 2, + INTEG_METHOD_BD = 3, + INTEG_METHOD_SD = 7, +}; diff --git a/src/core/PropagationPredicate.hpp b/src/core/PropagationPredicate.hpp new file mode 100644 index 00000000000..d2db196f58a --- /dev/null +++ b/src/core/PropagationPredicate.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 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 "CellParticleIterator.hpp" +#include "ParticleIterator.hpp" + +#include + +#include + +template struct PropagationPredicate { + Predicate predicate; + + PropagationPredicate(Predicate pred) : predicate(pred) {} + + bool operator()(Particle const &p) { return predicate(p.propagation()); }; +}; + +template +class ParticleRangeFiltered + : public boost::iterator_range, CellParticleIterator>> { + using base_type = boost::iterator_range, CellParticleIterator>>; + +public: + using base_type::base_type; + auto size() const { return std::distance(this->begin(), this->end()); }; +}; diff --git a/src/core/accumulators/Correlator.cpp b/src/core/accumulators/Correlator.cpp index cbc1c453a07..fccadbd62cd 100644 --- a/src/core/accumulators/Correlator.cpp +++ b/src/core/accumulators/Correlator.cpp @@ -18,8 +18,6 @@ */ #include "Correlator.hpp" -#include "integrate.hpp" - #include #include #include diff --git a/src/core/accumulators/Correlator.hpp b/src/core/accumulators/Correlator.hpp index 42588719223..e60fa27d40b 100644 --- a/src/core/accumulators/Correlator.hpp +++ b/src/core/accumulators/Correlator.hpp @@ -101,8 +101,8 @@ */ #include "AccumulatorBase.hpp" -#include "integrate.hpp" #include "observables/Observable.hpp" +#include "system/System.hpp" #include @@ -155,11 +155,12 @@ class Correlator : public AccumulatorBase { obs_ptr obs2, Utils::Vector3d correlation_args_ = {}) : AccumulatorBase(delta_N), finalized(false), t(0), m_correlation_args(correlation_args_), m_tau_lin(tau_lin), - m_dt(delta_N * get_time_step()), m_tau_max(tau_max), + m_dt(::System::get_system().get_time_step()), m_tau_max(tau_max), compressA_name(std::move(compress1_)), compressB_name(std::move(compress2_)), corr_operation_name(std::move(corr_operation)), A_obs(std::move(obs1)), B_obs(std::move(obs2)) { + m_dt *= static_cast(delta_N); initialize(); } diff --git a/src/core/bonded_interactions/thermalized_bond_utils.cpp b/src/core/bonded_interactions/thermalized_bond_utils.cpp index edde9e90e03..1b87b98c063 100644 --- a/src/core/bonded_interactions/thermalized_bond_utils.cpp +++ b/src/core/bonded_interactions/thermalized_bond_utils.cpp @@ -20,7 +20,6 @@ */ #include "thermalized_bond_utils.hpp" -#include "integrate.hpp" #include "thermalized_bond.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" diff --git a/src/core/cell_system/HybridDecomposition.hpp b/src/core/cell_system/HybridDecomposition.hpp index f5f7220a926..c8993027b7d 100644 --- a/src/core/cell_system/HybridDecomposition.hpp +++ b/src/core/cell_system/HybridDecomposition.hpp @@ -31,7 +31,6 @@ #include "LocalBox.hpp" #include "Particle.hpp" #include "ghosts.hpp" -#include "integrate.hpp" #include #include diff --git a/src/core/cells.cpp b/src/core/cells.cpp index 4c8676074b9..a0f95342f86 100644 --- a/src/core/cells.cpp +++ b/src/core/cells.cpp @@ -35,7 +35,6 @@ #include "Particle.hpp" #include "communication.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" #include "particle_node.hpp" #include "system/System.hpp" diff --git a/src/core/collision.cpp b/src/core/collision.cpp index 88941d72a6c..51e5cc1e83c 100644 --- a/src/core/collision.cpp +++ b/src/core/collision.cpp @@ -356,7 +356,6 @@ static void place_vs_and_relate_to_particle(CellStructure &cell_structure, 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->set_virtual(true); p_vs->type() = collision_params.vs_particle_type; } @@ -481,7 +480,7 @@ static void three_particle_binding_domain_decomposition( // Handle the collisions stored in the queue void handle_collisions(CellStructure &cell_structure) { - auto const &system = System::get_system(); + auto &system = System::get_system(); auto const &box_geo = *system.box_geo; // 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 @@ -657,6 +656,7 @@ void handle_collisions(CellStructure &cell_structure) { 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 diff --git a/src/core/dpd.cpp b/src/core/dpd.cpp index da57ce7fe5e..9ee236f798c 100644 --- a/src/core/dpd.cpp +++ b/src/core/dpd.cpp @@ -30,7 +30,6 @@ #include "BoxGeometry.hpp" #include "cell_system/CellStructure.hpp" #include "cells.hpp" -#include "integrate.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include "random.hpp" #include "system/System.hpp" diff --git a/src/core/ek/Solver.cpp b/src/core/ek/Solver.cpp index a11aed8eb98..2de532a762d 100644 --- a/src/core/ek/Solver.cpp +++ b/src/core/ek/Solver.cpp @@ -26,7 +26,6 @@ #include "ek/EKNone.hpp" #include "ek/EKWalberla.hpp" -#include "integrate.hpp" #include "system/System.hpp" #include diff --git a/src/core/electrostatics/coulomb.cpp b/src/core/electrostatics/coulomb.cpp index 870bd72ea92..52850937530 100644 --- a/src/core/electrostatics/coulomb.cpp +++ b/src/core/electrostatics/coulomb.cpp @@ -32,8 +32,6 @@ #include "communication.hpp" #include "electrostatics/icc.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" -#include "npt.hpp" #include "system/System.hpp" #include @@ -114,8 +112,7 @@ struct LongRangePressure { #ifdef P3M auto operator()(std::shared_ptr const &actor) const { - actor->charge_assign(m_particles); - return actor->p3m_calc_kspace_pressure_tensor(); + return actor->long_range_pressure(m_particles); } #endif // P3M @@ -215,24 +212,10 @@ struct LongRangeForce { #ifdef P3M void operator()(std::shared_ptr const &actor) const { - actor->charge_assign(m_particles); -#ifdef NPT - if (integ_switch == INTEG_METHOD_NPT_ISO) { - auto const energy = actor->long_range_kernel(true, true, m_particles); - npt_add_virial_contribution(energy); - } else -#endif // NPT - actor->add_long_range_forces(m_particles); + actor->add_long_range_forces(m_particles); } #ifdef CUDA void operator()(std::shared_ptr const &actor) const { -#ifdef NPT - if (integ_switch == INTEG_METHOD_NPT_ISO) { - actor->charge_assign(m_particles); - auto const energy = actor->long_range_energy(m_particles); - npt_add_virial_contribution(energy); - } -#endif // NPT actor->add_long_range_forces(m_particles); } #endif // CUDA @@ -266,7 +249,6 @@ struct LongRangeEnergy { #ifdef P3M auto operator()(std::shared_ptr const &actor) const { - actor->charge_assign(m_particles); return actor->long_range_energy(m_particles); } auto diff --git a/src/core/electrostatics/icc.cpp b/src/core/electrostatics/icc.cpp index dd7348163a5..975e76d4f44 100644 --- a/src/core/electrostatics/icc.cpp +++ b/src/core/electrostatics/icc.cpp @@ -34,13 +34,14 @@ #include "Particle.hpp" #include "ParticleRange.hpp" +#include "PropagationMode.hpp" #include "actor/visitors.hpp" #include "cell_system/CellStructure.hpp" #include "communication.hpp" #include "electrostatics/coulomb.hpp" #include "electrostatics/coulomb_inline.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" +#include "integrators/Propagation.hpp" #include "system/System.hpp" #include @@ -269,7 +270,7 @@ struct SanityChecksICC { void ICCStar::sanity_check() const { sanity_checks_active_solver(); #ifdef NPT - if (integ_switch == INTEG_METHOD_NPT_ISO) { + if (get_system().propagation->integ_switch == INTEG_METHOD_NPT_ISO) { throw std::runtime_error("ICC does not work in the NPT ensemble"); } #endif diff --git a/src/core/electrostatics/p3m.cpp b/src/core/electrostatics/p3m.cpp index e62d5ff7724..1a78b36d108 100644 --- a/src/core/electrostatics/p3m.cpp +++ b/src/core/electrostatics/p3m.cpp @@ -43,12 +43,14 @@ #include "Particle.hpp" #include "ParticlePropertyIterator.hpp" #include "ParticleRange.hpp" +#include "PropagationMode.hpp" #include "actor/visitors.hpp" #include "cell_system/CellStructure.hpp" #include "cell_system/CellStructureType.hpp" #include "communication.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" +#include "integrators/Propagation.hpp" +#include "npt.hpp" #include "system/System.hpp" #include "tuning.hpp" @@ -404,7 +406,8 @@ static auto calc_dipole_moment(boost::mpi::communicator const &comm, * in @cite essmann95a. The part \f$\Pi_{\textrm{corr}, \alpha, \beta}\f$ * eq. (2.8) is not present here since M is the empty set in our simulations. */ -Utils::Vector9d CoulombP3M::p3m_calc_kspace_pressure_tensor() { +Utils::Vector9d +CoulombP3M::long_range_pressure(ParticleRange const &particles) { using namespace detail::FFT_indexing; auto const &box_geo = *get_system().box_geo; @@ -412,6 +415,7 @@ Utils::Vector9d CoulombP3M::p3m_calc_kspace_pressure_tensor() { Utils::Vector9d node_k_space_pressure_tensor{}; if (p3m.sum_q2 > 0.) { + charge_assign(particles); p3m.sm.gather_grid(p3m.rs_mesh.data(), comm_cart, p3m.local_mesh.dim); fft_perform_forw(p3m.rs_mesh.data(), p3m.fft, comm_cart); @@ -464,12 +468,25 @@ Utils::Vector9d CoulombP3M::p3m_calc_kspace_pressure_tensor() { double CoulombP3M::long_range_kernel(bool force_flag, bool energy_flag, ParticleRange const &particles) { - auto const &box_geo = *get_system().box_geo; + auto const &system = get_system(); + auto const &box_geo = *system.box_geo; +#ifdef NPT + auto const npt_flag = + force_flag and (system.propagation->integ_switch == INTEG_METHOD_NPT_ISO); +#else + auto constexpr npt_flag = false; +#endif - /* Gather information for FFT grid inside the nodes domain (inner local mesh) - * and perform forward 3D FFT (Charge Assignment Mesh). */ - p3m.sm.gather_grid(p3m.rs_mesh.data(), comm_cart, p3m.local_mesh.dim); - fft_perform_forw(p3m.rs_mesh.data(), p3m.fft, comm_cart); + if (p3m.sum_q2 > 0.) { + if (not has_actor_of_type( + system.coulomb.impl->solver)) { + charge_assign(particles); + } + /* Gather information for FFT grid inside the nodes domain (inner local + * mesh) and perform forward 3D FFT (Charge Assignment Mesh). */ + p3m.sm.gather_grid(p3m.rs_mesh.data(), comm_cart, p3m.local_mesh.dim); + fft_perform_forw(p3m.rs_mesh.data(), p3m.fft, comm_cart); + } auto p_q_range = ParticlePropertyRange::charge_range(particles); auto p_force_range = ParticlePropertyRange::force_range(particles); @@ -549,7 +566,7 @@ double CoulombP3M::long_range_kernel(bool force_flag, bool energy_flag, } /* === k-space energy calculation === */ - if (energy_flag) { + if (energy_flag or npt_flag) { auto node_energy = 0.; for (int i = 0; i < p3m.fft.plan[3].new_size; i++) { // Use the energy optimized influence function for energy! @@ -575,7 +592,14 @@ double CoulombP3M::long_range_kernel(bool force_flag, bool energy_flag, energy += pref * box_dipole.value().norm2(); } } - return prefactor * energy; + energy *= prefactor; + if (npt_flag) { + npt_add_virial_contribution(energy); + } + if (not energy_flag) { + energy = 0.; + } + return energy; } return 0.; diff --git a/src/core/electrostatics/p3m.hpp b/src/core/electrostatics/p3m.hpp index 82506a93590..776eb57cb3f 100644 --- a/src/core/electrostatics/p3m.hpp +++ b/src/core/electrostatics/p3m.hpp @@ -218,7 +218,7 @@ struct CoulombP3M : public Coulomb::Actor { } /** Compute the k-space part of the pressure tensor */ - Utils::Vector9d p3m_calc_kspace_pressure_tensor(); + Utils::Vector9d long_range_pressure(ParticleRange const &particles); /** Compute the k-space part of energies. */ double long_range_energy(ParticleRange const &particles) { diff --git a/src/core/electrostatics/p3m_gpu.cpp b/src/core/electrostatics/p3m_gpu.cpp index 425adff720c..f8cf8abfe88 100644 --- a/src/core/electrostatics/p3m_gpu.cpp +++ b/src/core/electrostatics/p3m_gpu.cpp @@ -29,6 +29,9 @@ #include "electrostatics/coulomb.hpp" #include "ParticleRange.hpp" +#include "PropagationMode.hpp" +#include "integrators/Propagation.hpp" +#include "npt.hpp" #include "system/GpuParticleData.hpp" #include "system/System.hpp" @@ -46,7 +49,15 @@ static auto get_n_part_safe(GpuParticleData const &gpu) { return static_cast(n_part); } -void CoulombP3MGPU::add_long_range_forces(ParticleRange const &) { +void CoulombP3MGPU::add_long_range_forces(ParticleRange const &particles) { +#ifdef NPT + if (get_system().propagation->integ_switch == INTEG_METHOD_NPT_ISO) { + auto const energy = long_range_energy(particles); + npt_add_virial_contribution(energy); + } +#else + static_cast(particles); +#endif if (this_node == 0) { auto &gpu = get_system().gpu; p3m_gpu_add_farfield_force(*m_gpu_data, gpu, prefactor, diff --git a/src/core/energy.cpp b/src/core/energy.cpp index 1ab986975c6..ae66fd23e5b 100644 --- a/src/core/energy.cpp +++ b/src/core/energy.cpp @@ -24,9 +24,7 @@ #include "cell_system/CellStructure.hpp" #include "constraints.hpp" #include "energy_inline.hpp" -#include "forces.hpp" #include "global_ghost_flags.hpp" -#include "integrate.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include "short_range_loop.hpp" #include "system/System.hpp" diff --git a/src/core/forces.cpp b/src/core/forces.cpp index d10ff935d9e..1bc21bffcbf 100644 --- a/src/core/forces.cpp +++ b/src/core/forces.cpp @@ -38,7 +38,7 @@ #include "forces_inline.hpp" #include "galilei/ComFixed.hpp" #include "immersed_boundaries.hpp" -#include "integrate.hpp" +#include "integrators/Propagation.hpp" #include "lb/particle_coupling.hpp" #include "magnetostatics/dipoles.hpp" #include "nonbonded_interactions/VerletCriterion.hpp" @@ -49,7 +49,7 @@ #include "system/System.hpp" #include "thermostat.hpp" #include "thermostats/langevin_inline.hpp" -#include "virtual_sites.hpp" +#include "virtual_sites/relative.hpp" #include @@ -64,11 +64,8 @@ #include #include -/** Initialize the forces for a ghost particle */ -inline ParticleForce init_ghost_force(Particle const &) { return {}; } - /** External particle forces */ -inline ParticleForce external_force(Particle const &p) { +static ParticleForce external_force(Particle const &p) { ParticleForce f = {}; #ifdef EXTERNAL_FORCES @@ -89,63 +86,22 @@ inline ParticleForce external_force(Particle const &p) { return f; } -inline ParticleForce thermostat_force(Particle const &p, double time_step, - double kT) { - extern LangevinThermostat langevin; - if (!(thermo_switch & THERMO_LANGEVIN)) { - return {}; - } - -#ifdef ROTATION - return {friction_thermo_langevin(langevin, p, time_step, kT), - p.can_rotate() ? convert_vector_body_to_space( - p, friction_thermo_langevin_rotation( - langevin, p, time_step, kT)) - : Utils::Vector3d{}}; -#else - return friction_thermo_langevin(langevin, p, time_step, kT); -#endif -} - -/** Initialize the forces for a real particle */ -inline ParticleForce init_real_particle_force(Particle const &p, - double time_step, double kT) { - return thermostat_force(p, time_step, kT) + external_force(p); -} - -static void init_forces(const ParticleRange &particles, - const ParticleRange &ghost_particles, double time_step, - double kT) { +static void init_forces(ParticleRange const &particles, + ParticleRange const &ghost_particles) { #ifdef CALIPER CALI_CXX_MARK_FUNCTION; #endif - /* The force initialization depends on the used thermostat and the - thermodynamic ensemble */ - -#ifdef NPT - npt_reset_instantaneous_virials(); -#endif - - /* initialize forces with Langevin thermostat forces - or zero depending on the thermostat - set torque to zero for all and rescale quaternions - */ for (auto &p : particles) { - p.force_and_torque() = init_real_particle_force(p, time_step, kT); + p.force_and_torque() = external_force(p); } - /* initialize ghost forces with zero - set torque to zero for all and rescale quaternions - */ - for (auto &p : ghost_particles) { - p.force_and_torque() = init_ghost_force(p); - } + init_forces_ghosts(ghost_particles); } -void init_forces_ghosts(const ParticleRange &particles) { +void init_forces_ghosts(ParticleRange const &particles) { for (auto &p : particles) { - p.force_and_torque() = init_ghost_force(p); + p.force_and_torque() = {}; } } @@ -188,8 +144,12 @@ void System::System::calculate_forces(double kT) { (**icc).iteration(*cell_structure, particles, ghost_particles); } } +#endif // ELECTROSTATICS +#ifdef NPT + npt_reset_instantaneous_virials(); #endif - init_forces(particles, ghost_particles, time_step, kT); + init_forces(particles, ghost_particles); + thermostats_force_init(kT); calc_long_range_forces(particles); @@ -254,7 +214,7 @@ void System::System::calculate_forces(double kT) { immersed_boundaries.volume_conservation(*cell_structure); if (lb.is_solver_set()) { - LB::couple_particles(thermo_virtual, particles, ghost_particles, time_step); + LB::couple_particles(particles, ghost_particles, time_step); } #ifdef CUDA @@ -267,12 +227,14 @@ void System::System::calculate_forces(double kT) { #endif #endif // CUDA -// VIRTUAL_SITES distribute forces -#ifdef VIRTUAL_SITES - virtual_sites()->back_transfer_forces_and_torques(); +#ifdef VIRTUAL_SITES_RELATIVE + if (propagation->used_propagations & + (PropagationMode::TRANS_VS_RELATIVE | PropagationMode::ROT_VS_RELATIVE)) { + vs_relative_back_transfer_forces_and_torques(*cell_structure); + } #endif - // Communication Step: ghost forces + // Communication step: ghost forces cell_structure->ghosts_reduce_forces(); // should be pretty late, since it needs to zero out the total force @@ -282,7 +244,7 @@ void System::System::calculate_forces(double kT) { force_capping(particles, force_cap); // mark that forces are now up-to-date - recalc_forces = false; + propagation->recalc_forces = false; } void calc_long_range_forces(const ParticleRange &particles) { @@ -293,7 +255,6 @@ void calc_long_range_forces(const ParticleRange &particles) { #ifdef ELECTROSTATICS /* calculate k-space part of electrostatic interaction. */ Coulomb::get_coulomb().calc_long_range_force(particles); - #endif // ELECTROSTATICS #ifdef DIPOLES diff --git a/src/core/forces.hpp b/src/core/forces.hpp index e353fc23fa7..b65c4155018 100644 --- a/src/core/forces.hpp +++ b/src/core/forces.hpp @@ -28,24 +28,20 @@ */ #include "ParticleRange.hpp" -#include "system/System.hpp" #include -#include - -/** initialize real particle forces with thermostat forces and - ghost particle forces with zero. */ -void init_forces(const ParticleRange &particles, double time_step); +/** Assign external forces/torques to real particles and zero to ghosts. */ +void init_forces(ParticleRange const &particles, double time_step); /** Set forces of all ghosts to zero */ -void init_forces_ghosts(const ParticleRange &particles); +void init_forces_ghosts(ParticleRange const &particles); /** Calculate long range forces (P3M, ...). */ -void calc_long_range_forces(const ParticleRange &particles); +void calc_long_range_forces(ParticleRange const &particles); #ifdef NPT /** Update the NpT virial */ -void npt_add_virial_force_contribution(const Utils::Vector3d &force, - const Utils::Vector3d &d); +void npt_add_virial_force_contribution(Utils::Vector3d const &force, + Utils::Vector3d const &d); #endif diff --git a/src/core/ghosts.cpp b/src/core/ghosts.cpp index 4655c09ae86..c513656ffb9 100644 --- a/src/core/ghosts.cpp +++ b/src/core/ghosts.cpp @@ -126,10 +126,7 @@ serialize_and_reduce(Archive &ar, Particle &p, unsigned int data_parts, BoxGeometry const &box_geo, Utils::Vector3d const *ghost_shift) { if (data_parts & GHOSTTRANS_PROPRTS) { - ar &p.id() & p.mol_id() & p.type(); -#ifdef VIRTUAL_SITES - ar &p.virtual_flag(); -#endif + ar &p.id() & p.mol_id() & p.type() & p.propagation(); #ifdef ROTATION ar &p.rotation(); #ifdef ROTATIONAL_INERTIA diff --git a/src/core/integrate.cpp b/src/core/integrate.cpp index 97578018a75..9b2bcd50bb2 100644 --- a/src/core/integrate.cpp +++ b/src/core/integrate.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2022 The ESPResSo project + * Copyright (C) 2010-2023 The ESPResSo project * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010 * Max-Planck-Institute for Polymer Research, Theory Group * @@ -27,6 +27,7 @@ */ #include "integrate.hpp" +#include "integrators/Propagation.hpp" #include "integrators/brownian_inline.hpp" #include "integrators/steepest_descent.hpp" #include "integrators/stokesian_dynamics_inline.hpp" @@ -35,6 +36,7 @@ #include "BoxGeometry.hpp" #include "ParticleRange.hpp" +#include "PropagationMode.hpp" #include "accumulators.hpp" #include "bond_breakage/bond_breakage.hpp" #include "bonded_interactions/rigid_bond.hpp" @@ -55,7 +57,9 @@ #include "signalhandling.hpp" #include "system/System.hpp" #include "thermostat.hpp" -#include "virtual_sites.hpp" +#include "thermostats/langevin_inline.hpp" +#include "virtual_sites/lb_tracers.hpp" +#include "virtual_sites/relative.hpp" #include @@ -83,107 +87,161 @@ #endif #endif -int integ_switch = INTEG_METHOD_NVT; - -/** Actual simulation time. */ -static double sim_time = 0.0; - -bool recalc_forces = true; - -static int lb_skipped_md_steps = 0; -static int ek_skipped_md_steps = 0; - namespace { volatile std::sig_atomic_t ctrl_C = 0; } // namespace namespace LeesEdwards { -/** @brief Currently active Lees-Edwards protocol. */ -static std::shared_ptr protocol = nullptr; - -std::weak_ptr get_protocol() { return protocol; } /** * @brief Update the Lees-Edwards parameters of the box geometry * for the current simulation time. */ -static void update_box_params(BoxGeometry &box_geo) { +void LeesEdwards::update_box_params(BoxGeometry &box_geo, double sim_time) { if (box_geo.type() == BoxType::LEES_EDWARDS) { - assert(protocol != nullptr); - box_geo.lees_edwards_update(get_pos_offset(sim_time, *protocol), - get_shear_velocity(sim_time, *protocol)); + assert(m_protocol != nullptr); + box_geo.lees_edwards_update(get_pos_offset(sim_time, *m_protocol), + get_shear_velocity(sim_time, *m_protocol)); } } -void set_protocol(std::shared_ptr new_protocol) { - auto &system = System::get_system(); +void LeesEdwards::set_protocol(std::shared_ptr protocol) { + auto &system = get_system(); auto &cell_structure = *system.cell_structure; auto &box_geo = *system.box_geo; box_geo.set_type(BoxType::LEES_EDWARDS); - protocol = std::move(new_protocol); - LeesEdwards::update_box_params(box_geo); - ::recalc_forces = true; + m_protocol = std::move(protocol); + update_box_params(box_geo, system.get_sim_time()); + system.propagation->recalc_forces = true; cell_structure.set_resort_particles(Cells::RESORT_LOCAL); } -void unset_protocol() { - auto &system = System::get_system(); +void LeesEdwards::unset_protocol() { + auto &system = get_system(); auto &cell_structure = *system.cell_structure; auto &box_geo = *system.box_geo; - protocol = nullptr; + m_protocol = nullptr; box_geo.set_type(BoxType::CUBOID); - ::recalc_forces = true; + system.propagation->recalc_forces = true; cell_structure.set_resort_particles(Cells::RESORT_LOCAL); } -template void run_kernel(BoxGeometry const &box_geo) { - if (box_geo.type() == BoxType::LEES_EDWARDS) { - auto &system = System::get_system(); - auto &cell_structure = *system.cell_structure; - auto const kernel = Kernel{box_geo}; - auto const particles = cell_structure.local_particles(); - std::for_each(particles.begin(), particles.end(), - [&kernel](auto &p) { kernel(p); }); +} // namespace LeesEdwards + +void Propagation::update_default_propagation() { + switch (integ_switch) { + case INTEG_METHOD_STEEPEST_DESCENT: + default_propagation = PropagationMode::NONE; + break; + case INTEG_METHOD_NVT: { + // NOLINTNEXTLINE(bugprone-branch-clone) + if (thermo_switch & THERMO_LB & THERMO_LANGEVIN) { + default_propagation = PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE; +#ifdef ROTATION + default_propagation |= PropagationMode::ROT_LANGEVIN; +#endif + } else if (thermo_switch & THERMO_LB) { + default_propagation = PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE; +#ifdef ROTATION + default_propagation |= PropagationMode::ROT_EULER; +#endif + } else if (thermo_switch & THERMO_LANGEVIN) { + default_propagation = PropagationMode::TRANS_LANGEVIN; +#ifdef ROTATION + default_propagation |= PropagationMode::ROT_LANGEVIN; +#endif + } else { + default_propagation = PropagationMode::TRANS_NEWTON; +#ifdef ROTATION + default_propagation |= PropagationMode::ROT_EULER; +#endif + } + break; + } +#ifdef NPT + case INTEG_METHOD_NPT_ISO: + default_propagation = PropagationMode::TRANS_LANGEVIN_NPT; + break; +#endif + case INTEG_METHOD_BD: + default_propagation = PropagationMode::TRANS_BROWNIAN; +#ifdef ROTATION + default_propagation |= PropagationMode::ROT_BROWNIAN; +#endif + break; +#ifdef STOKESIAN_DYNAMICS + case INTEG_METHOD_SD: + default_propagation = PropagationMode::TRANS_STOKESIAN; + break; +#endif // STOKESIAN_DYNAMICS + default: + throw std::runtime_error("Unknown value for integ_switch"); } } -} // namespace LeesEdwards + +void System::System::update_used_propagations() { + int used_propagations = PropagationMode::NONE; + for (auto &p : cell_structure->local_particles()) { + used_propagations |= p.propagation(); + } + if (used_propagations & PropagationMode::SYSTEM_DEFAULT) { + used_propagations |= propagation->default_propagation; + } + used_propagations = boost::mpi::all_reduce(::comm_cart, used_propagations, + std::bit_or()); + propagation->used_propagations = used_propagations; +} void System::System::integrator_sanity_checks() const { if (time_step <= 0.) { runtimeErrorMsg() << "time_step not set"; } - switch (integ_switch) { - case INTEG_METHOD_STEEPEST_DESCENT: - if (thermo_switch != THERMO_OFF) + if (propagation->integ_switch == INTEG_METHOD_STEEPEST_DESCENT) { + if (thermo_switch != THERMO_OFF) { runtimeErrorMsg() << "The steepest descent integrator is incompatible with thermostats"; - break; - case INTEG_METHOD_NVT: - if (thermo_switch & (THERMO_NPT_ISO | THERMO_BROWNIAN | THERMO_SD)) + } + } + if (propagation->integ_switch == INTEG_METHOD_NVT) { + if (thermo_switch & (THERMO_NPT_ISO | THERMO_BROWNIAN | THERMO_SD)) { runtimeErrorMsg() << "The VV integrator is incompatible with the " "currently active combination of thermostats"; - break; + } + } #ifdef NPT - case INTEG_METHOD_NPT_ISO: - if (thermo_switch != THERMO_OFF and thermo_switch != THERMO_NPT_ISO) + if (propagation->used_propagations & PropagationMode::TRANS_LANGEVIN_NPT) { + if (thermo_switch != THERMO_OFF and thermo_switch != THERMO_NPT_ISO) { runtimeErrorMsg() << "The NpT integrator requires the NpT thermostat"; - if (box_geo->type() == BoxType::LEES_EDWARDS) + } + if (box_geo->type() == BoxType::LEES_EDWARDS) { runtimeErrorMsg() << "The NpT integrator cannot use Lees-Edwards"; - break; + } + } #endif - case INTEG_METHOD_BD: - if (thermo_switch != THERMO_BROWNIAN) + if (propagation->used_propagations & PropagationMode::TRANS_BROWNIAN) { + if (thermo_switch != THERMO_BROWNIAN) { runtimeErrorMsg() << "The BD integrator requires the BD thermostat"; - break; + } + } + if (propagation->used_propagations & PropagationMode::TRANS_STOKESIAN) { #ifdef STOKESIAN_DYNAMICS - case INTEG_METHOD_SD: - if (thermo_switch != THERMO_OFF and thermo_switch != THERMO_SD) + if (thermo_switch != THERMO_OFF && thermo_switch != THERMO_SD) { runtimeErrorMsg() << "The SD integrator requires the SD thermostat"; - break; + } #endif - default: - runtimeErrorMsg() << "Unknown value for integ_switch"; } +#ifdef ROTATION + for (auto const &p : cell_structure->local_particles()) { + using namespace PropagationMode; + if (p.can_rotate() and not p.is_virtual() and + (p.propagation() & (SYSTEM_DEFAULT | ROT_EULER | ROT_LANGEVIN | + ROT_BROWNIAN | ROT_STOKESIAN)) == 0) { + runtimeErrorMsg() + << "Rotating particles must have a rotation propagation mode enabled"; + break; + } + } +#endif } #ifdef WALBERLA @@ -238,76 +296,130 @@ static void resort_particles_if_needed(System::System &system) { } } +void System::System::thermostats_force_init(double kT) { + auto const &propagation = *this->propagation; + for (auto &p : cell_structure->local_particles()) { + if (propagation.should_propagate_with(p, PropagationMode::TRANS_LANGEVIN)) + p.force() += friction_thermo_langevin(langevin, p, time_step, kT); +#ifdef ROTATION + if (propagation.should_propagate_with(p, PropagationMode::ROT_LANGEVIN)) + p.torque() += convert_vector_body_to_space( + p, friction_thermo_langevin_rotation(langevin, p, time_step, kT)); +#endif + } +} + /** @brief Calls the hook for propagation kernels before the force calculation * @return whether or not to stop the integration loop early. */ static bool integrator_step_1(ParticleRange const &particles, + Propagation const &propagation, double kT, double time_step) { - bool early_exit = false; - switch (integ_switch) { - case INTEG_METHOD_STEEPEST_DESCENT: - early_exit = steepest_descent_step(particles); - break; - case INTEG_METHOD_NVT: - velocity_verlet_step_1(particles, time_step); - break; + // steepest decent + if (propagation.integ_switch == INTEG_METHOD_STEEPEST_DESCENT) + return steepest_descent_step(particles); + + for (auto &p : particles) { +#ifdef VIRTUAL_SITES + // virtual sites are updated later in the integration loop + if (p.is_virtual()) + continue; +#endif + if (propagation.should_propagate_with( + p, PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE)) + velocity_verlet_propagator_1(p, time_step); + if (propagation.should_propagate_with(p, PropagationMode::TRANS_NEWTON)) + velocity_verlet_propagator_1(p, time_step); +#ifdef ROTATION + if (propagation.should_propagate_with(p, PropagationMode::ROT_EULER)) + velocity_verlet_rotator_1(p, time_step); +#endif + if (propagation.should_propagate_with(p, PropagationMode::TRANS_LANGEVIN)) + velocity_verlet_propagator_1(p, time_step); +#ifdef ROTATION + if (propagation.should_propagate_with(p, PropagationMode::ROT_LANGEVIN)) + velocity_verlet_rotator_1(p, time_step); +#endif + if (propagation.should_propagate_with(p, PropagationMode::TRANS_BROWNIAN)) + brownian_dynamics_propagator(brownian, p, time_step, kT); +#ifdef ROTATION + if (propagation.should_propagate_with(p, PropagationMode::ROT_BROWNIAN)) + brownian_dynamics_rotator(brownian, p, time_step, kT); +#endif + } + #ifdef NPT - case INTEG_METHOD_NPT_ISO: - velocity_verlet_npt_step_1(particles, time_step); - break; + if ((propagation.used_propagations & PropagationMode::TRANS_LANGEVIN_NPT) and + (propagation.default_propagation & PropagationMode::TRANS_LANGEVIN_NPT)) { + auto pred = PropagationPredicateNPT(propagation.default_propagation); + velocity_verlet_npt_step_1(particles.filter(pred), time_step); + } #endif - case INTEG_METHOD_BD: - // the Ermak-McCammon's Brownian Dynamics requires a single step - // so, just skip here - break; + #ifdef STOKESIAN_DYNAMICS - case INTEG_METHOD_SD: - stokesian_dynamics_step_1(particles, time_step); - break; -#endif // STOKESIAN_DYNAMICS - default: - throw std::runtime_error("Unknown value for integ_switch"); + if ((propagation.used_propagations & PropagationMode::TRANS_STOKESIAN) and + (propagation.default_propagation & PropagationMode::TRANS_STOKESIAN)) { + auto pred = PropagationPredicateStokesian(propagation.default_propagation); + stokesian_dynamics_step_1(particles.filter(pred), time_step); } - return early_exit; +#endif // STOKESIAN_DYNAMICS + + return false; } -/** Calls the hook of the propagation kernels after force calculation */ -static void integrator_step_2(ParticleRange const &particles, double kT, +static void integrator_step_2(ParticleRange const &particles, + Propagation const &propagation, double kT, double time_step) { - switch (integ_switch) { - case INTEG_METHOD_STEEPEST_DESCENT: - // Nothing - break; - case INTEG_METHOD_NVT: - velocity_verlet_step_2(particles, time_step); - break; -#ifdef NPT - case INTEG_METHOD_NPT_ISO: - velocity_verlet_npt_step_2(particles, time_step); - break; + if (propagation.integ_switch == INTEG_METHOD_STEEPEST_DESCENT) + return; + + for (auto &p : particles) { +#ifdef VIRTUAL_SITES + // virtual sites are updated later in the integration loop + if (p.is_virtual()) + continue; +#endif + if (propagation.should_propagate_with( + p, PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE)) + velocity_verlet_propagator_2(p, time_step); + if (propagation.should_propagate_with(p, PropagationMode::TRANS_NEWTON)) + velocity_verlet_propagator_2(p, time_step); +#ifdef ROTATION + if (propagation.should_propagate_with(p, PropagationMode::ROT_EULER)) + velocity_verlet_rotator_2(p, time_step); +#endif + if (propagation.should_propagate_with(p, PropagationMode::TRANS_LANGEVIN)) + velocity_verlet_propagator_2(p, time_step); +#ifdef ROTATION + if (propagation.should_propagate_with(p, PropagationMode::ROT_LANGEVIN)) + velocity_verlet_rotator_2(p, time_step); #endif - case INTEG_METHOD_BD: - // the Ermak-McCammon's Brownian Dynamics requires a single step - brownian_dynamics_propagator(brownian, particles, time_step, kT); - break; -#ifdef STOKESIAN_DYNAMICS - case INTEG_METHOD_SD: - // Nothing - break; -#endif // STOKESIAN_DYNAMICS - default: - throw std::runtime_error("Unknown value for INTEG_SWITCH"); } -} -namespace System { +#ifdef NPT + if ((propagation.used_propagations & PropagationMode::TRANS_LANGEVIN_NPT) and + (propagation.default_propagation & PropagationMode::TRANS_LANGEVIN_NPT)) { + auto pred = PropagationPredicateNPT(propagation.default_propagation); + velocity_verlet_npt_step_2(particles.filter(pred), time_step); + } +#endif +} -int System::integrate(int n_steps, int reuse_forces) { +int System::System::integrate(int n_steps, int reuse_forces) { #ifdef CALIPER CALI_CXX_MARK_FUNCTION; #endif + auto &propagation = *this->propagation; +#ifdef VIRTUAL_SITES_RELATIVE + auto const has_vs_rel = [&propagation]() { + return propagation.used_propagations & (PropagationMode::ROT_VS_RELATIVE | + PropagationMode::TRANS_VS_RELATIVE); + }; +#endif // Prepare particle structure and run sanity checks of all active algorithms + propagation.update_default_propagation(); + update_used_propagations(); on_integration_start(); // If any method vetoes (e.g. P3M not initialized), immediately bail out @@ -316,14 +428,17 @@ int System::integrate(int n_steps, int reuse_forces) { // Additional preparations for the first integration step if (reuse_forces == INTEG_REUSE_FORCES_NEVER or - (recalc_forces and reuse_forces != INTEG_REUSE_FORCES_ALWAYS)) { + ((reuse_forces != INTEG_REUSE_FORCES_ALWAYS) and + propagation.recalc_forces)) { #ifdef CALIPER CALI_MARK_BEGIN("Initial Force Calculation"); #endif lb_lbcoupling_deactivate(); -#ifdef VIRTUAL_SITES - virtual_sites()->update(); +#ifdef VIRTUAL_SITES_RELATIVE + if (has_vs_rel()) { + vs_relative_update_particles(*cell_structure, *box_geo); + } #endif // Communication step: distribute ghost positions @@ -331,7 +446,7 @@ int System::integrate(int n_steps, int reuse_forces) { calculate_forces(::temperature); - if (integ_switch != INTEG_METHOD_STEEPEST_DESCENT) { + if (propagation.integ_switch != INTEG_METHOD_STEEPEST_DESCENT) { #ifdef ROTATION convert_initial_torques(cell_structure->local_particles()); #endif @@ -358,7 +473,7 @@ int System::integrate(int n_steps, int reuse_forces) { auto lb_active = false; auto ek_active = false; - if (integ_switch != INTEG_METHOD_STEEPEST_DESCENT) { + if (propagation.integ_switch != INTEG_METHOD_STEEPEST_DESCENT) { lb_active = lb.is_solver_set(); ek_active = ek.is_ready_for_propagation(); } @@ -386,15 +501,22 @@ int System::integrate(int n_steps, int reuse_forces) { save_old_position(particles, cell_structure->ghost_particles()); #endif - LeesEdwards::update_box_params(*box_geo); - bool early_exit = integrator_step_1(particles, time_step); + lees_edwards->update_box_params(*box_geo, sim_time); + bool early_exit = + integrator_step_1(particles, propagation, ::temperature, time_step); if (early_exit) break; - LeesEdwards::run_kernel(*box_geo); + sim_time += time_step; + if (box_geo->type() == BoxType::LEES_EDWARDS) { + auto const kernel = LeesEdwards::Push{*box_geo}; + for (auto &p : particles) { + kernel(p); + } + } #ifdef NPT - if (integ_switch != INTEG_METHOD_NPT_ISO) + if (propagation.integ_switch != INTEG_METHOD_NPT_ISO) #endif { resort_particles_if_needed(*this); @@ -410,9 +532,17 @@ int System::integrate(int n_steps, int reuse_forces) { } #endif -#ifdef VIRTUAL_SITES - virtual_sites()->update(); -#endif +#ifdef VIRTUAL_SITES_RELATIVE + if (has_vs_rel()) { +#ifdef NPT + if (propagation.integ_switch == INTEG_METHOD_NPT_ISO) { + cell_structure->update_ghosts_and_resort_particle( + Cells::DATA_PART_PROPERTIES); + } +#endif // NPT + vs_relative_update_particles(*cell_structure, *box_geo); + } +#endif // VIRTUAL_SITES_RELATIVE if (cell_structure->get_resort_particles() >= Cells::RESORT_LOCAL) n_verlet_updates++; @@ -424,14 +554,21 @@ int System::integrate(int n_steps, int reuse_forces) { calculate_forces(::temperature); -#ifdef VIRTUAL_SITES - virtual_sites()->after_force_calc(time_step); +#ifdef VIRTUAL_SITES_INERTIALESS_TRACERS + if (propagation.used_propagations & PropagationMode::TRANS_LB_TRACER) { + lb_tracers_add_particle_force_to_fluid(*cell_structure, time_step); + } #endif - integrator_step_2(particles, temperature, time_step); - if (integ_switch == INTEG_METHOD_BD) { + integrator_step_2(particles, propagation, ::temperature, time_step); + if (propagation.integ_switch == INTEG_METHOD_BD) { resort_particles_if_needed(*this); } - LeesEdwards::run_kernel(*box_geo); + if (box_geo->type() == BoxType::LEES_EDWARDS) { + auto const kernel = LeesEdwards::UpdateOffset{*box_geo}; + for (auto &p : particles) { + kernel(p); + } + } #ifdef BOND_CONSTRAINT // SHAKE velocity updates if (n_rigidbonds) { @@ -440,7 +577,7 @@ int System::integrate(int n_steps, int reuse_forces) { #endif // propagate one-step functionalities - if (integ_switch != INTEG_METHOD_STEEPEST_DESCENT) { + if (propagation.integ_switch != INTEG_METHOD_STEEPEST_DESCENT) { if (lb_active and ek_active) { // assume that they are coupled, which is not necessarily true auto const md_steps_per_lb_step = calc_md_steps_per_tau(lb.get_tau()); @@ -451,36 +588,39 @@ int System::integrate(int n_steps, int reuse_forces) { << "LB and EK are active but with different time steps."; } - assert(lb_skipped_md_steps == ek_skipped_md_steps); + assert(propagation.lb_skipped_md_steps == + propagation.ek_skipped_md_steps); - lb_skipped_md_steps += 1; - ek_skipped_md_steps += 1; - if (lb_skipped_md_steps >= md_steps_per_lb_step) { - lb_skipped_md_steps = 0; - ek_skipped_md_steps = 0; + propagation.lb_skipped_md_steps += 1; + propagation.ek_skipped_md_steps += 1; + if (propagation.lb_skipped_md_steps >= md_steps_per_lb_step) { + propagation.lb_skipped_md_steps = 0; + propagation.ek_skipped_md_steps = 0; lb.propagate(); ek.propagate(); } lb_lbcoupling_propagate(); } else if (lb_active) { auto const md_steps_per_lb_step = calc_md_steps_per_tau(lb.get_tau()); - lb_skipped_md_steps += 1; - if (lb_skipped_md_steps >= md_steps_per_lb_step) { - lb_skipped_md_steps = 0; + propagation.lb_skipped_md_steps += 1; + if (propagation.lb_skipped_md_steps >= md_steps_per_lb_step) { + propagation.lb_skipped_md_steps = 0; lb.propagate(); } lb_lbcoupling_propagate(); } else if (ek_active) { auto const md_steps_per_ek_step = calc_md_steps_per_tau(ek.get_tau()); - ek_skipped_md_steps += 1; - if (ek_skipped_md_steps >= md_steps_per_ek_step) { - ek_skipped_md_steps = 0; + propagation.ek_skipped_md_steps += 1; + if (propagation.ek_skipped_md_steps >= md_steps_per_ek_step) { + propagation.ek_skipped_md_steps = 0; ek.propagate(); } } -#ifdef VIRTUAL_SITES - virtual_sites()->after_lb_propagation(time_step); +#ifdef VIRTUAL_SITES_INERTIALESS_TRACERS + if (propagation.used_propagations & PropagationMode::TRANS_LB_TRACER) { + lb_tracers_propagate(*cell_structure, time_step); + } #endif #ifdef COLLISION_DETECTION @@ -503,7 +643,7 @@ int System::integrate(int n_steps, int reuse_forces) { } } // for-loop over integration steps - LeesEdwards::update_box_params(*box_geo); + lees_edwards->update_box_params(*box_geo, sim_time); #ifdef CALIPER CALI_CXX_MARK_LOOP_END(integration_loop); #endif @@ -512,15 +652,17 @@ int System::integrate(int n_steps, int reuse_forces) { CALLGRIND_STOP_INSTRUMENTATION; #endif -#ifdef VIRTUAL_SITES - virtual_sites()->update(); +#ifdef VIRTUAL_SITES_RELATIVE + if (has_vs_rel()) { + vs_relative_update_particles(*cell_structure, *box_geo); + } #endif // Verlet list statistics cell_structure->update_verlet_stats(n_steps, n_verlet_updates); #ifdef NPT - if (integ_switch == INTEG_METHOD_NPT_ISO) { + if (propagation.integ_switch == INTEG_METHOD_NPT_ISO) { synchronize_npt_state(); } #endif @@ -534,8 +676,8 @@ int System::integrate(int n_steps, int reuse_forces) { return integrated_steps; } -int System::integrate_with_signal_handler(int n_steps, int reuse_forces, - bool update_accumulators) { +int System::System::integrate_with_signal_handler(int n_steps, int reuse_forces, + bool update_accumulators) { assert(n_steps >= 0); // Override the signal handler so that the integrator obeys Ctrl+C @@ -585,23 +727,8 @@ int System::integrate_with_signal_handler(int n_steps, int reuse_forces, return 0; } -} // namespace System - -double get_time_step() { return System::get_system().get_time_step(); } - -double get_sim_time() { return sim_time; } - -void increment_sim_time(double amount) { sim_time += amount; } - -void set_time(double value) { - ::sim_time = value; - ::recalc_forces = true; - auto &system = System::get_system(); - auto &box_geo = *system.box_geo; - LeesEdwards::update_box_params(box_geo); -} - -void set_integ_switch(int value) { - ::integ_switch = value; - ::recalc_forces = true; +void System::System::set_sim_time(double value) { + sim_time = value; + propagation->recalc_forces = true; + lees_edwards->update_box_params(*box_geo, sim_time); } diff --git a/src/core/integrate.hpp b/src/core/integrate.hpp index 52f59ae1e3f..5ef12825249 100644 --- a/src/core/integrate.hpp +++ b/src/core/integrate.hpp @@ -29,21 +29,14 @@ #include "config/config.hpp" +#include "PropagationMode.hpp" + #ifdef WALBERLA #include #include #endif -/** \name Integrator switches */ -/**@{*/ -#define INTEG_METHOD_NPT_ISO 0 -#define INTEG_METHOD_NVT 1 -#define INTEG_METHOD_STEEPEST_DESCENT 2 -#define INTEG_METHOD_BD 3 -#define INTEG_METHOD_SD 7 -/**@}*/ - /** \name Integrator error codes */ /**@{*/ #define INTEG_ERROR_RUNTIME -1 @@ -54,18 +47,12 @@ /**@{*/ /// recalculate forces unconditionally (mostly used for timing) #define INTEG_REUSE_FORCES_NEVER -1 -/// recalculate forces if @ref recalc_forces is set +/// recalculate forces only if @ref Propagation::recalc_forces is set #define INTEG_REUSE_FORCES_CONDITIONALLY 0 /// do not recalculate forces (mostly when reading checkpoints with forces) #define INTEG_REUSE_FORCES_ALWAYS 1 /**@}*/ -/** Switch determining which integrator to use. */ -extern int integ_switch; - -/** If true, the forces will be recalculated before the next integration. */ -extern bool recalc_forces; - #ifdef WALBERLA void walberla_tau_sanity_checks(std::string method, double tau, double time_step); @@ -76,17 +63,3 @@ void walberla_agrid_sanity_checks(std::string method, Utils::Vector3d const &lattice_right, double agrid); #endif // WALBERLA - -/** Get time step */ -double get_time_step(); - -/** Get simulation time */ -double get_sim_time(); - -/** Increase simulation time (only on head node) */ -void increment_sim_time(double amount); - -/** @brief Set the simulation time. */ -void set_time(double value); - -void set_integ_switch(int value); diff --git a/src/script_interface/virtual_sites/VirtualSitesOff.hpp b/src/core/integrators/Propagation.hpp similarity index 51% rename from src/script_interface/virtual_sites/VirtualSitesOff.hpp rename to src/core/integrators/Propagation.hpp index 0987558aa55..f810c5f1ba9 100644 --- a/src/script_interface/virtual_sites/VirtualSitesOff.hpp +++ b/src/core/integrators/Propagation.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2022 The ESPResSo project + * Copyright (C) 2010-2023 The ESPResSo project * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010 * Max-Planck-Institute for Polymer Research, Theory Group * @@ -19,35 +19,31 @@ * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_VIRTUAL_SITES_VIRTUAL_SITES_OFF_HPP -#define SCRIPT_INTERFACE_VIRTUAL_SITES_VIRTUAL_SITES_OFF_HPP +#pragma once -#include "config/config.hpp" +#include "PropagationMode.hpp" -#ifdef VIRTUAL_SITES - -#include "VirtualSites.hpp" - -#include "core/virtual_sites/VirtualSitesOff.hpp" - -#include - -namespace ScriptInterface { -namespace VirtualSites { - -class VirtualSitesOff : public VirtualSites { +class Propagation { public: - VirtualSitesOff() : m_virtual_sites(std::make_shared<::VirtualSitesOff>()) {} - /** Vs implementation we are wrapping */ - std::shared_ptr<::VirtualSites> virtual_sites() override { - return m_virtual_sites; + int integ_switch = INTEG_METHOD_NVT; + int used_propagations = PropagationMode::NONE; + int default_propagation = PropagationMode::NONE; + int lb_skipped_md_steps = 0; + int ek_skipped_md_steps = 0; + /** If true, forces will be recalculated before the next integration. */ + bool recalc_forces = true; + + void update_default_propagation(); + + template + bool should_propagate_with(Particle const &p, int mode) const { + return (p.propagation() & mode) or + ((default_propagation & mode) and + (p.propagation() & PropagationMode::SYSTEM_DEFAULT)); } -private: - std::shared_ptr<::VirtualSitesOff> m_virtual_sites; + void set_integ_switch(int value) { + integ_switch = value; + recalc_forces = true; + } }; - -} /* namespace VirtualSites */ -} /* namespace ScriptInterface */ -#endif // VIRTUAL_SITES -#endif diff --git a/src/core/integrators/brownian_inline.hpp b/src/core/integrators/brownian_inline.hpp index 3396d7cabe3..9b238d182c1 100644 --- a/src/core/integrators/brownian_inline.hpp +++ b/src/core/integrators/brownian_inline.hpp @@ -19,41 +19,33 @@ * along with this program. If not, see . */ -#ifndef INTEGRATORS_BROWNIAN_INLINE_HPP -#define INTEGRATORS_BROWNIAN_INLINE_HPP +#pragma once #include "config/config.hpp" -#include "ParticleRange.hpp" -#include "integrate.hpp" #include "rotation.hpp" #include "thermostat.hpp" #include "thermostats/brownian_inline.hpp" -#include - inline void brownian_dynamics_propagator(BrownianThermostat const &brownian, - const ParticleRange &particles, - double time_step, double kT) { - for (auto &p : particles) { - // Don't propagate translational degrees of freedom of vs - if (!p.is_virtual() or thermo_virtual) { - p.pos() += bd_drag(brownian.gamma, p, time_step); - p.v() = bd_drag_vel(brownian.gamma, p); - p.pos() += bd_random_walk(brownian, p, time_step, kT); - p.v() += bd_random_walk_vel(brownian, p); -#ifdef ROTATION - if (!p.can_rotate()) - continue; - convert_torque_to_body_frame_apply_fix(p); - p.quat() = bd_drag_rot(brownian.gamma_rotation, p, time_step); - p.omega() = bd_drag_vel_rot(brownian.gamma_rotation, p); - p.quat() = bd_random_walk_rot(brownian, p, time_step, kT); - p.omega() += bd_random_walk_vel_rot(brownian, p); -#endif // ROTATION - } - } - increment_sim_time(time_step); + Particle &p, double time_step, + double kT) { + p.pos() += bd_drag(brownian.gamma, p, time_step); + p.v() = bd_drag_vel(brownian.gamma, p); + p.pos() += bd_random_walk(brownian, p, time_step, kT); + p.v() += bd_random_walk_vel(brownian, p); } -#endif // INTEGRATORS_BROWNIAN_INLINE_HPP +#ifdef ROTATION +inline void brownian_dynamics_rotator(BrownianThermostat const &brownian, + Particle &p, double time_step, + double kT) { + if (!p.can_rotate()) + return; + convert_torque_to_body_frame_apply_fix(p); + p.quat() = bd_drag_rot(brownian.gamma_rotation, p, time_step); + p.omega() = bd_drag_vel_rot(brownian.gamma_rotation, p); + p.quat() = bd_random_walk_rot(brownian, p, time_step, kT); + p.omega() += bd_random_walk_vel_rot(brownian, p); +} +#endif // ROTATION diff --git a/src/core/integrators/steepest_descent.cpp b/src/core/integrators/steepest_descent.cpp index 2370da4b02e..0e7b9b3540b 100644 --- a/src/core/integrators/steepest_descent.cpp +++ b/src/core/integrators/steepest_descent.cpp @@ -55,19 +55,16 @@ bool steepest_descent_step(const ParticleRange &particles) { for (unsigned int j = 0; j < 3; j++) { // Skip, if coordinate is fixed if (!p.is_fixed_along(j)) { - // Skip positional increments of virtual particles - if (!p.is_virtual()) { - // Square of force on particle - f += Utils::sqr(p.force()[j]); - - // Positional increment, crop to maximum allowed by user - auto const dp = - std::clamp(params.gamma * p.force()[j], -params.max_displacement, - params.max_displacement); - - // Move particle - p.pos()[j] += dp; - } + // Square of force on particle + f += Utils::sqr(p.force()[j]); + + // Positional increment, crop to maximum allowed by user + auto const dp = + std::clamp(params.gamma * p.force()[j], -params.max_displacement, + params.max_displacement); + + // Move particle + p.pos()[j] += dp; } } #ifdef ROTATION diff --git a/src/core/integrators/stokesian_dynamics_inline.hpp b/src/core/integrators/stokesian_dynamics_inline.hpp index 1127ff3e685..4150e8a819c 100644 --- a/src/core/integrators/stokesian_dynamics_inline.hpp +++ b/src/core/integrators/stokesian_dynamics_inline.hpp @@ -16,30 +16,24 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef STOKESIAN_DYNAMICS_INLINE_HPP -#define STOKESIAN_DYNAMICS_INLINE_HPP + +#pragma once #include "config/config.hpp" #ifdef STOKESIAN_DYNAMICS -#include "ParticleRange.hpp" -#include "communication.hpp" -#include "integrate.hpp" + #include "rotation.hpp" #include "stokesian_dynamics/sd_interface.hpp" -inline void stokesian_dynamics_propagate_vel_pos(const ParticleRange &particles, - double time_step) { - - // Compute new (translational and rotational) velocities - propagate_vel_pos_sd(particles, comm_cart, time_step); +inline void stokesian_dynamics_step_1(ParticleRangeStokesian const &particles, + double time_step) { + propagate_vel_pos_sd(particles, time_step); for (auto &p : particles) { - - // Translate particle + // translate p.pos() += p.v() * time_step; - - // Perform rotation + // rotate auto const norm = p.omega().norm(); if (norm != 0.) { auto const omega_unit = (1. / norm) * p.omega(); @@ -48,11 +42,4 @@ inline void stokesian_dynamics_propagate_vel_pos(const ParticleRange &particles, } } -inline void stokesian_dynamics_step_1(const ParticleRange &particles, - double time_step) { - stokesian_dynamics_propagate_vel_pos(particles, time_step); - increment_sim_time(time_step); -} - #endif // STOKESIAN_DYNAMICS -#endif diff --git a/src/core/integrators/velocity_verlet_inline.hpp b/src/core/integrators/velocity_verlet_inline.hpp index 7ae712fc53b..909dd1e2d0c 100644 --- a/src/core/integrators/velocity_verlet_inline.hpp +++ b/src/core/integrators/velocity_verlet_inline.hpp @@ -16,15 +16,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef INTEGRATORS_VELOCITY_VERLET_HPP -#define INTEGRATORS_VELOCITY_VERLET_HPP + +#pragma once #include "config/config.hpp" #include "Particle.hpp" #include "ParticleRange.hpp" #include "cell_system/CellStructure.hpp" -#include "integrate.hpp" #include "rotation.hpp" /** Propagate the velocities and positions. Integration steps before force @@ -32,26 +31,15 @@ * v(t) + 0.5 \Delta t f(t)/m \f]
\f[ p(t+\Delta t) = p(t) + \Delta t * v(t+0.5 \Delta t) \f] */ -inline void velocity_verlet_propagate_vel_pos(const ParticleRange &particles, - double time_step) { - - for (auto &p : particles) { -#ifdef ROTATION - propagate_omega_quat_particle(p, time_step); -#endif - - // Don't propagate translational degrees of freedom of vs - if (p.is_virtual()) - continue; - for (unsigned int j = 0; j < 3; j++) { - if (!p.is_fixed_along(j)) { - /* Propagate velocities: v(t+0.5*dt) = v(t) + 0.5 * dt * a(t) */ - p.v()[j] += 0.5 * time_step * p.force()[j] / p.mass(); +inline void velocity_verlet_propagator_1(Particle &p, double time_step) { + for (unsigned int j = 0; j < 3; j++) { + if (!p.is_fixed_along(j)) { + /* Propagate velocities: v(t+0.5*dt) = v(t) + 0.5 * dt * a(t) */ + p.v()[j] += 0.5 * time_step * p.force()[j] / p.mass(); - /* Propagate positions (only NVT): p(t + dt) = p(t) + dt * - * v(t+0.5*dt) */ - p.pos()[j] += time_step * p.v()[j]; - } + /* Propagate positions (only NVT): p(t + dt) = p(t) + dt * + * v(t+0.5*dt) */ + p.pos()[j] += time_step * p.v()[j]; } } } @@ -59,35 +47,23 @@ inline void velocity_verlet_propagate_vel_pos(const ParticleRange &particles, /** Final integration step of the Velocity Verlet integrator * \f[ v(t+\Delta t) = v(t+0.5 \Delta t) + 0.5 \Delta t f(t+\Delta t)/m \f] */ -inline void velocity_verlet_propagate_vel_final(const ParticleRange &particles, - double time_step) { - - for (auto &p : particles) { - // Virtual sites are not propagated during integration - if (p.is_virtual()) - continue; - - for (unsigned int j = 0; j < 3; j++) { - if (!p.is_fixed_along(j)) { - /* Propagate velocity: v(t+dt) = v(t+0.5*dt) + 0.5*dt * a(t+dt) */ - p.v()[j] += 0.5 * time_step * p.force()[j] / p.mass(); - } +inline void velocity_verlet_propagator_2(Particle &p, double time_step) { + for (unsigned int j = 0; j < 3; j++) { + if (!p.is_fixed_along(j)) { + /* Propagate velocity: v(t+dt) = v(t+0.5*dt) + 0.5*dt * a(t+dt) */ + p.v()[j] += 0.5 * time_step * p.force()[j] / p.mass(); } } } -inline void velocity_verlet_step_1(const ParticleRange &particles, - double time_step) { - velocity_verlet_propagate_vel_pos(particles, time_step); - increment_sim_time(time_step); -} - -inline void velocity_verlet_step_2(const ParticleRange &particles, - double time_step) { - velocity_verlet_propagate_vel_final(particles, time_step); #ifdef ROTATION - convert_torques_propagate_omega(particles, time_step); -#endif +inline void velocity_verlet_rotator_1(Particle &p, double time_step) { + if (p.can_rotate()) + propagate_omega_quat_particle(p, time_step); } -#endif +inline void velocity_verlet_rotator_2(Particle &p, double time_step) { + if (p.can_rotate()) + convert_torque_propagate_omega(p, time_step); +} +#endif // ROTATION diff --git a/src/core/integrators/velocity_verlet_npt.cpp b/src/core/integrators/velocity_verlet_npt.cpp index f73592cb936..328bf31859d 100644 --- a/src/core/integrators/velocity_verlet_npt.cpp +++ b/src/core/integrators/velocity_verlet_npt.cpp @@ -28,7 +28,6 @@ #include "cell_system/CellStructure.hpp" #include "communication.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" #include "npt.hpp" #include "system/System.hpp" #include "thermostat.hpp" @@ -44,14 +43,12 @@ static constexpr Utils::Vector3i nptgeom_dir{{1, 2, 4}}; -void velocity_verlet_npt_propagate_vel_final(const ParticleRange &particles, - double time_step) { +static void +velocity_verlet_npt_propagate_vel_final(ParticleRangeNPT const &particles, + double time_step) { nptiso.p_vel = {}; for (auto &p : particles) { - // Virtual sites are not propagated during integration - if (p.is_virtual()) - continue; auto const noise = friction_therm0_nptiso<2>(npt_iso, p.v(), p.id()); for (unsigned int j = 0; j < 3; j++) { if (!p.is_fixed_along(j)) { @@ -68,7 +65,7 @@ void velocity_verlet_npt_propagate_vel_final(const ParticleRange &particles, } /** Scale and communicate instantaneous NpT pressure */ -void velocity_verlet_npt_finalize_p_inst(double time_step) { +static void velocity_verlet_npt_finalize_p_inst(double time_step) { /* finalize derivation of p_inst */ nptiso.p_inst = 0.0; for (unsigned int i = 0; i < 3; i++) { @@ -87,8 +84,8 @@ void velocity_verlet_npt_finalize_p_inst(double time_step) { } } -void velocity_verlet_npt_propagate_pos(const ParticleRange &particles, - double time_step) { +static void velocity_verlet_npt_propagate_pos(ParticleRangeNPT const &particles, + double time_step) { auto &system = System::get_system(); auto &box_geo = *system.box_geo; @@ -125,8 +122,6 @@ void velocity_verlet_npt_propagate_pos(const ParticleRange &particles, /* propagate positions while rescaling positions and velocities */ for (auto &p : particles) { - if (p.is_virtual()) - continue; for (unsigned int j = 0; j < 3; j++) { if (!p.is_fixed_along(j)) { if (nptiso.geometry & ::nptgeom_dir[j]) { @@ -163,21 +158,11 @@ void velocity_verlet_npt_propagate_pos(const ParticleRange &particles, system.on_boxl_change(true); } -void velocity_verlet_npt_propagate_vel(const ParticleRange &particles, - double time_step) { +static void velocity_verlet_npt_propagate_vel(ParticleRangeNPT const &particles, + double time_step) { nptiso.p_vel = {}; for (auto &p : particles) { -#ifdef ROTATION - if (p.can_rotate()) { - runtimeErrorMsg() << "The isotropic NpT integrator doesn't propagate " - "angular velocities"; - } -#endif - - // Don't propagate translational degrees of freedom of vs - if (p.is_virtual()) - continue; for (unsigned int j = 0; j < 3; j++) { if (!p.is_fixed_along(j)) { auto const noise = friction_therm0_nptiso<1>(npt_iso, p.v(), p.id()); @@ -193,16 +178,16 @@ void velocity_verlet_npt_propagate_vel(const ParticleRange &particles, } } -void velocity_verlet_npt_step_1(const ParticleRange &particles, +void velocity_verlet_npt_step_1(ParticleRangeNPT const &particles, double time_step) { velocity_verlet_npt_propagate_vel(particles, time_step); velocity_verlet_npt_propagate_pos(particles, time_step); - increment_sim_time(time_step); } -void velocity_verlet_npt_step_2(const ParticleRange &particles, +void velocity_verlet_npt_step_2(ParticleRangeNPT const &particles, double time_step) { velocity_verlet_npt_propagate_vel_final(particles, time_step); velocity_verlet_npt_finalize_p_inst(time_step); } + #endif // NPT diff --git a/src/core/integrators/velocity_verlet_npt.hpp b/src/core/integrators/velocity_verlet_npt.hpp index 9b707b8a421..127296dfee1 100644 --- a/src/core/integrators/velocity_verlet_npt.hpp +++ b/src/core/integrators/velocity_verlet_npt.hpp @@ -16,14 +16,30 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef INTEGRATORS_VELOCITY_VERLET_NPT_HPP -#define INTEGRATORS_VELOCITY_VERLET_NPT_HPP + +#pragma once #include "config/config.hpp" #ifdef NPT #include "ParticleRange.hpp" +#include "PropagationMode.hpp" +#include "PropagationPredicate.hpp" + +struct PropagationPredicateNPT { + int modes; + PropagationPredicateNPT(int default_propagation) { + modes = PropagationMode::TRANS_LANGEVIN_NPT; + if (default_propagation & PropagationMode::TRANS_LANGEVIN_NPT) { + modes |= PropagationMode::SYSTEM_DEFAULT; + } + } + + bool operator()(int prop) const { return (prop & modes); } +}; + +using ParticleRangeNPT = ParticleRangeFiltered; /** Special propagator for NpT isotropic. * Propagate the velocities and positions. Integration steps before force @@ -34,7 +50,7 @@ * Propagate pressure, box_length (2 times) and positions, rescale * positions and velocities and check Verlet list criterion (only NpT). */ -void velocity_verlet_npt_step_1(const ParticleRange &particles, +void velocity_verlet_npt_step_1(ParticleRangeNPT const &particles, double time_step); /** Final integration step of the Velocity Verlet+NpT integrator. @@ -42,8 +58,7 @@ void velocity_verlet_npt_step_1(const ParticleRange &particles, * \f[ v(t+\Delta t) = v(t+0.5 \Delta t) * + 0.5 \Delta t \cdot F(t+\Delta t)/m \f] */ -void velocity_verlet_npt_step_2(const ParticleRange &particles, +void velocity_verlet_npt_step_2(ParticleRangeNPT const &particles, double time_step); #endif // NPT -#endif diff --git a/src/core/lb/particle_coupling.cpp b/src/core/lb/particle_coupling.cpp index 416522eae59..10dcc8be663 100644 --- a/src/core/lb/particle_coupling.cpp +++ b/src/core/lb/particle_coupling.cpp @@ -241,9 +241,6 @@ Utils::Vector3d ParticleCoupling::get_noise_term(Particle const &p) const { } void ParticleCoupling::kernel(Particle &p) { - if (p.is_virtual() and not m_couple_virtual) - return; - auto const agrid = m_lb.get_agrid(); auto const &box_geo = *System::get_system().box_geo; @@ -303,7 +300,7 @@ static void lb_coupling_sanity_checks(Particle const &p) { } #endif -void couple_particles(bool couple_virtual, ParticleRange const &real_particles, +void couple_particles(ParticleRange const &real_particles, ParticleRange const &ghost_particles, double time_step) { #ifdef CALIPER CALI_CXX_MARK_FUNCTION; @@ -311,11 +308,11 @@ void couple_particles(bool couple_virtual, ParticleRange const &real_particles, if (lb_particle_coupling.couple_to_md) { auto &lb = System::get_system().lb; if (lb.is_solver_set()) { - ParticleCoupling coupling{lb, couple_virtual, time_step}; + ParticleCoupling coupling{lb, time_step}; CouplingBookkeeping bookkeeping{}; for (auto const &particle_range : {real_particles, ghost_particles}) { for (auto &p : particle_range) { - if (bookkeeping.should_be_coupled(p)) { + if (not LB::is_tracer(p) and bookkeeping.should_be_coupled(p)) { #if defined(THERMOSTAT_PER_PARTICLE) and defined(PARTICLE_ANISOTROPY) lb_coupling_sanity_checks(p); #endif diff --git a/src/core/lb/particle_coupling.hpp b/src/core/lb/particle_coupling.hpp index fce6d7ceb52..2213c8ee3f1 100644 --- a/src/core/lb/particle_coupling.hpp +++ b/src/core/lb/particle_coupling.hpp @@ -22,6 +22,7 @@ #include "BoxGeometry.hpp" #include "Particle.hpp" #include "ParticleRange.hpp" +#include "PropagationMode.hpp" #include "lb/Solver.hpp" #include @@ -129,21 +130,18 @@ extern LB::ParticleCouplingConfig lb_particle_coupling; namespace LB { /** @brief Calculate particle-lattice interactions. */ -void couple_particles(bool couple_virtual, ParticleRange const &real_particles, +void couple_particles(ParticleRange const &real_particles, ParticleRange const &ghost_particles, double time_step); class ParticleCoupling { LB::Solver &m_lb; - bool m_couple_virtual; bool m_thermalized; double m_time_step; double m_noise_pref_wo_gamma; public: - ParticleCoupling(LB::Solver &lb, bool couple_virtual, double time_step, - double kT) - : m_lb{lb}, m_couple_virtual{couple_virtual}, m_thermalized{kT != 0.}, - m_time_step{time_step} { + ParticleCoupling(LB::Solver &lb, double time_step, double kT) + : m_lb{lb}, m_thermalized{kT != 0.}, m_time_step{time_step} { assert(kT >= 0.); /* Eq. (16) @cite ahlrichs99a, without the gamma term. * The factor 12 comes from the fact that we use random numbers @@ -154,8 +152,8 @@ class ParticleCoupling { m_noise_pref_wo_gamma = std::sqrt(variance_inv * 2. * kT / time_step); } - ParticleCoupling(LB::Solver &lb, bool couple_virtual, double time_step) - : ParticleCoupling(lb, couple_virtual, time_step, + ParticleCoupling(LB::Solver &lb, double time_step) + : ParticleCoupling(lb, time_step, lb.get_kT() * Utils::sqr(lb.get_lattice_speed())) {} Utils::Vector3d get_noise_term(Particle const &p) const; @@ -188,6 +186,19 @@ class CouplingBookkeeping { public: /** @brief Determine if a given particle should be coupled. */ bool should_be_coupled(Particle const &p) { + auto const propagation = p.propagation(); + if ((propagation & PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE) == 0 and + (propagation & PropagationMode::SYSTEM_DEFAULT) == 0 and + (propagation & PropagationMode::TRANS_LB_TRACER) == 0) { + return false; + } +#ifdef VIRTUAL_SITES_RELATIVE + if ((propagation & PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE) == 0 and + propagation & (PropagationMode::TRANS_VS_RELATIVE | + PropagationMode::ROT_VS_RELATIVE)) { + return false; + } +#endif // real particles: always couple if (not p.is_ghost()) { return true; @@ -202,4 +213,8 @@ class CouplingBookkeeping { } }; +inline bool is_tracer(Particle const &p) { + return (p.propagation() & PropagationMode::TRANS_LB_TRACER) != 0; +} + } // namespace LB diff --git a/src/core/lees_edwards/lees_edwards.hpp b/src/core/lees_edwards/lees_edwards.hpp index 631c945a3f7..49f592d9984 100644 --- a/src/core/lees_edwards/lees_edwards.hpp +++ b/src/core/lees_edwards/lees_edwards.hpp @@ -16,17 +16,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_LEES_EDWARDS_LEES_EDWARDS_HPP -#define CORE_LEES_EDWARDS_LEES_EDWARDS_HPP + +#pragma once #include "BoxGeometry.hpp" #include "Particle.hpp" #include "lees_edwards/protocols.hpp" +#include "system/Leaf.hpp" #include #include -#include namespace LeesEdwards { class UpdateOffset { protected: @@ -90,14 +90,20 @@ inline Utils::Vector3d verlet_list_offset(BoxGeometry const &box, return {}; } -/** @brief Get currently active Lees-Edwards protocol. */ -std::weak_ptr get_protocol(); +class LeesEdwards : public System::Leaf { + std::shared_ptr m_protocol; + +public: + /** @brief Get currently active Lees-Edwards protocol. */ + auto get_protocol() const { return m_protocol; } -/** @brief Set a new Lees-Edwards protocol. */ -void set_protocol(std::shared_ptr new_protocol); + /** @brief Set a new Lees-Edwards protocol. */ + void set_protocol(std::shared_ptr protocol); -/** @brief Delete the currently active Lees-Edwards protocol. */ -void unset_protocol(); + /** @brief Delete the currently active Lees-Edwards protocol. */ + void unset_protocol(); + + void update_box_params(BoxGeometry &box_geo, double sim_time); +}; } // namespace LeesEdwards -#endif diff --git a/src/core/lees_edwards/protocols.hpp b/src/core/lees_edwards/protocols.hpp index 29737168b40..8c4fc0f936c 100644 --- a/src/core/lees_edwards/protocols.hpp +++ b/src/core/lees_edwards/protocols.hpp @@ -16,14 +16,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_LEES_EDWARDS_PROTOCOLS_HPP -#define CORE_LEES_EDWARDS_PROTOCOLS_HPP -#include +#pragma once -#include +#include #include +#include namespace LeesEdwards { @@ -73,12 +72,12 @@ struct OscillatoryShear { }; /** Type which holds the currently active protocol */ -using ActiveProtocol = boost::variant; +using ActiveProtocol = std::variant; -class PosOffsetGetter : public boost::static_visitor { +class PosOffsetGetter { public: PosOffsetGetter(double time) : m_time{time} {} - template double operator()(const T &protocol) const { + template double operator()(T const &protocol) const { return protocol.pos_offset(m_time); } @@ -87,14 +86,14 @@ class PosOffsetGetter : public boost::static_visitor { }; inline double get_pos_offset(double time, ActiveProtocol const &protocol) { - return boost::apply_visitor(PosOffsetGetter(time), protocol); + return std::visit(PosOffsetGetter(time), protocol); } /** Visitor to get shear velocity from the Lees-Edwards protocol */ -class ShearVelocityGetter : public boost::static_visitor { +class ShearVelocityGetter { public: ShearVelocityGetter(double time) : m_time{time} {} - template double operator()(const T &protocol) const { + template double operator()(T const &protocol) const { return protocol.shear_velocity(m_time); } @@ -104,9 +103,7 @@ class ShearVelocityGetter : public boost::static_visitor { /** Calculation of current velocity */ inline double get_shear_velocity(double time, ActiveProtocol const &protocol) { - return boost::apply_visitor(ShearVelocityGetter(time), protocol); + return std::visit(ShearVelocityGetter(time), protocol); } } // namespace LeesEdwards - -#endif diff --git a/src/core/magnetostatics/dipoles.cpp b/src/core/magnetostatics/dipoles.cpp index 18f6eedcdeb..4f60e1fa8bf 100644 --- a/src/core/magnetostatics/dipoles.cpp +++ b/src/core/magnetostatics/dipoles.cpp @@ -31,8 +31,6 @@ #include "actor/visitors.hpp" #include "communication.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" -#include "npt.hpp" #include "system/System.hpp" #include @@ -40,7 +38,6 @@ #include #include -#include #include #include @@ -123,15 +120,7 @@ struct LongRangeForce { #ifdef DP3M void operator()(std::shared_ptr const &actor) const { - actor->dipole_assign(m_particles); -#ifdef NPT - if (integ_switch == INTEG_METHOD_NPT_ISO) { - auto const energy = actor->kernel(true, true, m_particles); - npt_add_virial_contribution(energy); - fprintf(stderr, "dipolar_P3M at this moment is added to p_vir[0]\n"); - } else -#endif // NPT - actor->kernel(true, false, m_particles); + actor->add_long_range_forces(m_particles); } #endif // DP3M void operator()(std::shared_ptr const &actor) const { @@ -165,8 +154,7 @@ struct LongRangeEnergy { #ifdef DP3M double operator()(std::shared_ptr const &actor) const { - actor->dipole_assign(m_particles); - return actor->kernel(false, true, m_particles); + return actor->long_range_energy(m_particles); } #endif // DP3M double diff --git a/src/core/magnetostatics/dp3m.cpp b/src/core/magnetostatics/dp3m.cpp index 7fb65ec0be7..d262e857b0f 100644 --- a/src/core/magnetostatics/dp3m.cpp +++ b/src/core/magnetostatics/dp3m.cpp @@ -43,11 +43,12 @@ #include "LocalBox.hpp" #include "Particle.hpp" #include "ParticleRange.hpp" +#include "PropagationMode.hpp" #include "cell_system/CellStructure.hpp" #include "cell_system/CellStructureType.hpp" #include "communication.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" +#include "integrators/Propagation.hpp" #include "npt.hpp" #include "system/System.hpp" #include "tuning.hpp" @@ -64,6 +65,7 @@ #include #include +#include #include #include #include @@ -251,17 +253,22 @@ template struct AssignForces { }; } // namespace -double DipolarP3M::kernel(bool force_flag, bool energy_flag, - ParticleRange const &particles) { - int i, ind, j[3]; +double DipolarP3M::long_range_kernel(bool force_flag, bool energy_flag, + ParticleRange const &particles) { /* k-space energy */ - double k_space_energy_dip = 0.; - double tmp0, tmp1; - - auto const &box_geo = *get_system().box_geo; + double energy = 0.; + auto const &system = get_system(); + auto const &box_geo = *system.box_geo; auto const dipole_prefac = prefactor / Utils::int_pow<3>(dp3m.params.mesh[0]); - - if (dp3m.sum_mu2 > 0) { +#ifdef NPT + auto const npt_flag = + force_flag and (system.propagation->integ_switch == INTEG_METHOD_NPT_ISO); +#else + auto constexpr npt_flag = false; +#endif + + if (dp3m.sum_mu2 > 0.) { + dipole_assign(particles); /* Gather information for FFT grid inside the nodes domain (inner local * mesh) and perform forward 3D FFT (Charge Assignment Mesh). */ std::array meshes = {{dp3m.rs_mesh_dip[0].data(), @@ -279,20 +286,21 @@ double DipolarP3M::kernel(bool force_flag, bool energy_flag, } /* === k-space energy calculation === */ - if (energy_flag) { + if (energy_flag or npt_flag) { /********************* Dipolar energy **********************/ - if (dp3m.sum_mu2 > 0) { + if (dp3m.sum_mu2 > 0.) { /* i*k differentiation for dipolar gradients: * |(\Fourier{\vect{mu}}(k)\cdot \vect{k})|^2 */ - ind = 0; - i = 0; - double node_k_space_energy_dip = 0.0; + int ind = 0; + int i = 0; + int j[3]; + double node_energy = 0.0; for (j[0] = 0; j[0] < dp3m.fft.plan[3].new_mesh[0]; j[0]++) { for (j[1] = 0; j[1] < dp3m.fft.plan[3].new_mesh[1]; j[1]++) { for (j[2] = 0; j[2] < dp3m.fft.plan[3].new_mesh[2]; j[2]++) { - node_k_space_energy_dip += + node_energy += dp3m.g_energy[i] * (Utils::sqr( dp3m.rs_mesh_dip[0][ind] * @@ -313,23 +321,19 @@ double DipolarP3M::kernel(bool force_flag, bool energy_flag, } } } - node_k_space_energy_dip *= - dipole_prefac * Utils::pi() * box_geo.length_inv()[0]; - boost::mpi::reduce(comm_cart, node_k_space_energy_dip, k_space_energy_dip, - std::plus<>(), 0); + node_energy *= dipole_prefac * Utils::pi() * box_geo.length_inv()[0]; + boost::mpi::reduce(comm_cart, node_energy, energy, std::plus<>(), 0); if (dp3m.energy_correction == 0.) calc_energy_correction(); if (this_node == 0) { /* self energy correction */ - k_space_energy_dip -= prefactor * dp3m.sum_mu2 * 2. * - Utils::int_pow<3>(dp3m.params.alpha) * - Utils::sqrt_pi_i() / 3.; + energy -= prefactor * dp3m.sum_mu2 * Utils::sqrt_pi_i() * (2. / 3.) * + Utils::int_pow<3>(dp3m.params.alpha); /* dipolar energy correction due to systematic Madelung-self effects */ - auto const volume = box_geo.volume(); - k_space_energy_dip += prefactor * dp3m.energy_correction / volume; + energy += prefactor * dp3m.energy_correction / box_geo.volume(); } } } // if (energy_flag) @@ -339,11 +343,13 @@ double DipolarP3M::kernel(bool force_flag, bool energy_flag, /**************************** * DIPOLAR TORQUES (k-space) ****************************/ - if (dp3m.sum_mu2 > 0) { + if (dp3m.sum_mu2 > 0.) { auto const two_pi_L_i = 2. * Utils::pi() * box_geo.length_inv()[0]; /* fill in ks_mesh array for torque calculation */ - ind = 0; - i = 0; + int ind = 0; + int i = 0; + int j[3]; + double tmp0, tmp1; for (j[0] = 0; j[0] < dp3m.fft.plan[3].new_mesh[0]; j[0]++) { // j[0]=n_y for (j[1] = 0; j[1] < dp3m.fft.plan[3].new_mesh[1]; @@ -497,12 +503,20 @@ double DipolarP3M::kernel(bool force_flag, bool energy_flag, if (dp3m.params.epsilon != P3M_EPSILON_METALLIC) { auto const surface_term = - calc_surface_term(force_flag, energy_flag, particles); - if (this_node == 0) - k_space_energy_dip += surface_term; + calc_surface_term(force_flag, energy_flag or npt_flag, particles); + if (this_node == 0) { + energy += surface_term; + } + } + if (npt_flag) { + npt_add_virial_contribution(energy); + fprintf(stderr, "dipolar_P3M at this moment is added to p_vir[0]\n"); + } + if (not energy_flag) { + energy = 0.; } - return k_space_energy_dip; + return energy; } double DipolarP3M::calc_surface_term(bool force_flag, bool energy_flag, diff --git a/src/core/magnetostatics/dp3m.hpp b/src/core/magnetostatics/dp3m.hpp index fedfb2c22ab..85bfcdeb638 100644 --- a/src/core/magnetostatics/dp3m.hpp +++ b/src/core/magnetostatics/dp3m.hpp @@ -169,9 +169,19 @@ struct DipolarP3M : public Dipoles::Actor { void tune(); bool is_tuned() const { return m_is_tuned; } + /** Compute the k-space part of energies. */ + double long_range_energy(ParticleRange const &particles) { + return long_range_kernel(false, true, particles); + } + + /** Compute the k-space part of forces. */ + void add_long_range_forces(ParticleRange const &particles) { + long_range_kernel(true, false, particles); + } + /** Compute the k-space part of forces and energies. */ - double kernel(bool force_flag, bool energy_flag, - ParticleRange const &particles); + double long_range_kernel(bool force_flag, bool energy_flag, + ParticleRange const &particles); /** Calculate real-space contribution of p3m dipolar pair forces and torques. * If NPT is compiled in, update the NpT virial. diff --git a/src/core/npt.cpp b/src/core/npt.cpp index 0ea9016b6aa..d75e4bbe068 100644 --- a/src/core/npt.cpp +++ b/src/core/npt.cpp @@ -20,11 +20,12 @@ #ifdef NPT +#include "PropagationMode.hpp" #include "communication.hpp" #include "config/config.hpp" #include "electrostatics/coulomb.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" +#include "integrators/Propagation.hpp" #include "magnetostatics/dipoles.hpp" #include "system/System.hpp" @@ -108,21 +109,19 @@ Utils::Vector NptIsoParameters::get_direction() const { static_cast(geometry & ::nptgeom_dir[2])}; } -void npt_ensemble_init(const BoxGeometry &box) { - if (integ_switch == INTEG_METHOD_NPT_ISO) { - /* prepare NpT-integration */ - nptiso.inv_piston = 1. / nptiso.piston; - nptiso.volume = pow(box.length()[nptiso.non_const_dim], nptiso.dimension); - if (recalc_forces) { - nptiso.p_inst = 0.0; - nptiso.p_vir = Utils::Vector3d{}; - nptiso.p_vel = Utils::Vector3d{}; - } +void npt_ensemble_init(Utils::Vector3d const &box_l, bool recalc_forces) { + nptiso.inv_piston = 1. / nptiso.piston; + nptiso.volume = std::pow(box_l[nptiso.non_const_dim], nptiso.dimension); + if (recalc_forces) { + nptiso.p_inst = 0.0; + nptiso.p_vir = Utils::Vector3d{}; + nptiso.p_vel = Utils::Vector3d{}; } } void integrator_npt_sanity_checks() { - if (integ_switch == INTEG_METHOD_NPT_ISO) { + if (::System::get_system().propagation->used_propagations & + PropagationMode::TRANS_LANGEVIN_NPT) { try { nptiso.coulomb_dipole_sanity_checks(); } catch (std::runtime_error const &err) { @@ -133,19 +132,23 @@ void integrator_npt_sanity_checks() { /** reset virial part of instantaneous pressure */ void npt_reset_instantaneous_virials() { - if (integ_switch == INTEG_METHOD_NPT_ISO) + if (::System::get_system().propagation->used_propagations & + PropagationMode::TRANS_LANGEVIN_NPT) { nptiso.p_vir = Utils::Vector3d{}; + } } void npt_add_virial_contribution(double energy) { - if (integ_switch == INTEG_METHOD_NPT_ISO) { + if (::System::get_system().propagation->used_propagations & + PropagationMode::TRANS_LANGEVIN_NPT) { nptiso.p_vir[0] += energy; } } void npt_add_virial_contribution(const Utils::Vector3d &force, const Utils::Vector3d &d) { - if (integ_switch == INTEG_METHOD_NPT_ISO) { + if (::System::get_system().propagation->used_propagations & + PropagationMode::TRANS_LANGEVIN_NPT) { nptiso.p_vir += hadamard_product(force, d); } } diff --git a/src/core/npt.hpp b/src/core/npt.hpp index 658526c033a..defec034b7b 100644 --- a/src/core/npt.hpp +++ b/src/core/npt.hpp @@ -22,13 +22,11 @@ * Exports for the NpT code. */ -#ifndef NPT_H -#define NPT_H +#pragma once #include "config/config.hpp" #ifdef NPT -#include "BoxGeometry.hpp" #include @@ -84,7 +82,7 @@ extern NptIsoParameters nptiso; /** @brief Synchronizes NpT state such as instantaneous and average pressure */ void synchronize_npt_state(); -void npt_ensemble_init(const BoxGeometry &box); +void npt_ensemble_init(Utils::Vector3d const &box_l, bool recalc_forces); void integrator_npt_sanity_checks(); void npt_reset_instantaneous_virials(); void npt_add_virial_contribution(double energy); @@ -92,4 +90,3 @@ void npt_add_virial_contribution(const Utils::Vector3d &force, const Utils::Vector3d &d); #endif // NPT -#endif diff --git a/src/core/particle_node.cpp b/src/core/particle_node.cpp index 385644ed9a8..3238d86631a 100644 --- a/src/core/particle_node.cpp +++ b/src/core/particle_node.cpp @@ -89,8 +89,6 @@ static void mpi_synchronize_max_seen_pid_local() { boost::mpi::broadcast(::comm_cart, ::max_seen_pid, 0); } -REGISTER_CALLBACK(mpi_synchronize_max_seen_pid_local) - void init_type_map(int type) { if (type < 0) { throw std::runtime_error("Types may not be negative"); diff --git a/src/core/pressure.cpp b/src/core/pressure.cpp index 3c385a2b501..0a06a6a2065 100644 --- a/src/core/pressure.cpp +++ b/src/core/pressure.cpp @@ -32,10 +32,11 @@ #include "pressure_inline.hpp" #include "short_range_loop.hpp" #include "system/System.hpp" -#include "virtual_sites.hpp" +#include "virtual_sites/relative.hpp" #include #include +#include #include @@ -107,10 +108,11 @@ std::shared_ptr System::calculate_pressure() { Dipoles::get_dipoles().calc_pressure_long_range(); #endif -#ifdef VIRTUAL_SITES +#ifdef VIRTUAL_SITES_RELATIVE if (!obs_pressure.virtual_sites.empty()) { - auto const vs_pressure = virtual_sites()->pressure_tensor(); - boost::copy(flatten(vs_pressure), obs_pressure.virtual_sites.begin()); + auto const vs_pressure = vs_relative_pressure_tensor(*cell_structure); + boost::copy(Utils::flatten(vs_pressure), + obs_pressure.virtual_sites.begin()); } #endif diff --git a/src/core/propagation.cpp b/src/core/propagation.cpp new file mode 100644 index 00000000000..e9a423e55ec --- /dev/null +++ b/src/core/propagation.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 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 . + */ + +#include "propagation.hpp" + +#include "config/config.hpp" + +#include "PropagationMode.hpp" + +#include +#include +#include + +// no-op function to generate a trace for code coverage +static bool force_code_coverage(bool value) { return value; } + +/** + * Note for developers: when enabling new propagation mode combinations, + * make sure every single line of this function has code coverage. + */ +bool is_valid_propagation_combination(int propagation) { + using namespace PropagationMode; + assert(propagation >= 0); + // check allowlist + switch (propagation) { + // only one mode + case NONE: // NOLINT(bugprone-branch-clone) + return force_code_coverage(true); + case SYSTEM_DEFAULT: + return force_code_coverage(true); + case TRANS_NEWTON: + return force_code_coverage(true); + case TRANS_BROWNIAN: + return force_code_coverage(true); + case TRANS_LANGEVIN: + return force_code_coverage(true); +#ifdef NPT + case TRANS_LANGEVIN_NPT: + return force_code_coverage(true); +#endif +#ifdef ROTATION + case ROT_EULER: + return force_code_coverage(true); + case ROT_BROWNIAN: + return force_code_coverage(true); + case ROT_LANGEVIN: + return force_code_coverage(true); +#endif // ROTATION + case TRANS_LB_MOMENTUM_EXCHANGE: + return force_code_coverage(true); +#ifdef VIRTUAL_SITES_INERTIALESS_TRACERS + case TRANS_LB_TRACER: + return force_code_coverage(true); +#endif +#ifdef ROTATION + // same mode for translation and rotation + case TRANS_NEWTON | ROT_EULER: + return force_code_coverage(true); + case TRANS_LANGEVIN | ROT_LANGEVIN: + return force_code_coverage(true); + case TRANS_BROWNIAN | ROT_BROWNIAN: + return force_code_coverage(true); +#ifdef STOKESIAN_DYNAMICS + case TRANS_STOKESIAN | ROT_STOKESIAN: + return force_code_coverage(true); +#endif + // other mode combinations + case TRANS_LB_MOMENTUM_EXCHANGE | ROT_EULER: + return force_code_coverage(true); + case TRANS_LB_MOMENTUM_EXCHANGE | ROT_LANGEVIN: + return force_code_coverage(true); +#ifdef VIRTUAL_SITES_RELATIVE + case TRANS_VS_RELATIVE | ROT_VS_RELATIVE: + return force_code_coverage(true); + case TRANS_VS_RELATIVE | ROT_VS_RELATIVE | TRANS_LB_MOMENTUM_EXCHANGE: + return force_code_coverage(true); + case TRANS_VS_RELATIVE | ROT_VS_RELATIVE | TRANS_LANGEVIN | ROT_LANGEVIN: + return force_code_coverage(true); + case TRANS_VS_RELATIVE | ROT_VS_RELATIVE | TRANS_LB_MOMENTUM_EXCHANGE | + ROT_LANGEVIN: + return force_code_coverage(true); +#endif // VIRTUAL_SITES_RELATIVE +#endif // ROTATION + } + return force_code_coverage(false); +} + +std::unordered_map propagation_flags_map() { + using namespace PropagationMode; + std::unordered_map enum_values{}; + enum_values["NONE"] = NONE; + enum_values["SYSTEM_DEFAULT"] = SYSTEM_DEFAULT; + enum_values["TRANS_NEWTON"] = TRANS_NEWTON; + enum_values["TRANS_LANGEVIN"] = TRANS_LANGEVIN; + enum_values["TRANS_LANGEVIN_NPT"] = TRANS_LANGEVIN_NPT; + enum_values["TRANS_VS_RELATIVE"] = TRANS_VS_RELATIVE; + enum_values["TRANS_LB_MOMENTUM_EXCHANGE"] = TRANS_LB_MOMENTUM_EXCHANGE; + enum_values["TRANS_LB_TRACER"] = TRANS_LB_TRACER; + enum_values["TRANS_BROWNIAN"] = TRANS_BROWNIAN; + enum_values["TRANS_STOKESIAN"] = TRANS_STOKESIAN; + enum_values["ROT_EULER"] = ROT_EULER; + enum_values["ROT_LANGEVIN"] = ROT_LANGEVIN; + enum_values["ROT_VS_RELATIVE"] = ROT_VS_RELATIVE; + enum_values["ROT_BROWNIAN"] = ROT_BROWNIAN; + enum_values["ROT_STOKESIAN"] = ROT_STOKESIAN; + return enum_values; +} + +std::string propagation_bitmask_to_string(int propagation) { + std::string serialized{""}; + for (auto const &[name, flag] : propagation_flags_map()) { + if (propagation & flag) { + serialized += "|" + name; + } + } + if (serialized.empty()) { + serialized = "NONE"; + } else { + serialized = serialized.substr(1); + } + return serialized; +} diff --git a/src/script_interface/virtual_sites/initialize.hpp b/src/core/propagation.hpp similarity index 56% rename from src/script_interface/virtual_sites/initialize.hpp rename to src/core/propagation.hpp index 3f126eca186..14f62965b6c 100644 --- a/src/script_interface/virtual_sites/initialize.hpp +++ b/src/core/propagation.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2022 The ESPResSo project + * Copyright (C) 2023 The ESPResSo project * * This file is part of ESPResSo. * @@ -17,19 +17,24 @@ * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_VIRTUAL_SITES_INITIALIZE_HPP -#define SCRIPT_INTERFACE_VIRTUAL_SITES_INITIALIZE_HPP +#pragma once -#include +#include "PropagationMode.hpp" -#include +#include +#include -namespace ScriptInterface { -namespace VirtualSites { - -void initialize(Utils::Factory *om); +/** + * @brief Check allowlist of valid propagation modes combinations. + */ +bool is_valid_propagation_combination(int propagation); -} /* namespace VirtualSites */ -} /* namespace ScriptInterface */ +/** + * @brief Convert @ref PropagationMode::PropagationMode to name/value pairs. + */ +std::unordered_map propagation_flags_map(); -#endif +/** + * @brief Convert a propagation modes bitmask to a string. + */ +std::string propagation_bitmask_to_string(int propagation); diff --git a/src/core/rotation.cpp b/src/core/rotation.cpp index c77622499a7..d248e483389 100644 --- a/src/core/rotation.cpp +++ b/src/core/rotation.cpp @@ -123,10 +123,7 @@ static void define_Qdd(Particle const &p, Utils::Quaternion &Qd, * \todo implement for fixed_coord_flag */ void propagate_omega_quat_particle(Particle &p, double time_step) { - - // If rotation for the particle is disabled entirely, return early. - if (!p.can_rotate()) - return; + assert(p.can_rotate()); Utils::Quaternion Qd{}, Qdd{}; Utils::Vector3d S{}, Wd{}; @@ -159,38 +156,32 @@ void propagate_omega_quat_particle(Particle &p, double time_step) { } } -void convert_torques_propagate_omega(const ParticleRange &particles, - double time_step) { - for (auto &p : particles) { - // Skip particle if rotation is turned off entirely for it. - if (!p.can_rotate()) - continue; - - convert_torque_to_body_frame_apply_fix(p); +void convert_torque_propagate_omega(Particle &p, double time_step) { + assert(p.can_rotate()); + convert_torque_to_body_frame_apply_fix(p); - // Propagation of angular velocities - p.omega() += hadamard_division(0.5 * time_step * p.torque(), p.rinertia()); + // Propagation of angular velocities + p.omega() += hadamard_division(0.5 * time_step * p.torque(), p.rinertia()); - // zeroth estimate of omega - Utils::Vector3d omega_0 = p.omega(); + // zeroth estimate of omega + Utils::Vector3d omega_0 = p.omega(); - /* if the tensor of inertia is isotropic, the following refinement is not - needed. - Otherwise repeat this loop 2-3 times depending on the required accuracy - */ + /* if the tensor of inertia is isotropic, the following refinement is not + needed. + Otherwise repeat this loop 2-3 times depending on the required accuracy + */ - const double rinertia_diff_01 = p.rinertia()[0] - p.rinertia()[1]; - const double rinertia_diff_12 = p.rinertia()[1] - p.rinertia()[2]; - const double rinertia_diff_20 = p.rinertia()[2] - p.rinertia()[0]; - for (int times = 0; times <= 5; times++) { - Utils::Vector3d Wd; + const double rinertia_diff_01 = p.rinertia()[0] - p.rinertia()[1]; + const double rinertia_diff_12 = p.rinertia()[1] - p.rinertia()[2]; + const double rinertia_diff_20 = p.rinertia()[2] - p.rinertia()[0]; + for (int times = 0; times <= 5; times++) { + Utils::Vector3d Wd; - Wd[0] = p.omega()[1] * p.omega()[2] * rinertia_diff_12 / p.rinertia()[0]; - Wd[1] = p.omega()[2] * p.omega()[0] * rinertia_diff_20 / p.rinertia()[1]; - Wd[2] = p.omega()[0] * p.omega()[1] * rinertia_diff_01 / p.rinertia()[2]; + Wd[0] = p.omega()[1] * p.omega()[2] * rinertia_diff_12 / p.rinertia()[0]; + Wd[1] = p.omega()[2] * p.omega()[0] * rinertia_diff_20 / p.rinertia()[1]; + Wd[2] = p.omega()[0] * p.omega()[1] * rinertia_diff_01 / p.rinertia()[2]; - p.omega() = omega_0 + (0.5 * time_step) * Wd; - } + p.omega() = omega_0 + (0.5 * time_step) * Wd; } } diff --git a/src/core/rotation.hpp b/src/core/rotation.hpp index 48e13aca1b5..be3b2fe8cdc 100644 --- a/src/core/rotation.hpp +++ b/src/core/rotation.hpp @@ -47,11 +47,7 @@ */ void propagate_omega_quat_particle(Particle &p, double time_step); -/** @brief Convert torques to the body-fixed frame and propagate - * angular velocities. - */ -void convert_torques_propagate_omega(const ParticleRange &particles, - double time_step); +void convert_torque_propagate_omega(Particle &p, double time_step); /** Convert torques to the body-fixed frame before the integration loop. */ void convert_initial_torques(const ParticleRange &particles); diff --git a/src/core/stokesian_dynamics/sd_interface.cpp b/src/core/stokesian_dynamics/sd_interface.cpp index 656e5833cd8..e9802142dd9 100644 --- a/src/core/stokesian_dynamics/sd_interface.cpp +++ b/src/core/stokesian_dynamics/sd_interface.cpp @@ -26,7 +26,7 @@ #include "BoxGeometry.hpp" #include "Particle.hpp" -#include "ParticleRange.hpp" +#include "communication.hpp" #include "system/System.hpp" #include "thermostat.hpp" @@ -87,7 +87,8 @@ void register_integrator(StokesianDynamicsParameters const &obj) { } /** Update translational and rotational velocities of all particles. */ -void sd_update_locally(ParticleRange const &parts) { +template +void sd_update_locally(ParticleIterable const &parts) { std::size_t i = 0; // Even though on the head node, the v_sd vector is larger than @@ -96,11 +97,6 @@ void sd_update_locally(ParticleRange const &parts) { // (which holds the velocities of ALL particles). for (auto &p : parts) { - // skip virtual particles - if (p.is_virtual()) { - continue; - } - // Copy velocities p.v()[0] = v_sd[6 * i + 0]; p.v()[1] = v_sd[6 * i + 1]; @@ -110,7 +106,7 @@ void sd_update_locally(ParticleRange const &parts) { p.omega()[1] = v_sd[6 * i + 4]; p.omega()[2] = v_sd[6 * i + 5]; - i++; + ++i; } } @@ -141,19 +137,19 @@ void set_sd_kT(double kT) { double get_sd_kT() { return sd_kT; } -void propagate_vel_pos_sd(const ParticleRange &particles, - const boost::mpi::communicator &comm, - const double time_step) { +void propagate_vel_pos_sd(ParticleRangeStokesian const &particles, + double const time_step) { + static std::vector parts_buffer{}; parts_buffer.clear(); boost::transform(particles, std::back_inserter(parts_buffer), [](auto const &p) { return SD_particle_data(p); }); - Utils::Mpi::gather_buffer(parts_buffer, comm, 0); + Utils::Mpi::gather_buffer(parts_buffer, ::comm_cart, 0); /* Buffer that holds local particle data, and all particles on the head * node used for sending particle data to head node. */ - if (comm.rank() == 0) { + if (::comm_cart.rank() == 0) { std::size_t n_part = parts_buffer.size(); static std::vector x_host{}; @@ -182,9 +178,7 @@ void propagate_vel_pos_sd(const ParticleRange &particles, f_host[6 * i + 4] = p.ext_force.torque[1]; f_host[6 * i + 5] = p.ext_force.torque[2]; - double radius = params.radii[p.type]; - - a_host[i] = radius; + a_host[i] = params.radii.at(p.type); ++i; } @@ -197,8 +191,8 @@ void propagate_vel_pos_sd(const ParticleRange &particles, v_sd.resize(particles.size() * 6); } // if (this_node == 0) {...} else - Utils::Mpi::scatter_buffer(v_sd.data(), - static_cast(particles.size() * 6), comm, 0); + Utils::Mpi::scatter_buffer( + v_sd.data(), static_cast(particles.size() * 6), ::comm_cart, 0); sd_update_locally(particles); } diff --git a/src/core/stokesian_dynamics/sd_interface.hpp b/src/core/stokesian_dynamics/sd_interface.hpp index bdc95116e0a..7977c31e066 100644 --- a/src/core/stokesian_dynamics/sd_interface.hpp +++ b/src/core/stokesian_dynamics/sd_interface.hpp @@ -17,24 +17,38 @@ * along with this program. If not, see . */ +#pragma once + /** @file * See @cite durlofsky87a for the Stokesian dynamics method used here. * See @cite banchio03a and @cite brady88a for the thermalization method. */ -#ifndef STOKESIAN_DYNAMICS_INTERFACE_H -#define STOKESIAN_DYNAMICS_INTERFACE_H - #include "config/config.hpp" #ifdef STOKESIAN_DYNAMICS #include "ParticleRange.hpp" - -#include +#include "PropagationMode.hpp" +#include "PropagationPredicate.hpp" #include +struct PropagationPredicateStokesian { + int modes; + PropagationPredicateStokesian(int default_propagation) { + modes = PropagationMode::TRANS_STOKESIAN; + if (default_propagation & PropagationMode::TRANS_STOKESIAN) { + modes |= PropagationMode::SYSTEM_DEFAULT; + } + } + + bool operator()(int prop) const { return (prop & modes); } +}; + +using ParticleRangeStokesian = + ParticleRangeFiltered; + struct StokesianDynamicsParameters { double viscosity; std::unordered_map radii; @@ -61,9 +75,7 @@ double get_sd_kT(); * is gathered from all nodes and their velocities and angular velocities are * set according to the Stokesian Dynamics method. */ -void propagate_vel_pos_sd(const ParticleRange &particles, - const boost::mpi::communicator &comm, +void propagate_vel_pos_sd(ParticleRangeStokesian const &particles, double time_step); #endif // STOKESIAN_DYNAMICS -#endif diff --git a/src/core/system/System.cpp b/src/core/system/System.cpp index 10331064ee4..075bbb30be0 100644 --- a/src/core/system/System.cpp +++ b/src/core/system/System.cpp @@ -24,6 +24,7 @@ #include "BoxGeometry.hpp" #include "LocalBox.hpp" +#include "PropagationMode.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" #include "cell_system/CellStructure.hpp" #include "cell_system/CellStructureType.hpp" @@ -35,12 +36,10 @@ #include "errorhandling.hpp" #include "global_ghost_flags.hpp" #include "immersed_boundaries.hpp" -#include "integrate.hpp" #include "npt.hpp" #include "particle_node.hpp" #include "thermostat.hpp" -#include "virtual_sites.hpp" -#include "virtual_sites/VirtualSitesOff.hpp" +#include "virtual_sites/relative.hpp" #include #include @@ -61,12 +60,15 @@ System::System(Private) { box_geo = std::make_shared(); local_geo = std::make_shared(); cell_structure = std::make_shared(*box_geo); + propagation = std::make_shared(); nonbonded_ias = std::make_shared(); comfixed = std::make_shared(); galilei = std::make_shared(); bond_breakage = std::make_shared(); + lees_edwards = std::make_shared(); reinit_thermo = true; time_step = -1.; + sim_time = 0.; force_cap = 0.; min_global_cut = INACTIVE_CUTOFF; } @@ -74,6 +76,7 @@ System::System(Private) { void System::initialize() { auto handle = shared_from_this(); cell_structure->bind_system(handle); + lees_edwards->bind_system(handle); #ifdef CUDA gpu.bind_system(handle); gpu.initialize(); @@ -107,7 +110,7 @@ void System::set_time_step(double value) { void System::set_force_cap(double value) { force_cap = value; - ::recalc_forces = true; + propagation->recalc_forces = true; } void System::set_min_global_cut(double value) { @@ -176,7 +179,7 @@ void System::on_periodicity_change() { #endif #ifdef STOKESIAN_DYNAMICS - if (::integ_switch == INTEG_METHOD_SD) { + if (propagation->integ_switch == INTEG_METHOD_SD) { if (box_geo->periodic(0u) or box_geo->periodic(1u) or box_geo->periodic(2u)) runtimeErrorMsg() << "Stokesian Dynamics requires periodicity " << "(False, False, False)\n"; @@ -223,13 +226,13 @@ void System::on_timestep_change() { void System::on_short_range_ia_change() { rebuild_cell_structure(); - ::recalc_forces = true; + propagation->recalc_forces = true; } void System::on_non_bonded_ia_change() { nonbonded_ias->recalc_maximal_cutoffs(); rebuild_cell_structure(); - ::recalc_forces = true; + propagation->recalc_forces = true; } void System::on_coulomb_change() { @@ -246,13 +249,15 @@ void System::on_dipoles_change() { on_short_range_ia_change(); } -void System::on_constraint_change() { ::recalc_forces = true; } +void System::on_constraint_change() { propagation->recalc_forces = true; } -void System::on_lb_boundary_conditions_change() { ::recalc_forces = true; } +void System::on_lb_boundary_conditions_change() { + propagation->recalc_forces = true; +} void System::on_particle_local_change() { cell_structure->update_ghosts_and_resort_particle(global_ghost_flags()); - ::recalc_forces = true; + propagation->recalc_forces = true; } void System::on_particle_change() { @@ -267,7 +272,7 @@ void System::on_particle_change() { #ifdef DIPOLES dipoles.on_particle_change(); #endif - ::recalc_forces = true; + propagation->recalc_forces = true; /* the particle information is no longer valid */ invalidate_fetch_cache(); @@ -281,7 +286,9 @@ void System::on_particle_charge_change() { void System::update_dependent_particles() { #ifdef VIRTUAL_SITES - virtual_sites()->update(); +#ifdef VIRTUAL_SITES_RELATIVE + vs_relative_update_particles(*cell_structure, *box_geo); +#endif cell_structure->update_ghosts_and_resort_particle(global_ghost_flags()); #endif @@ -379,11 +386,13 @@ void System::on_integration_start() { if (reinit_thermo) { thermo_init(time_step); reinit_thermo = false; - ::recalc_forces = true; + propagation->recalc_forces = true; } #ifdef NPT - npt_ensemble_init(*box_geo); + if (propagation->integ_switch == INTEG_METHOD_NPT_ISO) { + npt_ensemble_init(box_geo->length(), propagation->recalc_forces); + } #endif invalidate_fetch_cache(); @@ -420,9 +429,4 @@ void mpi_init_stand_alone(int argc, char **argv) { // initialize the MpiCallbacks framework Communication::init(mpi_env); - - // default-construct global state of the system -#ifdef VIRTUAL_SITES - set_virtual_sites(std::make_shared()); -#endif } diff --git a/src/core/system/System.hpp b/src/core/system/System.hpp index 832d54f2b49..0e6174457ce 100644 --- a/src/core/system/System.hpp +++ b/src/core/system/System.hpp @@ -39,6 +39,7 @@ class BoxGeometry; class LocalBox; struct CellStructure; +class Propagation; class InteractionsNonBonded; class ComFixed; class Galilei; @@ -46,6 +47,9 @@ class Observable_stat; namespace BondBreakage { class BondBreakage; } +namespace LeesEdwards { +class LeesEdwards; +} namespace System { @@ -76,6 +80,12 @@ class System : public std::enable_shared_from_this { /** @brief Set @ref time_step. */ void set_time_step(double value); + /** @brief Get @ref sim_time. */ + auto get_sim_time() const { return sim_time; } + + /** @brief Set @ref sim_time. */ + void set_sim_time(double value); + /** @brief Get @ref force_cap. */ auto get_force_cap() const { return force_cap; } @@ -151,7 +161,7 @@ class System : public std::enable_shared_from_this { * It is up to the propagation kernels to increment the simulation time. * * This function propagates the system according to the choice of integrator - * stored in @ref integ_switch. The general structure is: + * stored in @ref Propagation::integ_switch. The general structure is: * - if reuse_forces is zero, recalculate the forces based on the current * state of the system * - Loop over the number of simulation steps: @@ -178,6 +188,8 @@ class System : public std::enable_shared_from_this { int integrate_with_signal_handler(int n_steps, int reuse_forces, bool update_accumulators); + void thermostats_force_init(double kT); + /** \name Hook procedures * These procedures are called if several significant changes to * the system happen which may make a reinitialization of subsystems @@ -216,10 +228,6 @@ class System : public std::enable_shared_from_this { void on_particle_change(); /** @brief Called every time a particle charge changes. */ void on_particle_charge_change(); - /** @brief Update particles with properties depending on other particles, - * namely virtual sites and ICC charges. - */ - void update_dependent_particles(); /** called before calculating observables, i.e. energy, pressure or * the integrator (forces). Initialize any methods here which are not * initialized immediately (P3M etc.). @@ -227,6 +235,16 @@ class System : public std::enable_shared_from_this { void on_observable_calc(); /**@}*/ + /** + * @brief Update particles with properties depending on other particles, + * namely virtual sites and ICC charges. + */ + void update_dependent_particles(); + /** + * @brief Update the global propagation bitmask. + */ + void update_used_propagations(); + Coulomb::Solver coulomb; Dipoles::Solver dipoles; LB::Solver lb; @@ -234,10 +252,12 @@ class System : public std::enable_shared_from_this { std::shared_ptr box_geo; std::shared_ptr local_geo; std::shared_ptr cell_structure; + std::shared_ptr propagation; std::shared_ptr nonbonded_ias; std::shared_ptr comfixed; std::shared_ptr galilei; std::shared_ptr bond_breakage; + std::shared_ptr lees_edwards; protected: /** @brief Whether the thermostat has to be reinitialized before integration. @@ -245,6 +265,8 @@ class System : public std::enable_shared_from_this { bool reinit_thermo; /** @brief Molecular dynamics integrator time step. */ double time_step; + /** @brief Molecular dynamics integrator simulation time. */ + double sim_time; /** @brief Molecular dynamics integrator force capping. */ double force_cap; /** diff --git a/src/core/system/System.impl.hpp b/src/core/system/System.impl.hpp index fdb950d2b4c..3a2c1e9f662 100644 --- a/src/core/system/System.impl.hpp +++ b/src/core/system/System.impl.hpp @@ -29,6 +29,8 @@ #include "cell_system/CellStructure.hpp" #include "galilei/ComFixed.hpp" #include "galilei/Galilei.hpp" +#include "integrators/Propagation.hpp" +#include "lees_edwards/lees_edwards.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include "BoxGeometry.hpp" diff --git a/src/core/thermostat.cpp b/src/core/thermostat.cpp index 3b0ce275bad..cc44101ac32 100644 --- a/src/core/thermostat.cpp +++ b/src/core/thermostat.cpp @@ -29,7 +29,6 @@ #include "communication.hpp" #include "dpd.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" #include "npt.hpp" #include "system/System.hpp" #include "thermostat.hpp" @@ -41,7 +40,6 @@ int thermo_switch = THERMO_OFF; double temperature = 0.0; -bool thermo_virtual = true; using Thermostat::GammaType; @@ -185,16 +183,6 @@ void mpi_set_langevin_gamma_rot(GammaType const &gamma) { mpi_call_all(mpi_set_langevin_gamma_rot_local, gamma); } -void mpi_set_thermo_virtual_local(bool thermo_virtual) { - ::thermo_virtual = thermo_virtual; -} - -REGISTER_CALLBACK(mpi_set_thermo_virtual_local) - -void mpi_set_thermo_virtual(bool thermo_virtual) { - mpi_call_all(mpi_set_thermo_virtual_local, thermo_virtual); -} - void mpi_set_temperature_local(double temperature) { ::temperature = temperature; try { diff --git a/src/core/thermostat.hpp b/src/core/thermostat.hpp index dfd0c507c36..88878664599 100644 --- a/src/core/thermostat.hpp +++ b/src/core/thermostat.hpp @@ -18,8 +18,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_THERMOSTAT_HPP -#define CORE_THERMOSTAT_HPP + +#pragma once + /** \file * Implementation in \ref thermostat.cpp. */ @@ -93,9 +94,6 @@ extern int thermo_switch; /** Temperature of the thermostat. */ extern double temperature; -/** True if the thermostat should act on virtual particles. */ -extern bool thermo_virtual; - #ifdef THERMOSTAT_PER_PARTICLE inline auto const & handle_particle_gamma(Thermostat::GammaType const &particle_gamma, @@ -409,8 +407,6 @@ void mpi_set_brownian_gamma_rot(Thermostat::GammaType const &gamma); void mpi_set_langevin_gamma(Thermostat::GammaType const &gamma); void mpi_set_langevin_gamma_rot(Thermostat::GammaType const &gamma); -void mpi_set_thermo_virtual(bool thermo_virtual); - void mpi_set_temperature(double temperature); void mpi_set_temperature_local(double temperature); @@ -421,5 +417,3 @@ void mpi_set_thermo_switch_local(int thermo_switch); void mpi_set_nptiso_gammas(double gamma0, double gammav); void mpi_set_nptiso_gammas_local(double gamma0, double gammav); #endif - -#endif diff --git a/src/core/thermostats/brownian_inline.hpp b/src/core/thermostats/brownian_inline.hpp index bc2ee7675f1..f010b03eeaa 100644 --- a/src/core/thermostats/brownian_inline.hpp +++ b/src/core/thermostats/brownian_inline.hpp @@ -19,8 +19,7 @@ * along with this program. If not, see . */ -#ifndef THERMOSTATS_BROWNIAN_INLINE_HPP -#define THERMOSTATS_BROWNIAN_INLINE_HPP +#pragma once #include "config/config.hpp" @@ -150,10 +149,6 @@ inline Utils::Vector3d bd_drag_vel(Thermostat::GammaType const &brownian_gamma, */ inline Utils::Vector3d bd_random_walk(BrownianThermostat const &brownian, Particle const &p, double dt, double kT) { - // skip the translation thermalizing for virtual sites unless enabled - if (p.is_virtual() and !thermo_virtual) - return {}; - Thermostat::GammaType sigma_pos = brownian.sigma_pos; #ifdef THERMOSTAT_PER_PARTICLE // override default if particle-specific gamma @@ -219,10 +214,6 @@ inline Utils::Vector3d bd_random_walk(BrownianThermostat const &brownian, */ inline Utils::Vector3d bd_random_walk_vel(BrownianThermostat const &brownian, Particle const &p) { - // skip the translation thermalizing for virtual sites unless enabled - if (p.is_virtual() and !thermo_virtual) - return {}; - auto const noise = Random::noise_gaussian( brownian.rng_counter(), brownian.rng_seed(), p.id()); Utils::Vector3d velocity = {}; @@ -384,5 +375,3 @@ bd_random_walk_vel_rot(BrownianThermostat const &brownian, Particle const &p) { return mask(p.rotation(), domega); } #endif // ROTATION - -#endif // THERMOSTATS_BROWNIAN_INLINE_HPP diff --git a/src/core/thermostats/langevin_inline.hpp b/src/core/thermostats/langevin_inline.hpp index 419d1314536..602758c4a11 100644 --- a/src/core/thermostats/langevin_inline.hpp +++ b/src/core/thermostats/langevin_inline.hpp @@ -19,8 +19,7 @@ * along with this program. If not, see . */ -#ifndef THERMOSTATS_LANGEVIN_INLINE_HPP -#define THERMOSTATS_LANGEVIN_INLINE_HPP +#pragma once #include "config/config.hpp" @@ -44,11 +43,6 @@ inline Utils::Vector3d friction_thermo_langevin(LangevinThermostat const &langevin, Particle const &p, double time_step, double kT) { - // Early exit for virtual particles without thermostat - if (p.is_virtual() and !thermo_virtual) { - return {}; - } - // Determine prefactors for the friction and the noise term #ifdef THERMOSTAT_PER_PARTICLE auto const gamma = handle_particle_gamma(p.gamma(), langevin.gamma); @@ -61,7 +55,6 @@ friction_thermo_langevin(LangevinThermostat const &langevin, Particle const &p, auto const friction_op = handle_particle_anisotropy(p, pref_friction); auto const noise_op = handle_particle_anisotropy(p, pref_noise); - return friction_op * p.v() + noise_op * Random::noise_uniform( langevin.rng_counter(), langevin.rng_seed(), p.id()); @@ -97,6 +90,4 @@ friction_thermo_langevin_rotation(LangevinThermostat const &langevin, return -hadamard_product(pref_friction, p.omega()) + hadamard_product(pref_noise, noise); } - #endif // ROTATION -#endif diff --git a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp index db3f81fc755..ee604d1f2b3 100644 --- a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp +++ b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp @@ -29,6 +29,7 @@ namespace utf = boost::unit_test; #include "Observable_stat.hpp" #include "Particle.hpp" +#include "PropagationMode.hpp" #include "accumulators/TimeSeries.hpp" #include "actor/registration.hpp" #include "bonded_interactions/bonded_interaction_utils.hpp" @@ -42,6 +43,7 @@ namespace utf = boost::unit_test; #include "electrostatics/p3m.hpp" #include "galilei/Galilei.hpp" #include "integrate.hpp" +#include "integrators/Propagation.hpp" #include "magnetostatics/dipoles.hpp" #include "nonbonded_interactions/lj.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" @@ -340,7 +342,7 @@ BOOST_FIXTURE_TEST_CASE(espresso_system_stand_alone, ParticleFactory) { // check integration { // set up velocity-Verlet integrator - set_integ_switch(INTEG_METHOD_NVT); + espresso::system->propagation->set_integ_switch(INTEG_METHOD_NVT); // reset system remove_translational_motion(system); diff --git a/src/core/unit_tests/Particle_test.cpp b/src/core/unit_tests/Particle_test.cpp index 8eef353fc60..a03c2c2e514 100644 --- a/src/core/unit_tests/Particle_test.cpp +++ b/src/core/unit_tests/Particle_test.cpp @@ -24,6 +24,7 @@ #include #include "Particle.hpp" +#include "PropagationMode.hpp" #include "config/config.hpp" #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include @@ -290,4 +292,8 @@ BOOST_AUTO_TEST_CASE(particle_bitfields) { p.set_cannot_rotate_all_axes(); BOOST_CHECK(not p.can_rotate()); #endif + + static_assert( + std::is_same_v, + decltype(ParticleProperties::propagation)>); } diff --git a/src/core/unit_tests/Verlet_list_test.cpp b/src/core/unit_tests/Verlet_list_test.cpp index 143147720fd..04d2ca1f4d0 100644 --- a/src/core/unit_tests/Verlet_list_test.cpp +++ b/src/core/unit_tests/Verlet_list_test.cpp @@ -36,14 +36,17 @@ namespace bdata = boost::unit_test::data; #include "particle_management.hpp" #include "Particle.hpp" +#include "PropagationMode.hpp" #include "cell_system/CellStructureType.hpp" #include "cells.hpp" #include "communication.hpp" #include "integrate.hpp" +#include "integrators/Propagation.hpp" #include "integrators/steepest_descent.hpp" #include "nonbonded_interactions/lj.hpp" #include "npt.hpp" #include "particle_node.hpp" +#include "system/System.hpp" #include "thermostat.hpp" #include @@ -85,7 +88,8 @@ struct : public IntegratorHelper { void set_integrator() const override { mpi_set_thermo_switch_local(THERMO_OFF); register_integrator(SteepestDescentParameters(0., 0.01, 100.)); - set_integ_switch(INTEG_METHOD_STEEPEST_DESCENT); + espresso::system->propagation->set_integ_switch( + INTEG_METHOD_STEEPEST_DESCENT); } void set_particle_properties(int pid) const override { set_particle_property(pid, &Particle::ext_force, @@ -98,7 +102,7 @@ struct : public IntegratorHelper { struct : public IntegratorHelper { void set_integrator() const override { mpi_set_thermo_switch_local(THERMO_OFF); - set_integ_switch(INTEG_METHOD_NVT); + espresso::system->propagation->set_integ_switch(INTEG_METHOD_NVT); } void set_particle_properties(int pid) const override { set_particle_v(pid, {20., 0., 0.}); @@ -110,7 +114,7 @@ struct : public IntegratorHelper { struct : public IntegratorHelper { void set_integrator() const override { ::nptiso = NptIsoParameters(1., 1e9, {true, true, true}, true); - set_integ_switch(INTEG_METHOD_NPT_ISO); + espresso::system->propagation->set_integ_switch(INTEG_METHOD_NPT_ISO); mpi_set_temperature_local(1.); mpi_npt_iso_set_rng_seed(0); mpi_set_thermo_switch_local(thermo_switch | THERMO_NPT_ISO); diff --git a/src/core/unit_tests/energy_test.cpp b/src/core/unit_tests/energy_test.cpp index cd9e9dbf455..09a5637f3a9 100644 --- a/src/core/unit_tests/energy_test.cpp +++ b/src/core/unit_tests/energy_test.cpp @@ -22,6 +22,7 @@ #include #include "Particle.hpp" +#include "PropagationMode.hpp" #include "energy_inline.hpp" #include @@ -47,7 +48,7 @@ BOOST_AUTO_TEST_CASE(translational_kinetic_energy_) { #ifdef MASS p.mass() = 2.; #endif - p.set_virtual(true); + p.propagation() = PropagationMode::TRANS_VS_RELATIVE; p.v() = {3., 4., 5.}; auto const expected = 0.; @@ -78,7 +79,7 @@ BOOST_AUTO_TEST_CASE(rotational_kinetic_energy_) { #ifdef ROTATIONAL_INERTIA p.rinertia() = {1., 2., 3.}; #endif - p.set_virtual(true); + p.propagation() = PropagationMode::ROT_VS_RELATIVE; p.omega() = {3., 4., 5.}; p.set_can_rotate_all_axes(); diff --git a/src/core/unit_tests/lb_particle_coupling_test.cpp b/src/core/unit_tests/lb_particle_coupling_test.cpp index 73ba0a71db0..50e512ceb55 100644 --- a/src/core/unit_tests/lb_particle_coupling_test.cpp +++ b/src/core/unit_tests/lb_particle_coupling_test.cpp @@ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE(rng) { lb_particle_coupling.gamma = 0.2; auto &lb = espresso::system->lb; - LB::ParticleCoupling coupling{lb, true, params.time_step, 1.}; + LB::ParticleCoupling coupling{lb, params.time_step, 1.}; BOOST_REQUIRE(lb_particle_coupling.rng_counter_coupling); BOOST_CHECK_EQUAL(lb_lbcoupling_get_rng_state(), 17); BOOST_CHECK(not lb_lbcoupling_is_seed_required()); @@ -209,7 +209,7 @@ BOOST_AUTO_TEST_CASE(rng) { BOOST_CHECK(step1_random1 != step2_random1); BOOST_CHECK(step1_random1 != step2_random2); - LB::ParticleCoupling coupling_unthermalized{lb, true, params.time_step, 0.}; + LB::ParticleCoupling coupling_unthermalized{lb, params.time_step, 0.}; auto const step3_norandom = coupling_unthermalized.get_noise_term(test_partcl_2); BOOST_CHECK((step3_norandom == Utils::Vector3d{0., 0., 0.})); @@ -218,7 +218,7 @@ BOOST_AUTO_TEST_CASE(rng) { BOOST_AUTO_TEST_CASE(drift_vel_offset) { Particle p{}; auto &lb = espresso::system->lb; - LB::ParticleCoupling coupling{lb, false, params.time_step}; + LB::ParticleCoupling coupling{lb, params.time_step}; BOOST_CHECK_EQUAL(coupling.lb_drift_velocity_offset(p).norm(), 0); Utils::Vector3d expected{}; #ifdef LB_ELECTROHYDRODYNAMICS @@ -261,7 +261,7 @@ BOOST_DATA_TEST_CASE(swimmer_force, bdata::make(kTs), kT) { // swimmer coupling { if (in_local_halo(p.pos())) { - LB::ParticleCoupling coupling{lb, true, params.time_step}; + LB::ParticleCoupling coupling{lb, params.time_step}; coupling.kernel(p); auto const interpolated = LB::get_force_to_be_applied(p.pos()); auto const expected = @@ -312,7 +312,7 @@ BOOST_DATA_TEST_CASE(particle_coupling, bdata::make(kTs), kT) { auto const gamma = 0.2; lb_lbcoupling_set_gamma(gamma); Particle p{}; - LB::ParticleCoupling coupling{lb, false, params.time_step}; + LB::ParticleCoupling coupling{lb, params.time_step}; auto expected = coupling.get_noise_term(p); #ifdef LB_ELECTROHYDRODYNAMICS p.mu_E() = Utils::Vector3d{-2., 1.5, 1.}; @@ -371,7 +371,7 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, set_particle_property(pid, &Particle::mu_E, Utils::Vector3d{-2., 1.5, 1.}); #endif - LB::ParticleCoupling coupling{lb, thermo_virtual, params.time_step}; + LB::ParticleCoupling coupling{lb, params.time_step}; auto const p_opt = copy_particle_to_head_node(comm, system, pid); auto expected = Utils::Vector3d{}; if (rank == 0) { @@ -443,8 +443,7 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, lb_lbcoupling_broadcast(); auto const particles = cell_structure.local_particles(); auto const ghost_particles = cell_structure.ghost_particles(); - LB::couple_particles(thermo_virtual, particles, ghost_particles, - params.time_step); + LB::couple_particles(particles, ghost_particles, params.time_step); auto const p_opt = copy_particle_to_head_node(comm, system, pid); if (rank == 0) { auto const &p = *p_opt; @@ -468,8 +467,7 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, } } // couple particle to LB - LB::couple_particles(thermo_virtual, particles, ghost_particles, - params.time_step); + LB::couple_particles(particles, ghost_particles, params.time_step); { auto const p_opt = copy_particle_to_head_node(comm, system, pid); if (rank == 0) { diff --git a/src/core/virtual_sites.cpp b/src/core/virtual_sites.cpp index 1838760dbf6..370beeeef9f 100644 --- a/src/core/virtual_sites.cpp +++ b/src/core/virtual_sites.cpp @@ -21,7 +21,7 @@ #include "config/config.hpp" -#ifdef VIRTUAL_SITES +#ifdef VIRTUAL_SITES_RELATIVE #include "virtual_sites.hpp" @@ -29,7 +29,6 @@ #include "Particle.hpp" #include "communication.hpp" #include "errorhandling.hpp" -#include "integrate.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include @@ -40,24 +39,10 @@ #include #include -namespace { -std::shared_ptr m_virtual_sites; -} - -const std::shared_ptr &virtual_sites() { return m_virtual_sites; } - -void set_virtual_sites(std::shared_ptr const &v) { - m_virtual_sites = v; - recalc_forces = true; -} - -#ifdef VIRTUAL_SITES_RELATIVE - -/** Calculate the rotation quaternion and distance between two particles */ std::tuple, double> calculate_vs_relate_to_params(Particle const &p_vs, Particle const &p_relate_to, - BoxGeometry const &box_geo, - double min_global_cut) { + BoxGeometry const &box_geo, double min_global_cut, + bool override_cutoff_check) { // get the distance between the particles auto d = box_geo.get_mi_vector(p_vs.pos(), p_relate_to.pos()); @@ -65,7 +50,7 @@ calculate_vs_relate_to_params(Particle const &p_vs, Particle const &p_relate_to, // than minimum global cutoff. If so, warn user. auto const dist = d.norm(); if (dist > min_global_cut and ::communicator.size > 1 and - not virtual_sites()->get_override_cutoff_check()) { + not override_cutoff_check) { runtimeErrorMsg() << "Warning: The distance between virtual and non-virtual particle (" << dist << ") is larger than the minimum global cutoff (" @@ -128,4 +113,3 @@ calculate_vs_relate_to_params(Particle const &p_vs, Particle const &p_relate_to, } #endif // VIRTUAL_SITES_RELATIVE -#endif // VIRTUAL_SITES diff --git a/src/core/virtual_sites.hpp b/src/core/virtual_sites.hpp index ae85a2a570c..aeafd663fb5 100644 --- a/src/core/virtual_sites.hpp +++ b/src/core/virtual_sites.hpp @@ -21,30 +21,22 @@ #include "config/config.hpp" -#ifdef VIRTUAL_SITES -#include "Particle.hpp" - -#include "virtual_sites/VirtualSites.hpp" - -#include - -/** @brief Get active virtual sites implementation */ -const std::shared_ptr &virtual_sites(); - -/** @brief Set active virtual sites implementation */ -void set_virtual_sites(std::shared_ptr const &v); - #ifdef VIRTUAL_SITES_RELATIVE #include "BoxGeometry.hpp" +#include "Particle.hpp" +#include "PropagationMode.hpp" #include #include -std::tuple, double> calculate_vs_relate_to_params( - Particle const &p_current, Particle const &p_relate_to, - BoxGeometry const &box_geo, double min_global_cut); +/** Calculate the rotation quaternion and distance between two particles */ +std::tuple, double> +calculate_vs_relate_to_params(Particle const &p_current, + Particle const &p_relate_to, + BoxGeometry const &box_geo, double min_global_cut, + bool override_cutoff_check = false); /** * @brief Setup a virtual site to track a real particle. @@ -61,7 +53,8 @@ inline void vs_relate_to(Particle &p_vs, Particle const &p_relate_to, vs_relative.to_particle_id = p_relate_to.id(); std::tie(vs_relative.rel_orientation, vs_relative.distance) = calculate_vs_relate_to_params(p_vs, p_relate_to, box_geo, min_global_cut); + p_vs.propagation() = + PropagationMode::TRANS_VS_RELATIVE | PropagationMode::ROT_VS_RELATIVE; } #endif // VIRTUAL_SITES_RELATIVE -#endif // VIRTUAL_SITES diff --git a/src/core/virtual_sites/CMakeLists.txt b/src/core/virtual_sites/CMakeLists.txt index 13401606e58..a59c29840e2 100644 --- a/src/core/virtual_sites/CMakeLists.txt +++ b/src/core/virtual_sites/CMakeLists.txt @@ -17,7 +17,5 @@ # along with this program. If not, see . # -target_sources( - espresso_core - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/VirtualSitesInertialessTracers.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/VirtualSitesRelative.cpp) +target_sources(espresso_core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/lb_tracers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relative.cpp) diff --git a/src/core/virtual_sites/VirtualSites.hpp b/src/core/virtual_sites/VirtualSites.hpp deleted file mode 100644 index 0d172059d22..00000000000 --- a/src/core/virtual_sites/VirtualSites.hpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2010-2022 The ESPResSo project - * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010 - * Max-Planck-Institute for Polymer Research, Theory Group - * - * 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 . - */ -#ifndef VIRTUAL_SITES_VIRTUAL_SITES_HPP -#define VIRTUAL_SITES_VIRTUAL_SITES_HPP - -/** \file - * This file contains routine to handle virtual sites - * Virtual sites are like particles, but they will not be integrated. - * Step performed for virtual sites: - * - update virtual sites - * - calculate forces - * - distribute forces - * - move non-virtual particles - * - update virtual sites - */ - -#include "config/config.hpp" - -#ifdef VIRTUAL_SITES -#include -#include - -/** @brief Base class for virtual sites implementations */ -class VirtualSites { -public: - VirtualSites() = default; - virtual ~VirtualSites() = default; - - /** - * @brief Update positions and velocities of virtual sites. - */ - virtual void update() const {} - /** Back-transfer forces (and torques) to non-virtual particles. */ - virtual void back_transfer_forces_and_torques() const {} - /** @brief Called after force calculation (and before rattle/shake) */ - virtual void after_force_calc(double) {} - virtual void after_lb_propagation(double) {} - /** @brief Pressure contribution. */ - virtual Utils::Matrix pressure_tensor() const { return {}; } - /** @brief Enable/disable quaternion calculations for vs.*/ - void set_have_quaternion(const bool &have_quaternion) { - m_have_quaternion = have_quaternion; - } - bool have_quaternions() const { return m_have_quaternion; } - /** @brief Enable/disable override for the vs cutoff check */ - void set_override_cutoff_check(const bool &override_cutoff_check) { - m_override_cutoff_check = override_cutoff_check; - } - bool get_override_cutoff_check() const { return m_override_cutoff_check; } - -private: - bool m_have_quaternion = false; - bool m_override_cutoff_check = false; -}; - -#endif -#endif diff --git a/src/core/virtual_sites/VirtualSitesInertialessTracers.cpp b/src/core/virtual_sites/lb_tracers.cpp similarity index 88% rename from src/core/virtual_sites/VirtualSitesInertialessTracers.cpp rename to src/core/virtual_sites/lb_tracers.cpp index 4993ac0012f..b688a5ab13c 100644 --- a/src/core/virtual_sites/VirtualSitesInertialessTracers.cpp +++ b/src/core/virtual_sites/lb_tracers.cpp @@ -20,9 +20,8 @@ #ifdef VIRTUAL_SITES_INERTIALESS_TRACERS -#include "VirtualSitesInertialessTracers.hpp" - #include "BoxGeometry.hpp" +#include "PropagationMode.hpp" #include "cell_system/CellStructure.hpp" #include "errorhandling.hpp" #include "forces.hpp" @@ -46,10 +45,10 @@ static bool lb_active_check(DeferredActiveLBChecks const &check) { return true; } -void VirtualSitesInertialessTracers::after_force_calc(double time_step) { +void lb_tracers_add_particle_force_to_fluid(CellStructure &cell_structure, + double time_step) { DeferredActiveLBChecks check_lb_solver_set{}; auto &system = System::get_system(); - auto &cell_structure = *system.cell_structure; auto const &box_geo = *system.box_geo; auto const agrid = (check_lb_solver_set()) ? system.lb.get_agrid() : 0.; auto const to_lb_units = (check_lb_solver_set()) ? 1. / agrid : 0.; @@ -58,14 +57,14 @@ void VirtualSitesInertialessTracers::after_force_calc(double time_step) { init_forces_ghosts(cell_structure.ghost_particles()); cell_structure.update_ghosts_and_resort_particle(Cells::DATA_PART_FORCE); - // Set to store ghost particles (ids) that have already been coupled + // Keep track of ghost particles (ids) that have already been coupled LB::CouplingBookkeeping bookkeeping{}; - // Apply particle forces to the LB fluid at particle positions - // For physical particles, also set particle velocity = fluid velocity + // Apply particle forces to the LB fluid at particle positions. + // For physical particles, also set particle velocity = fluid velocity. for (auto const &particle_range : {cell_structure.local_particles(), cell_structure.ghost_particles()}) { for (auto const &p : particle_range) { - if (!p.is_virtual()) + if (!LB::is_tracer(p)) continue; if (!lb_active_check(check_lb_solver_set)) { return; @@ -82,17 +81,16 @@ void VirtualSitesInertialessTracers::after_force_calc(double time_step) { init_forces_ghosts(cell_structure.ghost_particles()); } -void VirtualSitesInertialessTracers::after_lb_propagation(double time_step) { +void lb_tracers_propagate(CellStructure &cell_structure, double time_step) { DeferredActiveLBChecks check_lb_solver_set{}; auto &system = System::get_system(); - auto &cell_structure = *system.cell_structure; auto const &lb = system.lb; auto const verlet_skin = cell_structure.get_verlet_skin(); auto const verlet_skin_sq = verlet_skin * verlet_skin; // Advect particles for (auto &p : cell_structure.local_particles()) { - if (!p.is_virtual()) + if (!LB::is_tracer(p)) continue; if (!lb_active_check(check_lb_solver_set)) { return; diff --git a/src/core/virtual_sites/VirtualSitesInertialessTracers.hpp b/src/core/virtual_sites/lb_tracers.hpp similarity index 65% rename from src/core/virtual_sites/VirtualSitesInertialessTracers.hpp rename to src/core/virtual_sites/lb_tracers.hpp index 0fbc25bb1be..927d82de611 100644 --- a/src/core/virtual_sites/VirtualSitesInertialessTracers.hpp +++ b/src/core/virtual_sites/lb_tracers.hpp @@ -16,22 +16,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef VIRTUAL_SITES_VIRTUAL_SITES_INERTIALESS_TRACERS_HPP -#define VIRTUAL_SITES_VIRTUAL_SITES_INERTIALESS_TRACERS_HPP + +#pragma once #include "config/config.hpp" #ifdef VIRTUAL_SITES_INERTIALESS_TRACERS -#include "VirtualSites.hpp" +#include "BoxGeometry.hpp" -/** @brief Virtual sites which are advected with an lb fluid. Forces on them are - * instantaneously transferred to the fluid - */ -class VirtualSitesInertialessTracers : public VirtualSites { - void after_force_calc(double time_step) override; - void after_lb_propagation(double time_step) override; -}; +void lb_tracers_add_particle_force_to_fluid(CellStructure &cell_structure, + double time_step); +void lb_tracers_propagate(CellStructure &cell_structure, double time_step); #endif // VIRTUAL_SITES_INERTIALESS_TRACERS -#endif diff --git a/src/core/virtual_sites/VirtualSitesRelative.cpp b/src/core/virtual_sites/relative.cpp similarity index 69% rename from src/core/virtual_sites/VirtualSitesRelative.cpp rename to src/core/virtual_sites/relative.cpp index 5503950c26d..2a8887f7cde 100644 --- a/src/core/virtual_sites/VirtualSitesRelative.cpp +++ b/src/core/virtual_sites/relative.cpp @@ -17,27 +17,25 @@ * along with this program. If not, see . */ -#include "VirtualSitesRelative.hpp" - +#include "config/config.hpp" #ifdef VIRTUAL_SITES_RELATIVE #include "BoxGeometry.hpp" #include "Particle.hpp" +#include "PropagationMode.hpp" #include "cell_system/CellStructure.hpp" #include "cells.hpp" #include "errorhandling.hpp" #include "forces.hpp" -#include "integrate.hpp" +#include "lees_edwards/lees_edwards.hpp" #include "rotation.hpp" -#include "system/System.hpp" #include #include #include #include -#include "integrate.hpp" -#include "lees_edwards/lees_edwards.hpp" +#include /** * @brief Vector pointing from the real particle to the virtual site. @@ -79,17 +77,14 @@ static Utils::Vector3d velocity(Particle const &p_ref, Particle const &p_vs) { * @param p Virtual site. * @return Pointer to real particle. */ -static Particle *get_reference_particle(Particle const &p) { - if (!p.is_virtual()) { - return nullptr; - } +static Particle *get_reference_particle(CellStructure &cell_structure, + Particle const &p) { auto const &vs_rel = p.vs_relative(); if (vs_rel.to_particle_id == -1) { runtimeErrorMsg() << "Particle with id " << p.id() << " is a dangling virtual site"; return nullptr; } - auto &cell_structure = *System::get_system().cell_structure; auto p_ref_ptr = cell_structure.get_local_particle(vs_rel.to_particle_id); if (!p_ref_ptr) { runtimeErrorMsg() << "No real particle with id " << vs_rel.to_particle_id @@ -112,33 +107,52 @@ static auto constraint_stress(Particle const &p_ref, Particle const &p_vs) { return tensor_product(-p_vs.force(), connection_vector(p_ref, p_vs)); } -void VirtualSitesRelative::update() const { - auto const &system = System::get_system(); - auto const &box_geo = *system.box_geo; - auto &cell_structure = *system.cell_structure; +static bool is_vs_relative_trans(Particle const &p) { + return p.propagation() & PropagationMode::TRANS_VS_RELATIVE; +} +static bool is_vs_relative_rot(Particle const &p) { + return p.propagation() & PropagationMode::ROT_VS_RELATIVE; +} + +static bool is_vs_relative(Particle const &p) { + return (is_vs_relative_trans(p) or is_vs_relative_rot(p)); +} + +void vs_relative_update_particles(CellStructure &cell_structure, + BoxGeometry const &box_geo) { cell_structure.ghosts_update(Cells::DATA_PART_POSITION | Cells::DATA_PART_MOMENTUM); auto const particles = cell_structure.local_particles(); for (auto &p : particles) { - auto const *p_ref_ptr = get_reference_particle(p); + if (!is_vs_relative(p)) { + continue; + } + + auto const *p_ref_ptr = get_reference_particle(cell_structure, p); if (!p_ref_ptr) continue; auto const &p_ref = *p_ref_ptr; - p.image_box() = p_ref.image_box(); - p.pos() = p_ref.pos() + connection_vector(p_ref, p); - p.v() = velocity(p_ref, p); - - if (box_geo.type() == BoxType::LEES_EDWARDS) { - auto push = LeesEdwards::Push(box_geo); - push(p, -1); // includes a position fold - } else { - box_geo.fold_position(p.pos(), p.image_box()); + + // position update + if (is_vs_relative_trans(p)) { + p.image_box() = p_ref.image_box(); + p.pos() = p_ref.pos() + connection_vector(p_ref, p); + p.v() = velocity(p_ref, p); + + if (box_geo.type() == BoxType::LEES_EDWARDS) { + auto push = LeesEdwards::Push(box_geo); + push(p, -1); // includes a position fold + } else { + box_geo.fold_position(p.pos(), p.image_box()); + } } - if (have_quaternions()) + // Orientation update + if (is_vs_relative_rot(p)) { p.quat() = p_ref.quat() * p.vs_relative().quat; + } } if (cell_structure.check_resort_required()) { @@ -148,36 +162,43 @@ void VirtualSitesRelative::update() const { // Distribute forces that have accumulated on virtual particles to the // associated real particles -void VirtualSitesRelative::back_transfer_forces_and_torques() const { - auto &cell_structure = *System::get_system().cell_structure; +void vs_relative_back_transfer_forces_and_torques( + CellStructure &cell_structure) { cell_structure.ghosts_reduce_forces(); init_forces_ghosts(cell_structure.ghost_particles()); // Iterate over all the particles in the local cells for (auto &p : cell_structure.local_particles()) { - auto *p_ref_ptr = get_reference_particle(p); - if (!p_ref_ptr) + if (!is_vs_relative(p)) continue; - // Add forces and torques + auto *p_ref_ptr = get_reference_particle(cell_structure, p); + assert(p_ref_ptr != nullptr); + auto &p_ref = *p_ref_ptr; - p_ref.force() += p.force(); - p_ref.torque() += - vector_product(connection_vector(p_ref, p), p.force()) + p.torque(); + if (is_vs_relative_trans(p)) { + p_ref.force() += p.force(); + p_ref.torque() += vector_product(connection_vector(p_ref, p), p.force()); + } + + if (is_vs_relative_rot(p)) { + p_ref.torque() += p.torque(); + } } } // Rigid body contribution to scalar pressure and pressure tensor -Utils::Matrix VirtualSitesRelative::pressure_tensor() const { - auto const &cell_structure = *System::get_system().cell_structure; - +Utils::Matrix +vs_relative_pressure_tensor(CellStructure const &cell_structure) { Utils::Matrix pressure_tensor = {}; for (auto const &p : cell_structure.local_particles()) { - auto const *p_ref_ptr = get_reference_particle(p); - if (p_ref_ptr) { - pressure_tensor += constraint_stress(*p_ref_ptr, p); + if (is_vs_relative_trans(p)) { + if (auto const *p_ref_ptr = cell_structure.get_local_particle( + p.vs_relative().to_particle_id)) { + pressure_tensor += constraint_stress(*p_ref_ptr, p); + } } } diff --git a/src/core/virtual_sites/VirtualSitesRelative.hpp b/src/core/virtual_sites/relative.hpp similarity index 59% rename from src/core/virtual_sites/VirtualSitesRelative.hpp rename to src/core/virtual_sites/relative.hpp index e7050f9c348..c0a655d23a6 100644 --- a/src/core/virtual_sites/VirtualSitesRelative.hpp +++ b/src/core/virtual_sites/relative.hpp @@ -17,29 +17,24 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef VIRTUAL_SITES_VIRTUAL_SITES_RELATIVE_HPP -#define VIRTUAL_SITES_VIRTUAL_SITES_RELATIVE_HPP + +#pragma once #include "config/config.hpp" + #ifdef VIRTUAL_SITES_RELATIVE -#include "VirtualSites.hpp" +#include "BoxGeometry.hpp" +#include "cell_system/CellStructure.hpp" #include #include -/** @brief Virtual sites implementation for rigid bodies */ -class VirtualSitesRelative : public VirtualSites { -public: - VirtualSitesRelative() = default; - /** @copydoc VirtualSites::update */ - void update() const override; - /** @copydoc VirtualSites::back_transfer_forces_and_torques */ - void back_transfer_forces_and_torques() const override; - /** @copydoc VirtualSites::pressure_tensor */ - Utils::Matrix pressure_tensor() const override; -}; - -#endif +void vs_relative_update_particles(CellStructure &cell_structure, + BoxGeometry const &box_geo); +void vs_relative_back_transfer_forces_and_torques( + CellStructure &cell_structure); +Utils::Matrix +vs_relative_pressure_tensor(CellStructure const &cell_structure); -#endif +#endif // VIRTUAL_SITES_RELATIVE diff --git a/src/python/espressomd/particle_data.py b/src/python/espressomd/particle_data.py index d9b72500f1d..bde3cb5218a 100644 --- a/src/python/espressomd/particle_data.py +++ b/src/python/espressomd/particle_data.py @@ -26,6 +26,7 @@ from .utils import check_type_or_throw_except from .code_features import assert_features, has_features from .script_interface import script_interface_register, ScriptInterfaceHelper +from .propagation import Propagation import itertools @@ -368,12 +369,15 @@ class ParticleHandle(ScriptInterfaceHelper): delete_bond : Delete an unverified bond held by the particle. bonds : ``Particle`` property containing a list of all current bonds held by ``Particle``. + is_virtual() + Whether the particle is a virtual site. + """ _so_name = "Particles::ParticleHandle" _so_creation_policy = "GLOBAL" _so_bind_methods = ( - "delete_all_bonds", + "delete_all_bonds", "is_virtual", ) # here we must redefine the script interface setters @@ -383,6 +387,8 @@ def set_params(self, **kwargs): self.set_parameter(name, value) def set_parameter(self, name, value): + if name == "propagation": + value = int(value) return self.call_method("set_param_parallel", name=name, value=value) def remove(self): @@ -419,6 +425,7 @@ def to_dict(self): del pdict[k] if has_features("EXCLUSIONS"): pdict["exclusions"] = self.exclusions + pdict["propagation"] = self.propagation pdict["bonds"] = self.bonds return pdict @@ -552,7 +559,16 @@ def exclusions(self, p_ids): assert_features("EXCLUSIONS") self.call_method("set_exclusions", p_ids=p_ids) - def vs_auto_relate_to(self, rel_to): + @property + def propagation(self): + return Propagation(self.get_parameter("propagation")) + + @propagation.setter + def propagation(self, value): + self.set_parameter("propagation", int(value)) + + def vs_auto_relate_to(self, rel_to, override_cutoff_check=False, + couple_to_lb=False, couple_to_langevin=False): """ Setup this particle as virtual site relative to the particle in argument ``rel_to``. A particle cannot relate to itself. @@ -561,6 +577,16 @@ def vs_auto_relate_to(self, rel_to): ----------- rel_to : :obj:`int` or :obj:`ParticleHandle` Particle to relate to (either particle id or particle object). + override_cutoff_check : :obj:`bool` + If True, does not check whether the cell system cutoffs + are consistent with the distance between virtual and + non-virtual particles. + couple_to_lb : :obj:`bool` + If True, the virtual site is coupled to LB friction and noise. + couple_to_langevin : :obj:`bool` + If True, the virtual site is coupled to Langevin friction + and noise. If ``couple_to_lb`` is also True, propagate LB's + equations of motion and Langevin's equations of rotation. """ if isinstance(rel_to, ParticleHandle): @@ -568,8 +594,19 @@ def vs_auto_relate_to(self, rel_to): else: check_type_or_throw_except( rel_to, 1, int, "Argument of 'vs_auto_relate_to' has to be of type ParticleHandle or int") - self.call_method("vs_relate_to", pid=rel_to) + self.call_method( + "vs_relate_to", + pid=rel_to, + override_cutoff_check=override_cutoff_check) handle_errors("vs_auto_relate_to") + if self.propagation != Propagation.NONE: + if couple_to_lb: + self.propagation |= Propagation.TRANS_LB_MOMENTUM_EXCHANGE + if couple_to_langevin: + if not couple_to_lb: + self.propagation |= Propagation.ROT_LANGEVIN | Propagation.TRANS_LANGEVIN + else: + self.propagation |= Propagation.ROT_LANGEVIN def add_verified_bond(self, bond): """ @@ -1088,6 +1125,8 @@ def add(self, *args, **kwargs): def _place_new_particle(self, p_dict): bonds = [] + if "propagation" in p_dict: + p_dict["propagation"] = int(p_dict["propagation"]) if "bonds" in p_dict: bonds = p_dict.pop("bonds") if nesting_level(bonds) == 1: diff --git a/src/python/espressomd/propagation.py b/src/python/espressomd/propagation.py new file mode 100644 index 00000000000..22bbf3cfe4b --- /dev/null +++ b/src/python/espressomd/propagation.py @@ -0,0 +1,60 @@ +# +# Copyright (C) 2023 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 . +# + +import enum + + +class Propagation(enum.IntFlag): + """ + Flags for propagation modes. Use the pipe operator to combine multiple + propagation modes. Not all combinations of propagation modes are allowed. + Flags for virtual sites are special and instruct the propagator to skip + integration; virtual sites can still be coupled to thermostats (Langevin, + lattice-Boltzmann) to apply friction and noise to their forces and torques. + """ + NONE = 0 + """No propagation.""" + SYSTEM_DEFAULT = 2**0 + """Use the system default propagator for motion and rotation.""" + TRANS_NEWTON = 2**1 + """Velocity-Verlet algorithm that integrates Newton's equations of motion.""" + TRANS_LANGEVIN = 2**2 + """Velocity-Verlet algorithm that integrates Langevin's equations of motion.""" + TRANS_LANGEVIN_NPT = 2**3 + """Velocity-Verlet algorithm that integrates Langevin's equations of motion coupled to a piston.""" + TRANS_VS_RELATIVE = 2**4 + """Algorithm for virtual sites relative motion.""" + TRANS_LB_MOMENTUM_EXCHANGE = 2**5 + """Algorithm for momentum exchange between particles and LB fluid cells.""" + TRANS_LB_TRACER = 2**6 + """Algorithm for LB inertialess tracers advection.""" + TRANS_BROWNIAN = 2**7 + """Euler algorithm that integrates Brownian's equations of motion.""" + TRANS_STOKESIAN = 2**8 + """Euler algorithm that integrates Stoke's equations of motion.""" + ROT_EULER = 2**10 + """Velocity-Verlet algorithm that integrates Euler's equations of rotation.""" + ROT_LANGEVIN = 2**11 + """Velocity-Verlet algorithm that integrates Langevin's equations of rotation.""" + ROT_VS_RELATIVE = 2**12 + """Algorithm for virtual sites relative rotation.""" + ROT_BROWNIAN = 2**13 + """Euler algorithm that integrates Brownian's equations of rotation.""" + ROT_STOKESIAN = 2**14 + """Euler algorithm that integrates Stokes' equations of rotation.""" diff --git a/src/python/espressomd/swimmer_helpers.py b/src/python/espressomd/swimmer_helpers.py index 1585f8c5079..efd042f09e3 100644 --- a/src/python/espressomd/swimmer_helpers.py +++ b/src/python/espressomd/swimmer_helpers.py @@ -20,8 +20,6 @@ import numpy as np from . import code_features from .math import calc_quaternions_from_angles -if code_features.has_features("VIRTUAL_SITES_RELATIVE"): - from .virtual_sites import VirtualSitesRelative def add_dipole_particle(system, particle, dipole_length, @@ -66,19 +64,13 @@ def add_dipole_particle(system, particle, dipole_length, raise ValueError("'dipole_length' must be >= 0.") code_features.assert_features(["ENGINE", "VIRTUAL_SITES_RELATIVE"]) - if not isinstance(system.virtual_sites, VirtualSitesRelative): - raise RuntimeError( - "system.virtual_sites must be espressomd.virtual_sites.VirtualSitesRelative.") - if not system.virtual_sites.have_quaternion: - raise RuntimeError( - "system.virtual_sites must have quaternion option turned on ('have_quaternion = True').") p = system.part.add( pos=particle.pos + dip_sign * dipole_length * particle.director, - virtual=True, type=dipole_particle_type, swimming={ + type=dipole_particle_type, swimming={ "f_swim": particle.swimming["f_swim"], "is_engine_force_on_fluid": True, }) - p.vs_auto_relate_to(particle) + p.vs_auto_relate_to(particle, couple_to_lb=True) p.vs_quat = calc_quaternions_from_angles(np.pi, 0.) return p diff --git a/src/python/espressomd/system.py b/src/python/espressomd/system.py index 43d06407342..1fd8802017c 100644 --- a/src/python/espressomd/system.py +++ b/src/python/espressomd/system.py @@ -36,7 +36,6 @@ from . import lees_edwards from . import particle_data from . import thermostat -from . import virtual_sites from .code_features import has_features, assert_features from . import utils @@ -178,8 +177,6 @@ def __init__(self, **kwargs): setable_properties = ["box_l", "min_global_cut", "periodicity", "time", "time_step", "force_cap", "max_oif_objects"] - if has_features("VIRTUAL_SITES"): - setable_properties.append("_active_virtual_sites_handle") self.call_method("set_system_handle", obj=_System(**kwargs)) self.integrator = integrate.IntegratorHandle() @@ -215,9 +212,6 @@ def __init__(self, **kwargs): self.non_bonded_inter = interactions.NonBondedInteractions() self.part = particle_data.ParticleList() self.thermostat = thermostat.Thermostat() - if has_features("VIRTUAL_SITES"): - self._active_virtual_sites_handle = virtual_sites.ActiveVirtualSitesHandle( - implementation=virtual_sites.VirtualSitesOff()) # lock class self.call_method("lock_system_creation") @@ -242,11 +236,8 @@ def _restore_object(cls, so_callback, so_callback_args, state): return so def __getstate__(self): - checkpointable_properties = [] - if has_features("VIRTUAL_SITES"): - checkpointable_properties.append("_active_virtual_sites_handle") - checkpointable_properties += [ - "non_bonded_inter", "bonded_inter", "lees_edwards", + checkpointable_properties = [ + "non_bonded_inter", "bonded_inter", "part", "auto_update_accumulators", "constraints", ] @@ -340,24 +331,6 @@ def max_cut_bonded(self): """ return self.cell_system.max_cut_bonded - @property - def virtual_sites(self): - """ - Set the virtual site implementation. - - Requires feature ``VIRTUAL_SITES``. - - Type: :obj:`espressomd.virtual_sites.ActiveVirtualSitesHandle` - - """ - assert_features("VIRTUAL_SITES") - return self._active_virtual_sites_handle.implementation - - @virtual_sites.setter - def virtual_sites(self, value): - assert_features("VIRTUAL_SITES") - self._active_virtual_sites_handle.implementation = value - @property def lb(self): """ diff --git a/src/python/espressomd/thermostat.pxd b/src/python/espressomd/thermostat.pxd index ccaceaacfc5..5830f75948a 100644 --- a/src/python/espressomd/thermostat.pxd +++ b/src/python/espressomd/thermostat.pxd @@ -25,7 +25,6 @@ from .utils cimport Vector3d cdef extern from "thermostat.hpp": double temperature int thermo_switch - cbool thermo_virtual int THERMO_OFF int THERMO_LANGEVIN int THERMO_LB @@ -103,7 +102,6 @@ cdef extern from "thermostat.hpp": void mpi_set_langevin_gamma(const double & gamma) void mpi_set_langevin_gamma_rot(const double & gamma) - void mpi_set_thermo_virtual(cbool thermo_virtual) void mpi_set_temperature(double temperature) void mpi_set_thermo_switch(int thermo_switch) diff --git a/src/python/espressomd/thermostat.pyx b/src/python/espressomd/thermostat.pyx index 80baef72f8b..ae269c4d38e 100644 --- a/src/python/espressomd/thermostat.pyx +++ b/src/python/espressomd/thermostat.pyx @@ -32,7 +32,7 @@ def AssertThermostatType(*allowed_thermostats): cdef class Thermostat: @AssertThermostatType(THERMO_LANGEVIN, THERMO_DPD) def set_langevin(self, kT=None, gamma=None, gamma_rotation=None, - act_on_virtual=False, seed=None): + seed=None): ... This will prefix an assertion that prevents setting up the Langevin @@ -104,7 +104,6 @@ cdef class Thermostat: if thmst["type"] == "LANGEVIN": self.set_langevin(kT=thmst["kT"], gamma=thmst["gamma"], gamma_rotation=thmst["gamma_rotation"], - act_on_virtual=thmst["act_on_virtual"], seed=thmst["seed"]) langevin_set_rng_counter(thmst["counter"]) if thmst["type"] == "LB": @@ -117,7 +116,6 @@ cdef class Thermostat: thmst["LB_fluid"].call_method("activate") self.set_lb( LB_fluid=thmst["LB_fluid"], - act_on_virtual=thmst["act_on_virtual"], gamma=thmst["gamma"], seed=thmst["rng_counter_fluid"]) thmst["LB_fluid"].call_method("deactivate") @@ -133,7 +131,6 @@ cdef class Thermostat: if thmst["type"] == "BROWNIAN": self.set_brownian(kT=thmst["kT"], gamma=thmst["gamma"], gamma_rotation=thmst["gamma_rotation"], - act_on_virtual=thmst["act_on_virtual"], seed=thmst["seed"]) brownian_set_rng_counter(thmst["counter"]) if thmst["type"] == "SD": @@ -155,7 +152,6 @@ cdef class Thermostat: lang_dict = {} lang_dict["type"] = "LANGEVIN" lang_dict["kT"] = temperature - lang_dict["act_on_virtual"] = thermo_virtual lang_dict["seed"] = langevin.rng_seed() lang_dict["counter"] = langevin.rng_counter() IF PARTICLE_ANISOTROPY: @@ -179,7 +175,6 @@ cdef class Thermostat: lang_dict = {} lang_dict["type"] = "BROWNIAN" lang_dict["kT"] = temperature - lang_dict["act_on_virtual"] = thermo_virtual lang_dict["seed"] = brownian.rng_seed() lang_dict["counter"] = brownian.rng_counter() IF PARTICLE_ANISOTROPY: @@ -204,7 +199,6 @@ cdef class Thermostat: lb_dict["LB_fluid"] = self._LB_fluid lb_dict["gamma"] = lb_lbcoupling_get_gamma() lb_dict["type"] = "LB" - lb_dict["act_on_virtual"] = thermo_virtual lb_dict["rng_counter_fluid"] = lb_lbcoupling_get_rng_state() thermo_list.append(lb_dict) if thermo_switch & THERMO_NPT_ISO: @@ -246,7 +240,6 @@ cdef class Thermostat: """ self._set_temperature(0.) - mpi_set_thermo_virtual(True) IF PARTICLE_ANISOTROPY: mpi_set_langevin_gamma(utils.make_Vector3d((0., 0., 0.))) mpi_set_brownian_gamma(utils.make_Vector3d((0., 0., 0.))) @@ -264,9 +257,8 @@ cdef class Thermostat: lb_lbcoupling_set_gamma(0.0) mpi_bcast_lb_particle_coupling() - @AssertThermostatType(THERMO_LANGEVIN, THERMO_DPD) - def set_langevin(self, kT, gamma, gamma_rotation=None, - act_on_virtual=False, seed=None): + @AssertThermostatType(THERMO_LANGEVIN, THERMO_DPD, THERMO_LB) + def set_langevin(self, kT, gamma, gamma_rotation=None, seed=None): """ Sets the Langevin thermostat. @@ -283,9 +275,6 @@ cdef class Thermostat: The same applies to ``gamma_rotation``, which requires the feature ``ROTATION`` to work properly. But also accepts three floats if ``PARTICLE_ANISOTROPY`` is also compiled in. - act_on_virtual : :obj:`bool`, optional - If ``True`` the thermostat will act on virtual sites, default is - ``False``. seed : :obj:`int` Initial counter value (or seed) of the philox RNG. Required on first activation of the Langevin thermostat. @@ -390,11 +379,8 @@ cdef class Thermostat: IF ROTATION: mpi_set_langevin_gamma_rot(gamma_rotation) - mpi_set_thermo_virtual(act_on_virtual) - @AssertThermostatType(THERMO_BROWNIAN) - def set_brownian(self, kT, gamma, gamma_rotation=None, - act_on_virtual=False, seed=None): + def set_brownian(self, kT, gamma, gamma_rotation=None, seed=None): """Sets the Brownian thermostat. Parameters @@ -410,9 +396,6 @@ cdef class Thermostat: The same applies to ``gamma_rotation``, which requires the feature ``ROTATION`` to work properly. But also accepts three floats if ``PARTICLE_ANISOTROPY`` is also compiled in. - act_on_virtual : :obj:`bool`, optional - If ``True`` the thermostat will act on virtual sites, default is - ``False``. seed : :obj:`int` Initial counter value (or seed) of the philox RNG. Required on first activation of the Brownian thermostat. @@ -518,15 +501,8 @@ cdef class Thermostat: IF ROTATION: mpi_set_brownian_gamma_rot(gamma_rotation) - mpi_set_thermo_virtual(act_on_virtual) - - @AssertThermostatType(THERMO_LB, THERMO_DPD) - def set_lb( - self, - seed=None, - act_on_virtual=True, - LB_fluid=None, - gamma=0.0): + @AssertThermostatType(THERMO_LB, THERMO_DPD, THERMO_LANGEVIN) + def set_lb(self, seed=None, LB_fluid=None, gamma=0.0): """ Sets the LB thermostat. @@ -538,8 +514,6 @@ cdef class Thermostat: seed : :obj:`int` Seed for the random number generator, required if kT > 0. Must be positive. - act_on_virtual : :obj:`bool`, optional - If ``True`` the thermostat will act on virtual sites (default). gamma : :obj:`float` Frictional coupling constant for the MD particle coupling. @@ -547,7 +521,7 @@ cdef class Thermostat: from .lb import HydrodynamicInteraction if not isinstance(LB_fluid, HydrodynamicInteraction): raise ValueError( - "The LB thermostat requires a LB / LBGPU instance as a keyword arg.") + "The LB thermostat requires a LB instance as a keyword arg.") self._LB_fluid = LB_fluid if LB_fluid.kT > 0.: @@ -568,7 +542,6 @@ cdef class Thermostat: global thermo_switch mpi_set_thermo_switch(thermo_switch | THERMO_LB) - mpi_set_thermo_virtual(act_on_virtual) lb_lbcoupling_set_gamma(gamma) mpi_bcast_lb_particle_coupling() diff --git a/src/python/espressomd/virtual_sites.py b/src/python/espressomd/virtual_sites.py deleted file mode 100644 index ab29f03448c..00000000000 --- a/src/python/espressomd/virtual_sites.py +++ /dev/null @@ -1,72 +0,0 @@ -# -# Copyright (C) 2010-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 . -# - -from .code_features import has_features -from .script_interface import ScriptInterfaceHelper, script_interface_register - -if has_features("VIRTUAL_SITES"): - @script_interface_register - class ActiveVirtualSitesHandle(ScriptInterfaceHelper): - - """Handle for the virtual sites implementation active in the core - - This should not be used directly. - - Attributes - ---------- - have_quaternion : :obj:`bool` - Whether the virtual sites has a quaternion (only relevant for - :class:`~espressomd.virtual_sites.VirtualSitesRelative`). - override_cutoff_check : :obj:`bool` - Whether to disable the sanity check that triggers when attempting - to set up a virtual site too far away from the real particle in a - MPI-parallel simulation with more than 1 core. Disabling this - check is not recommended; it is only relevant in rare situations - when using the :ref:`Hybrid decomposition` cell system. - - """ - _so_name = "VirtualSites::ActiveVirtualSitesHandle" - - @script_interface_register - class VirtualSitesOff(ScriptInterfaceHelper): - - """Virtual sites implementation which does nothing (default)""" - _so_name = "VirtualSites::VirtualSitesOff" - - -if has_features("VIRTUAL_SITES_INERTIALESS_TRACERS"): - @script_interface_register - class VirtualSitesInertialessTracers(ScriptInterfaceHelper): - - """Virtual sites which are advected with an LB fluid without inertia. - Forces are on them are transferred to the fluid instantly. - - """ - _so_name = "VirtualSites::VirtualSitesInertialessTracers" - - -if has_features("VIRTUAL_SITES_RELATIVE"): - @script_interface_register - class VirtualSitesRelative(ScriptInterfaceHelper): - - """Virtual sites implementation placing virtual sites relative to other - particles. See :ref:`Rigid arrangements of particles` for details. - - """ - _so_name = "VirtualSites::VirtualSitesRelative" diff --git a/src/script_interface/CMakeLists.txt b/src/script_interface/CMakeLists.txt index 290d6becf61..94030a06391 100644 --- a/src/script_interface/CMakeLists.txt +++ b/src/script_interface/CMakeLists.txt @@ -50,7 +50,6 @@ add_subdirectory(reaction_methods) add_subdirectory(scafacos) add_subdirectory(shapes) add_subdirectory(system) -add_subdirectory(virtual_sites) add_subdirectory(walberla) install(TARGETS espresso_script_interface diff --git a/src/script_interface/cell_system/CellSystem.cpp b/src/script_interface/cell_system/CellSystem.cpp index 5a88f0d1612..42388dc9bce 100644 --- a/src/script_interface/cell_system/CellSystem.cpp +++ b/src/script_interface/cell_system/CellSystem.cpp @@ -28,7 +28,6 @@ #include "core/cell_system/RegularDecomposition.hpp" #include "core/cells.hpp" #include "core/communication.hpp" -#include "core/integrate.hpp" #include "core/nonbonded_interactions/nonbonded_interaction_data.hpp" #include "core/particle_node.hpp" #include "core/system/System.hpp" diff --git a/src/script_interface/h5md/h5md.cpp b/src/script_interface/h5md/h5md.cpp index 2bace3a2e82..e3218ce8701 100644 --- a/src/script_interface/h5md/h5md.cpp +++ b/src/script_interface/h5md/h5md.cpp @@ -26,7 +26,6 @@ #include "h5md.hpp" #include "cell_system/CellStructure.hpp" -#include "core/integrate.hpp" #include "core/system/System.hpp" #include @@ -38,12 +37,12 @@ namespace Writer { Variant H5md::do_call_method(const std::string &name, const VariantMap ¶meters) { if (name == "write") { - auto const &system = System::get_system(); - auto &cell_structure = *system.cell_structure; - m_h5md->write( - cell_structure.local_particles(), get_sim_time(), - static_cast(std::round(get_sim_time() / get_time_step())), - *system.box_geo); + auto const &system = ::System::get_system(); + auto const particles = system.cell_structure->local_particles(); + auto const sim_time = system.get_sim_time(); + auto const time_step = system.get_time_step(); + auto const n_steps = static_cast(std::round(sim_time / time_step)); + m_h5md->write(particles, sim_time, n_steps, *system.box_geo); } else if (name == "flush") { m_h5md->flush(); } else if (name == "close") { diff --git a/src/script_interface/initialize.cpp b/src/script_interface/initialize.cpp index efecee634f4..a114aae664e 100644 --- a/src/script_interface/initialize.cpp +++ b/src/script_interface/initialize.cpp @@ -45,7 +45,6 @@ #include "reaction_methods/initialize.hpp" #include "shapes/initialize.hpp" #include "system/initialize.hpp" -#include "virtual_sites/initialize.hpp" #include "walberla/initialize.hpp" namespace ScriptInterface { @@ -72,7 +71,6 @@ void initialize(Utils::Factory *f) { Profiler::initialize(f); Shapes::initialize(f); System::initialize(f); - VirtualSites::initialize(f); ReactionMethods::initialize(f); #ifdef H5MD Writer::initialize(f); diff --git a/src/script_interface/integrators/BrownianDynamics.cpp b/src/script_interface/integrators/BrownianDynamics.cpp index ecc7e7699cd..e4af62a7b2c 100644 --- a/src/script_interface/integrators/BrownianDynamics.cpp +++ b/src/script_interface/integrators/BrownianDynamics.cpp @@ -21,12 +21,15 @@ #include "script_interface/ScriptInterface.hpp" -#include "core/integrate.hpp" +#include "core/PropagationMode.hpp" +#include "core/integrators/Propagation.hpp" namespace ScriptInterface { namespace Integrators { -void BrownianDynamics::activate() { set_integ_switch(INTEG_METHOD_BD); } +void ScriptInterface::Integrators::BrownianDynamics::activate() { + get_system().propagation->set_integ_switch(INTEG_METHOD_BD); +} } // namespace Integrators } // namespace ScriptInterface diff --git a/src/script_interface/integrators/IntegratorHandle.cpp b/src/script_interface/integrators/IntegratorHandle.cpp index 59dacfe4830..3b08badf6ea 100644 --- a/src/script_interface/integrators/IntegratorHandle.cpp +++ b/src/script_interface/integrators/IntegratorHandle.cpp @@ -27,7 +27,8 @@ #include "VelocityVerlet.hpp" #include "VelocityVerletIsoNPT.hpp" -#include "core/integrate.hpp" +#include "core/PropagationMode.hpp" +#include "core/integrators/Propagation.hpp" #include "core/system/System.hpp" #include @@ -44,8 +45,11 @@ IntegratorHandle::IntegratorHandle() { [&]() { get_system().set_time_step(get_value(v)); }); }, [this]() { return get_system().get_time_step(); }}, - {"time", [](Variant const &v) { set_time(get_value(v)); }, - []() { return get_sim_time(); }}, + {"time", + [&](Variant const &v) { + get_system().set_sim_time(get_value(v)); + }, + [this]() { return get_system().get_sim_time(); }}, {"force_cap", [this](Variant const &v) { get_system().set_force_cap(get_value(v)); @@ -62,7 +66,7 @@ IntegratorHandle::IntegratorHandle() { m_instance->activate(); }, [this]() { - switch (::integ_switch) { + switch (get_system().propagation->integ_switch) { case INTEG_METHOD_STEEPEST_DESCENT: return Variant{ std::dynamic_pointer_cast(m_instance)}; @@ -93,7 +97,8 @@ void IntegratorHandle::on_bind_system(::System::System &system) { auto const ¶ms = *m_params; for (auto const &key : get_parameter_insertion_order()) { if (params.count(key) != 0ul) { - if (not(key == "time_step" and ::integ_switch == INTEG_METHOD_NVT and + if (not(key == "time_step" and + system.propagation->integ_switch == INTEG_METHOD_NVT and system.get_time_step() == -1. and is_type(params.at(key)) and get_value(is_type(params.at(key))) == -1.)) { diff --git a/src/script_interface/integrators/SteepestDescent.cpp b/src/script_interface/integrators/SteepestDescent.cpp index c3a4b1bb02a..5b8f7ddce13 100644 --- a/src/script_interface/integrators/SteepestDescent.cpp +++ b/src/script_interface/integrators/SteepestDescent.cpp @@ -21,7 +21,9 @@ #include "script_interface/ScriptInterface.hpp" +#include "core/PropagationMode.hpp" #include "core/integrate.hpp" +#include "core/integrators/Propagation.hpp" #include "core/integrators/steepest_descent.hpp" #include @@ -69,7 +71,7 @@ void SteepestDescent::do_construct(VariantMap const ¶ms) { void SteepestDescent::activate() { register_integrator(get_instance()); - set_integ_switch(INTEG_METHOD_STEEPEST_DESCENT); + get_system().propagation->set_integ_switch(INTEG_METHOD_STEEPEST_DESCENT); } } // namespace Integrators diff --git a/src/script_interface/integrators/StokesianDynamics.cpp b/src/script_interface/integrators/StokesianDynamics.cpp index 54e7ab8c8f5..40d0c7b7845 100644 --- a/src/script_interface/integrators/StokesianDynamics.cpp +++ b/src/script_interface/integrators/StokesianDynamics.cpp @@ -25,7 +25,8 @@ #include "script_interface/ScriptInterface.hpp" -#include "core/integrate.hpp" +#include "core/PropagationMode.hpp" +#include "core/integrators/Propagation.hpp" #include "core/stokesian_dynamics/sd_interface.hpp" #include @@ -92,7 +93,7 @@ void StokesianDynamics::do_construct(VariantMap const ¶ms) { void StokesianDynamics::activate() { context()->parallel_try_catch([&]() { register_integrator(get_instance()); - set_integ_switch(INTEG_METHOD_SD); + get_system().propagation->set_integ_switch(INTEG_METHOD_SD); }); } diff --git a/src/script_interface/integrators/VelocityVerlet.cpp b/src/script_interface/integrators/VelocityVerlet.cpp index 4d7c7e32ea7..325ea420522 100644 --- a/src/script_interface/integrators/VelocityVerlet.cpp +++ b/src/script_interface/integrators/VelocityVerlet.cpp @@ -21,12 +21,15 @@ #include "script_interface/ScriptInterface.hpp" -#include "core/integrate.hpp" +#include "core/PropagationMode.hpp" +#include "core/integrators/Propagation.hpp" namespace ScriptInterface { namespace Integrators { -void VelocityVerlet::activate() { set_integ_switch(INTEG_METHOD_NVT); } +void VelocityVerlet::activate() { + get_system().propagation->set_integ_switch(INTEG_METHOD_NVT); +} } // namespace Integrators } // namespace ScriptInterface diff --git a/src/script_interface/integrators/VelocityVerletIsoNPT.cpp b/src/script_interface/integrators/VelocityVerletIsoNPT.cpp index 8464f75247c..52c43b9faef 100644 --- a/src/script_interface/integrators/VelocityVerletIsoNPT.cpp +++ b/src/script_interface/integrators/VelocityVerletIsoNPT.cpp @@ -25,7 +25,8 @@ #include "script_interface/ScriptInterface.hpp" -#include "core/integrate.hpp" +#include "core/PropagationMode.hpp" +#include "core/integrators/Propagation.hpp" #include "core/npt.hpp" #include @@ -64,7 +65,7 @@ void VelocityVerletIsoNPT::do_construct(VariantMap const ¶ms) { void VelocityVerletIsoNPT::activate() { ::nptiso = get_instance(); - set_integ_switch(INTEG_METHOD_NPT_ISO); + get_system().propagation->set_integ_switch(INTEG_METHOD_NPT_ISO); get_system().on_thermostat_param_change(); } diff --git a/src/script_interface/lees_edwards/LeesEdwards.hpp b/src/script_interface/lees_edwards/LeesEdwards.hpp index 579b4493f6f..d4ddd81fb99 100644 --- a/src/script_interface/lees_edwards/LeesEdwards.hpp +++ b/src/script_interface/lees_edwards/LeesEdwards.hpp @@ -16,18 +16,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_LEES_EDWARDS_LEES_EDWARDS_HPP -#define SCRIPT_INTERFACE_LEES_EDWARDS_LEES_EDWARDS_HPP + +#pragma once #include "Protocol.hpp" #include "core/BoxGeometry.hpp" #include "core/lees_edwards/LeesEdwardsBC.hpp" #include "core/lees_edwards/lees_edwards.hpp" -#include "core/system/System.hpp" #include "script_interface/ScriptInterface.hpp" #include "script_interface/auto_parameters/AutoParameters.hpp" +#include "script_interface/system/Leaf.hpp" #include #include @@ -35,32 +35,32 @@ namespace ScriptInterface { namespace LeesEdwards { -class LeesEdwards : public AutoParameters { +class LeesEdwards : public AutoParameters { + std::shared_ptr<::LeesEdwards::LeesEdwards> m_lees_edwards; std::shared_ptr m_protocol; - LeesEdwardsBC const &m_lebc; + std::unique_ptr m_params; public: - LeesEdwards() - : m_protocol{nullptr}, - m_lebc{System::get_system().box_geo->lees_edwards_bc()} { + LeesEdwards() { add_parameters( {{"protocol", [this](Variant const &value) { if (is_none(value)) { - auto const &system = System::get_system(); + auto const &system = get_system(); context()->parallel_try_catch([&system]() { auto constexpr invalid_dir = LeesEdwardsBC::invalid_dir; system.lb.lebc_sanity_checks(invalid_dir, invalid_dir); }); m_protocol = nullptr; system.box_geo->set_lees_edwards_bc(LeesEdwardsBC{}); - ::LeesEdwards::unset_protocol(); + m_lees_edwards->unset_protocol(); return; } context()->parallel_try_catch([this]() { auto constexpr invalid_dir = LeesEdwardsBC::invalid_dir; - if (m_lebc.shear_direction == invalid_dir or - m_lebc.shear_plane_normal == invalid_dir) { + auto const &lebc = get_lebc(); + if (lebc.shear_direction == invalid_dir or + lebc.shear_plane_normal == invalid_dir) { throw std::runtime_error( "Parameters 'shear_plane_normal' and 'shear_direction' " "must be initialized together with 'protocol' on first " @@ -68,7 +68,7 @@ class LeesEdwards : public AutoParameters { } }); m_protocol = get_value>(value); - ::LeesEdwards::set_protocol(m_protocol->protocol()); + m_lees_edwards->set_protocol(m_protocol->protocol()); }, [this]() { if (m_protocol) @@ -76,13 +76,13 @@ class LeesEdwards : public AutoParameters { return make_variant(none); }}, {"shear_velocity", AutoParameter::read_only, - [this]() { return m_lebc.shear_velocity; }}, + [this]() { return get_lebc().shear_velocity; }}, {"pos_offset", AutoParameter::read_only, - [this]() { return m_lebc.pos_offset; }}, + [this]() { return get_lebc().pos_offset; }}, {"shear_direction", AutoParameter::read_only, - [this]() { return get_shear_name(m_lebc.shear_direction); }}, + [this]() { return get_shear_name(get_lebc().shear_direction); }}, {"shear_plane_normal", AutoParameter::read_only, - [this]() { return get_shear_name(m_lebc.shear_plane_normal); }}}); + [this]() { return get_shear_name(get_lebc().shear_plane_normal); }}}); } Variant do_call_method(std::string const &name, @@ -103,21 +103,19 @@ class LeesEdwards : public AutoParameters { throw std::invalid_argument("Parameters 'shear_direction' and " "'shear_plane_normal' must differ"); } - auto const &system = System::get_system(); + auto const &system = get_system(); system.lb.lebc_sanity_checks(shear_direction, shear_plane_normal); // update box geometry and cell structure system.box_geo->set_lees_edwards_bc( LeesEdwardsBC{0., 0., shear_direction, shear_plane_normal}); - ::LeesEdwards::set_protocol(m_protocol->protocol()); + m_lees_edwards->set_protocol(m_protocol->protocol()); }); } return {}; } void do_construct(VariantMap const ¶ms) override { - if (not params.empty()) { - do_call_method("set_boundary_conditions", params); - } + m_params = std::make_unique(params); } private: @@ -147,9 +145,20 @@ class LeesEdwards : public AutoParameters { } return {none}; } + + LeesEdwardsBC const &get_lebc() const { + return get_system().box_geo->lees_edwards_bc(); + } + + void on_bind_system(::System::System &system) override { + m_lees_edwards = system.lees_edwards; + auto const ¶ms = *m_params; + if (not params.empty()) { + do_call_method("set_boundary_conditions", params); + } + m_params.reset(); + } }; } // namespace LeesEdwards } // namespace ScriptInterface - -#endif diff --git a/src/script_interface/lees_edwards/LinearShear.hpp b/src/script_interface/lees_edwards/LinearShear.hpp index 270a84dcc33..ceaa279da8d 100644 --- a/src/script_interface/lees_edwards/LinearShear.hpp +++ b/src/script_interface/lees_edwards/LinearShear.hpp @@ -16,34 +16,32 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_LEES_EDWARDS_LINEAR_SHEAR_HPP -#define SCRIPT_INTERFACE_LEES_EDWARDS_LINEAR_SHEAR_HPP + +#pragma once #include "core/lees_edwards/lees_edwards.hpp" #include "script_interface/ScriptInterface.hpp" #include "script_interface/auto_parameters/AutoParameters.hpp" -#include - #include +#include namespace ScriptInterface { namespace LeesEdwards { class LinearShear : public Protocol { + using CoreClass = ::LeesEdwards::LinearShear; + public: LinearShear() - : m_protocol{std::make_shared<::LeesEdwards::ActiveProtocol>( - ::LeesEdwards::LinearShear())} { + : m_protocol{ + std::make_shared<::LeesEdwards::ActiveProtocol>(CoreClass())} { add_parameters( {{"initial_pos_offset", - boost::get<::LeesEdwards::LinearShear>(*m_protocol) - .m_initial_pos_offset}, - {"shear_velocity", - boost::get<::LeesEdwards::LinearShear>(*m_protocol).m_shear_velocity}, - {"time_0", - boost::get<::LeesEdwards::LinearShear>(*m_protocol).m_time_0}}); + std::get(*m_protocol).m_initial_pos_offset}, + {"shear_velocity", std::get(*m_protocol).m_shear_velocity}, + {"time_0", std::get(*m_protocol).m_time_0}}); } std::shared_ptr<::LeesEdwards::ActiveProtocol> protocol() override { return m_protocol; @@ -55,5 +53,3 @@ class LinearShear : public Protocol { } // namespace LeesEdwards } // namespace ScriptInterface - -#endif diff --git a/src/script_interface/lees_edwards/Off.hpp b/src/script_interface/lees_edwards/Off.hpp index d5de550e72d..8d77b60bb26 100644 --- a/src/script_interface/lees_edwards/Off.hpp +++ b/src/script_interface/lees_edwards/Off.hpp @@ -16,8 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_LEES_EDWARDS_OFF_HPP -#define SCRIPT_INTERFACE_LEES_EDWARDS_OFF_HPP + +#pragma once #include "core/lees_edwards/lees_edwards.hpp" @@ -44,5 +44,3 @@ class Off : public Protocol { } // namespace LeesEdwards } // namespace ScriptInterface - -#endif diff --git a/src/script_interface/lees_edwards/OscillatoryShear.hpp b/src/script_interface/lees_edwards/OscillatoryShear.hpp index 7bf9923b193..d860c6c77cd 100644 --- a/src/script_interface/lees_edwards/OscillatoryShear.hpp +++ b/src/script_interface/lees_edwards/OscillatoryShear.hpp @@ -16,36 +16,32 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_LEES_EDWARDS_OSCILLATORY_SHEAR_HPP -#define SCRIPT_INTERFACE_LEES_EDWARDS_OSCILLATORY_SHEAR_HPP + +#pragma once #include "core/lees_edwards/lees_edwards.hpp" #include "script_interface/ScriptInterface.hpp" #include "script_interface/auto_parameters/AutoParameters.hpp" -#include - #include +#include namespace ScriptInterface { namespace LeesEdwards { class OscillatoryShear : public Protocol { + using CoreClass = ::LeesEdwards::OscillatoryShear; + public: OscillatoryShear() - : m_protocol{std::make_shared<::LeesEdwards::ActiveProtocol>( - ::LeesEdwards::OscillatoryShear())} { - add_parameters( - {{"initial_pos_offset", - boost::get<::LeesEdwards::OscillatoryShear>(*m_protocol) - .m_initial_pos_offset}, - {"amplitude", - boost::get<::LeesEdwards::OscillatoryShear>(*m_protocol).m_amplitude}, - {"omega", - boost::get<::LeesEdwards::OscillatoryShear>(*m_protocol).m_omega}, - {"time_0", - boost::get<::LeesEdwards::OscillatoryShear>(*m_protocol).m_time_0}}); + : m_protocol{ + std::make_shared<::LeesEdwards::ActiveProtocol>(CoreClass())} { + add_parameters({{"initial_pos_offset", + std::get(*m_protocol).m_initial_pos_offset}, + {"amplitude", std::get(*m_protocol).m_amplitude}, + {"omega", std::get(*m_protocol).m_omega}, + {"time_0", std::get(*m_protocol).m_time_0}}); } std::shared_ptr<::LeesEdwards::ActiveProtocol> protocol() override { return m_protocol; @@ -57,5 +53,3 @@ class OscillatoryShear : public Protocol { } // namespace LeesEdwards } // namespace ScriptInterface - -#endif diff --git a/src/script_interface/lees_edwards/Protocol.hpp b/src/script_interface/lees_edwards/Protocol.hpp index faa09aa5a9e..e4af81552c9 100644 --- a/src/script_interface/lees_edwards/Protocol.hpp +++ b/src/script_interface/lees_edwards/Protocol.hpp @@ -16,8 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_LEES_EDWARDS_PROTOCOL_HPP -#define SCRIPT_INTERFACE_LEES_EDWARDS_PROTOCOL_HPP + +#pragma once #include "core/lees_edwards/lees_edwards.hpp" @@ -36,5 +36,3 @@ class Protocol : public AutoParameters { } // namespace LeesEdwards } // namespace ScriptInterface - -#endif diff --git a/src/script_interface/particle_data/ParticleHandle.cpp b/src/script_interface/particle_data/ParticleHandle.cpp index edb4156a77b..fab4cb40323 100644 --- a/src/script_interface/particle_data/ParticleHandle.cpp +++ b/src/script_interface/particle_data/ParticleHandle.cpp @@ -25,11 +25,13 @@ #include "script_interface/get_value.hpp" #include "core/BoxGeometry.hpp" +#include "core/PropagationMode.hpp" #include "core/bonded_interactions/bonded_interaction_data.hpp" #include "core/cell_system/CellStructure.hpp" #include "core/exclusions.hpp" #include "core/nonbonded_interactions/nonbonded_interaction_data.hpp" #include "core/particle_node.hpp" +#include "core/propagation.hpp" #include "core/rotation.hpp" #include "core/system/System.hpp" #include "core/virtual_sites.hpp" @@ -238,19 +240,6 @@ ParticleHandle::ParticleHandle() { }, #endif // ELECTROSTATICS [this]() { return get_particle_data(m_pid).q(); }}, - {"virtual", -#ifdef VIRTUAL_SITES - [this](Variant const &value) { - set_particle_property(&Particle::virtual_flag, value); - }, -#else // VIRTUAL_SITES - [](Variant const &value) { - if (get_value(value)) { - throw std::runtime_error("Feature VIRTUAL_SITES not compiled in"); - } - }, -#endif // VIRTUAL_SITES - [this]() { return get_particle_data(m_pid).is_virtual(); }}, #ifdef ROTATION {"director", [this](Variant const &value) { @@ -375,6 +364,17 @@ ParticleHandle::ParticleHandle() { [this]() { return get_particle_data(m_pid).ext_torque(); }}, #endif // ROTATION #endif // EXTERNAL_FORCES + {"propagation", + [this](Variant const &value) { + auto const propagation = get_value(value); + if (!is_valid_propagation_combination(propagation)) { + throw std::domain_error(error_msg( + "propagation", "propagation combination not accepted: " + + propagation_bitmask_to_string(propagation))); + } + set_particle_property(&Particle::propagation, value); + }, + [this]() { return get_particle_data(m_pid).propagation(); }}, #ifdef THERMOSTAT_PER_PARTICLE {"gamma", [this](Variant const &value) { @@ -396,7 +396,6 @@ ParticleHandle::ParticleHandle() { auto const &box_geo = *System::get_system().box_geo; return box_geo.folded_position(get_particle_data(m_pid).pos()); }}, - {"lees_edwards_offset", [this](Variant const &value) { set_particle_property(&Particle::lees_edwards_offset, value); @@ -446,8 +445,14 @@ ParticleHandle::ParticleHandle() { throw std::invalid_argument(error_msg( "vs_relative", "must take the form [id, distance, quaternion]")); } - set_particle_property( - [&vs_relative](Particle &p) { p.vs_relative() = vs_relative; }); + set_particle_property([&vs_relative](Particle &p) { + p.vs_relative() = vs_relative; + if (vs_relative.to_particle_id != -1) { + p.propagation() = PropagationMode::TRANS_VS_RELATIVE | + PropagationMode::ROT_VS_RELATIVE; + } + assert(is_valid_propagation_combination(p.propagation())); + }); }, [this]() { auto const vs_rel = get_particle_data(m_pid).vs_relative(); @@ -587,12 +592,19 @@ Variant ParticleHandle::do_call_method(std::string const &name, std::ignore = get_real_particle(context()->get_comm(), m_pid); remove_particle(m_pid); }); + } else if (name == "is_virtual") { + if (not context()->is_head_node()) { + return {}; + } + return get_particle_data(m_pid).is_virtual(); #ifdef VIRTUAL_SITES_RELATIVE } else if (name == "vs_relate_to") { if (not context()->is_head_node()) { return {}; } auto const other_pid = get_value(params, "pid"); + auto const override_cutoff_check = + get_value(params, "override_cutoff_check"); if (m_pid == other_pid) { throw std::invalid_argument("A virtual site cannot relate to itself"); } @@ -613,10 +625,10 @@ Variant ParticleHandle::do_call_method(std::string const &name, auto const &p_current = get_particle_data(m_pid); auto const &p_relate_to = get_particle_data(other_pid); auto const [quat, dist] = calculate_vs_relate_to_params( - p_current, p_relate_to, *system.box_geo, system.get_min_global_cut()); + p_current, p_relate_to, *system.box_geo, system.get_min_global_cut(), + override_cutoff_check); set_parameter("vs_relative", Variant{std::vector{ {other_pid, dist, quat2vector(quat)}}}); - set_parameter("virtual", true); #endif // VIRTUAL_SITES_RELATIVE #ifdef EXCLUSIONS } else if (name == "has_exclusion") { @@ -763,38 +775,41 @@ void ParticleHandle::do_construct(VariantMap const ¶ms) { // create a default-constructed particle make_new_particle(m_pid, pos); - context()->parallel_try_catch([&]() { - // set particle properties (filter out read-only and deferred properties) - std::set const skip = { - "pos_folded", "pos", "quat", "director", "id", "lees_edwards_flag", - "exclusions", "dip", "node", "image_box", "bonds", "__cpt_sentinel", - }; + try { + context()->parallel_try_catch([&]() { + /* clang-format off */ + // set particle properties (filter out read-only and deferred properties) + std::set const skip = { + "pos_folded", "pos", "quat", "director", "id", + "exclusions", "dip", "node", "image_box", "bonds", + "lees_edwards_flag", "__cpt_sentinel", + }; + /* clang-format on */ #ifdef ROTATION - // multiple parameters can potentially set the quaternion, but only one - // can be allowed to; these conditionals are required to handle a reload - // from a checkpoint file, where all properties exist (avoids accidentally - // overwriting the quaternion by the default-constructed dipole moment) - for (std::string name : {"quat", "director", "dip"}) { - if (has_param(name)) { - do_set_parameter(name, params.at(name)); - break; + // multiple parameters can potentially set the quaternion, but only one + // can be allowed to; these conditionals are required to handle a reload + // from a checkpoint file, where all properties exist (avoids accidentally + // overwriting the quaternion by the default-constructed dipole moment) + for (std::string name : {"quat", "director", "dip"}) { + if (has_param(name)) { + do_set_parameter(name, params.at(name)); + break; + } } - } #endif // ROTATION - std::vector sorted_param_names = {}; - std::for_each(params.begin(), params.end(), [&](auto const &kv) { - if (skip.count(kv.first) == 0) { - sorted_param_names.push_back(kv.first); + for (auto const &name : get_parameter_insertion_order()) { + if (params.count(name) != 0ul and skip.count(name) == 0ul) { + do_set_parameter(name, params.at(name)); + } + } + if (not has_param("type")) { + do_set_parameter("type", 0); } }); - std::sort(sorted_param_names.begin(), sorted_param_names.end()); - for (auto const &name : sorted_param_names) { - do_set_parameter(name, params.at(name)); - } - if (not has_param("type")) { - do_set_parameter("type", 0); - } - }); + } catch (...) { + remove_particle(m_pid); + throw; + } } } // namespace Particles diff --git a/src/script_interface/system/System.cpp b/src/script_interface/system/System.cpp index d80a9840b7d..1a8f1a45225 100644 --- a/src/script_interface/system/System.cpp +++ b/src/script_interface/system/System.cpp @@ -31,6 +31,7 @@ #include "core/nonbonded_interactions/nonbonded_interaction_data.hpp" #include "core/object-in-fluid/oif_global_forces.hpp" #include "core/particle_node.hpp" +#include "core/propagation.hpp" #include "core/rotation.hpp" #include "core/system/System.hpp" #include "core/system/System.impl.hpp" @@ -42,6 +43,7 @@ #include "script_interface/galilei/ComFixed.hpp" #include "script_interface/galilei/Galilei.hpp" #include "script_interface/integrators/IntegratorHandle.hpp" +#include "script_interface/lees_edwards/LeesEdwards.hpp" #include "script_interface/magnetostatics/Container.hpp" #include @@ -74,6 +76,7 @@ struct System::Leaves { std::shared_ptr comfixed; std::shared_ptr galilei; std::shared_ptr bond_breakage; + std::shared_ptr lees_edwards; #ifdef ELECTROSTATICS std::shared_ptr electrostatics; #endif @@ -110,7 +113,7 @@ System::System() : m_instance{}, m_leaves{std::make_shared()} { m_instance->on_boxl_change(); }); }, - []() { return ::System::get_system().box_geo->length(); }}, + [this]() { return m_instance->box_geo->length(); }}, {"periodicity", [this](Variant const &v) { auto const periodicity = get_value(v); @@ -144,6 +147,7 @@ System::System() : m_instance{}, m_leaves{std::make_shared()} { add_parameter("comfixed", &Leaves::comfixed); add_parameter("galilei", &Leaves::galilei); add_parameter("bond_breakage", &Leaves::bond_breakage); + add_parameter("lees_edwards", &Leaves::lees_edwards); #ifdef ELECTROSTATICS add_parameter("electrostatics", &Leaves::electrostatics); #endif @@ -216,6 +220,8 @@ static void rotate_system(CellStructure &cell_structure, double phi, } cell_structure.set_resort_particles(Cells::RESORT_GLOBAL); + cell_structure.update_ghosts_and_resort_particle(Cells::DATA_PART_POSITION | + Cells::DATA_PART_PROPERTIES); } /** Rescale all particle positions in direction @p dir by a factor @p scale. @@ -306,6 +312,9 @@ Variant System::do_call_method(std::string const &name, m_instance->update_dependent_particles(); return {}; } + if (name == "get_propagation_modes_enum") { + return make_unordered_map_of_variants(propagation_flags_map()); + } if (name == "session_shutdown") { if (m_instance) { if (&::System::get_system() == m_instance.get()) { diff --git a/src/script_interface/virtual_sites/ActiveVirtualSitesHandle.hpp b/src/script_interface/virtual_sites/ActiveVirtualSitesHandle.hpp deleted file mode 100644 index ce35e026b3b..00000000000 --- a/src/script_interface/virtual_sites/ActiveVirtualSitesHandle.hpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2010-2022 The ESPResSo project - * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010 - * Max-Planck-Institute for Polymer Research, Theory Group - * - * 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 . - */ - -#ifndef SCRIPT_INTERFACE_VIRTUAL_SITES_ACTIVE_VIRTUAL_SITES_HANDLE_HPP -#define SCRIPT_INTERFACE_VIRTUAL_SITES_ACTIVE_VIRTUAL_SITES_HANDLE_HPP - -#include "config/config.hpp" - -#ifdef VIRTUAL_SITES - -#include "VirtualSites.hpp" -#include "script_interface/auto_parameters/AutoParameters.hpp" - -#include "core/errorhandling.hpp" -#include "core/virtual_sites.hpp" - -#include - -namespace ScriptInterface { -namespace VirtualSites { - -class ActiveVirtualSitesHandle - : public AutoParameters { -public: - ActiveVirtualSitesHandle() { - add_parameters({{"implementation", - [this](Variant const &value) { - m_active_implementation = - get_value>(value); - ::set_virtual_sites( - m_active_implementation->virtual_sites()); - }, - [this]() { return m_active_implementation; }}}); - } - -private: - std::shared_ptr m_active_implementation; -}; -} /* namespace VirtualSites */ -} /* namespace ScriptInterface */ -#endif // VIRTUAL_SITES -#endif diff --git a/src/script_interface/virtual_sites/VirtualSites.hpp b/src/script_interface/virtual_sites/VirtualSites.hpp deleted file mode 100644 index a68368042eb..00000000000 --- a/src/script_interface/virtual_sites/VirtualSites.hpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2010-2022 The ESPResSo project - * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010 - * Max-Planck-Institute for Polymer Research, Theory Group - * - * 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 . - */ - -#ifndef SCRIPT_INTERFACE_VIRTUAL_SITES_VIRTUAL_SITES_HPP -#define SCRIPT_INTERFACE_VIRTUAL_SITES_VIRTUAL_SITES_HPP - -#include "config/config.hpp" - -#ifdef VIRTUAL_SITES - -#include "script_interface/auto_parameters/AutoParameters.hpp" - -#include "core/virtual_sites.hpp" - -#include - -namespace ScriptInterface { -namespace VirtualSites { - -class VirtualSites : public AutoParameters { -public: - VirtualSites() { - add_parameters( - {{"have_quaternion", - [this](const Variant &v) { - virtual_sites()->set_have_quaternion(get_value(v)); - }, - [this]() { return virtual_sites()->have_quaternions(); }}, - {"override_cutoff_check", - [this](const Variant &v) { - virtual_sites()->set_override_cutoff_check(get_value(v)); - }, - [this]() { return virtual_sites()->get_override_cutoff_check(); }}}); - } - /** Vs implementation we are wrapping */ - virtual std::shared_ptr<::VirtualSites> virtual_sites() = 0; -}; - -} /* namespace VirtualSites */ -} /* namespace ScriptInterface */ -#endif // VIRTUAL_SITES -#endif diff --git a/src/script_interface/virtual_sites/VirtualSitesInertialessTracers.hpp b/src/script_interface/virtual_sites/VirtualSitesInertialessTracers.hpp deleted file mode 100644 index 1fbe703c9e8..00000000000 --- a/src/script_interface/virtual_sites/VirtualSitesInertialessTracers.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2010-2022 The ESPResSo project - * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010 - * Max-Planck-Institute for Polymer Research, Theory Group - * - * 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 . - */ - -#ifndef SCRIPT_INTERFACE_VIRTUAL_SITES_VIRTUAL_SITES_INERTIALESS_TRACERS_HPP -#define SCRIPT_INTERFACE_VIRTUAL_SITES_VIRTUAL_SITES_INERTIALESS_TRACERS_HPP - -#include "config/config.hpp" - -#ifdef VIRTUAL_SITES_INERTIALESS_TRACERS - -#include "VirtualSites.hpp" - -#include "core/virtual_sites/VirtualSitesInertialessTracers.hpp" - -#include - -namespace ScriptInterface { -namespace VirtualSites { - -class VirtualSitesInertialessTracers : public VirtualSites { -public: - VirtualSitesInertialessTracers() - : m_virtual_sites(std::make_shared<::VirtualSitesInertialessTracers>()) {} - /** Vs implementation we are wrapping */ - std::shared_ptr<::VirtualSites> virtual_sites() override { - return m_virtual_sites; - } - -private: - std::shared_ptr<::VirtualSitesInertialessTracers> m_virtual_sites; -}; - -} /* namespace VirtualSites */ -} /* namespace ScriptInterface */ -#endif // VIRTUAL_SITES_INERTIALESS_TRACERS -#endif diff --git a/src/script_interface/virtual_sites/VirtualSitesRelative.hpp b/src/script_interface/virtual_sites/VirtualSitesRelative.hpp deleted file mode 100644 index 0b70e3869e5..00000000000 --- a/src/script_interface/virtual_sites/VirtualSitesRelative.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2010-2022 The ESPResSo project - * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010 - * Max-Planck-Institute for Polymer Research, Theory Group - * - * 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 . - */ - -#ifndef SCRIPT_INTERFACE_VIRTUAL_SITES_VIRTUAL_SITES_RELATIVE_HPP -#define SCRIPT_INTERFACE_VIRTUAL_SITES_VIRTUAL_SITES_RELATIVE_HPP - -#include "config/config.hpp" - -#ifdef VIRTUAL_SITES_RELATIVE - -#include "VirtualSites.hpp" - -#include "core/virtual_sites/VirtualSitesRelative.hpp" - -#include - -namespace ScriptInterface { -namespace VirtualSites { - -class VirtualSitesRelative : public VirtualSites { -public: - VirtualSitesRelative() - : m_virtual_sites(std::make_shared<::VirtualSitesRelative>()) {} - /** Vs implementation we are wrapping */ - std::shared_ptr<::VirtualSites> virtual_sites() override { - return m_virtual_sites; - } - -private: - std::shared_ptr<::VirtualSitesRelative> m_virtual_sites; -}; - -} /* namespace VirtualSites */ -} /* namespace ScriptInterface */ -#endif // VIRTUAL_SITES_RELATIVE -#endif diff --git a/src/script_interface/virtual_sites/initialize.cpp b/src/script_interface/virtual_sites/initialize.cpp deleted file mode 100644 index 7d43ec56d2b..00000000000 --- a/src/script_interface/virtual_sites/initialize.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2015-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 . - */ -#include "config/config.hpp" - -#include "initialize.hpp" - -#include "ActiveVirtualSitesHandle.hpp" -#include "VirtualSitesInertialessTracers.hpp" -#include "VirtualSitesOff.hpp" -#include "VirtualSitesRelative.hpp" - -namespace ScriptInterface { -namespace VirtualSites { - -void initialize(Utils::Factory *om) { -#ifdef VIRTUAL_SITES - om->register_new("VirtualSites::VirtualSitesOff"); -#ifdef VIRTUAL_SITES_INERTIALESS_TRACERS - om->register_new( - "VirtualSites::VirtualSitesInertialessTracers"); -#endif -#ifdef VIRTUAL_SITES_RELATIVE - om->register_new("VirtualSites::VirtualSitesRelative"); -#endif - om->register_new( - "VirtualSites::ActiveVirtualSitesHandle"); -#endif -} - -} // namespace VirtualSites -} // namespace ScriptInterface diff --git a/src/script_interface/walberla/LBFluid.cpp b/src/script_interface/walberla/LBFluid.cpp index 744ac5a8e08..44c5bbcd698 100644 --- a/src/script_interface/walberla/LBFluid.cpp +++ b/src/script_interface/walberla/LBFluid.cpp @@ -25,7 +25,6 @@ #include "WalberlaCheckpoint.hpp" #include "core/BoxGeometry.hpp" -#include "core/integrate.hpp" #include "core/lb/LBWalberla.hpp" #include "core/lees_edwards/lees_edwards.hpp" #include "core/lees_edwards/protocols.hpp" @@ -171,20 +170,21 @@ void LBFluid::do_construct(VariantMap const ¶ms) { } m_instance = new_lb_walberla(lb_lattice, lb_visc, lb_dens, single_precision); - if (auto le_protocol = LeesEdwards::get_protocol().lock()) { + auto const &system = ::System::get_system(); + if (auto le_protocol = system.lees_edwards->get_protocol()) { if (lb_kT != 0.) { throw std::runtime_error( "Lees-Edwards LB doesn't support thermalization"); } - auto const &le_bc = ::System::get_system().box_geo->lees_edwards_bc(); + auto const &le_bc = system.box_geo->lees_edwards_bc(); auto lees_edwards_object = std::make_unique( le_bc.shear_direction, le_bc.shear_plane_normal, - [this, le_protocol]() { - return get_pos_offset(get_sim_time(), *le_protocol) / + [this, le_protocol, &system]() { + return get_pos_offset(system.get_sim_time(), *le_protocol) / m_lb_params->get_agrid(); }, - [this, le_protocol]() { - return get_shear_velocity(get_sim_time(), *le_protocol) * + [this, le_protocol, &system]() { + return get_shear_velocity(system.get_sim_time(), *le_protocol) * (m_lb_params->get_tau() / m_lb_params->get_agrid()); }); m_instance->set_collision_model(std::move(lees_edwards_object)); diff --git a/testsuite/python/CMakeLists.txt b/testsuite/python/CMakeLists.txt index 58b707593d8..c89f315f0a8 100644 --- a/testsuite/python/CMakeLists.txt +++ b/testsuite/python/CMakeLists.txt @@ -227,7 +227,6 @@ python_test(FILE engine_lb.py MAX_NUM_PROC 1 GPU_SLOTS 1 SUFFIX 1_core) python_test(FILE icc.py MAX_NUM_PROC 4) python_test(FILE icc_interface.py MAX_NUM_PROC 1 GPU_SLOTS 1) python_test(FILE mass-and-rinertia_per_particle.py MAX_NUM_PROC 2 LABELS long) -python_test(FILE integrate.py MAX_NUM_PROC 4) python_test(FILE interactions_bond_angle.py MAX_NUM_PROC 4) python_test(FILE interactions_bonded_interface.py MAX_NUM_PROC 4) python_test(FILE interactions_bonded_interface.py MAX_NUM_PROC 1 SUFFIX 1_core) @@ -264,10 +263,8 @@ python_test(FILE writevtf.py MAX_NUM_PROC 4) python_test(FILE lb_pressure_tensor.py MAX_NUM_PROC 1 GPU_SLOTS 3 LABELS long) # python_test(FILE ek_fluctuations.py MAX_NUM_PROC 1) # TODO python_test(FILE exclusions.py MAX_NUM_PROC 2) -python_test(FILE langevin_thermostat.py MAX_NUM_PROC 1) -python_test(FILE langevin_thermostat_stats.py MAX_NUM_PROC 1 LABELS long) -python_test(FILE brownian_dynamics.py MAX_NUM_PROC 1) -python_test(FILE brownian_dynamics_stats.py MAX_NUM_PROC 1 LABELS long) +python_test(FILE integrator_langevin_stats.py MAX_NUM_PROC 1 LABELS long) +python_test(FILE integrator_brownian_stats.py MAX_NUM_PROC 1 LABELS long) python_test(FILE lees_edwards.py MAX_NUM_PROC 4) python_test(FILE lb_lees_edwards.py MAX_NUM_PROC 1) python_test(FILE lb_lees_edwards_particle_coupling.py MAX_NUM_PROC 1) @@ -282,7 +279,6 @@ python_test(FILE virtual_sites_tracers.py MAX_NUM_PROC 2 DEPENDENCIES python_test(FILE regular_decomposition.py MAX_NUM_PROC 4) python_test(FILE hybrid_decomposition.py MAX_NUM_PROC 1 SUFFIX 1_core) python_test(FILE hybrid_decomposition.py MAX_NUM_PROC 4) -python_test(FILE integrator_npt.py MAX_NUM_PROC 4) python_test(FILE integrator_npt_stats.py MAX_NUM_PROC 4 LABELS long) python_test(FILE integrator_steepest_descent.py MAX_NUM_PROC 4) python_test(FILE ibm.py MAX_NUM_PROC 2) @@ -338,7 +334,6 @@ python_test(FILE lb_slice.py MAX_NUM_PROC 2) python_test(FILE lb_boundary_velocity.py MAX_NUM_PROC 1) # python_test(FILE lb_boundary_volume_force.py MAX_NUM_PROC 2) # TODO python_test(FILE lb_circular_couette.py MAX_NUM_PROC 2 GPU_SLOTS 1) -python_test(FILE lb_thermo_virtual.py MAX_NUM_PROC 2 GPU_SLOTS 1) python_test(FILE lb_poiseuille.py MAX_NUM_PROC 4 GPU_SLOTS 1) python_test(FILE lb_poiseuille_cylinder.py MAX_NUM_PROC 2 GPU_SLOTS 1) python_test(FILE lb_interpolation.py MAX_NUM_PROC 4 GPU_SLOTS 1) @@ -370,8 +365,7 @@ python_test(FILE galilei.py MAX_NUM_PROC 4) python_test(FILE linear_momentum.py MAX_NUM_PROC 4) python_test(FILE linear_momentum_lb.py MAX_NUM_PROC 2 GPU_SLOTS 1) python_test(FILE mmm1d.py MAX_NUM_PROC 2 GPU_SLOTS 1) -python_test(FILE stokesian_dynamics.py MAX_NUM_PROC 2 LABELS long) -python_test(FILE stokesian_thermostat.py MAX_NUM_PROC 2) +python_test(FILE integrator_stokesian_stats.py MAX_NUM_PROC 2 LABELS long) python_test(FILE elc.py MAX_NUM_PROC 2 GPU_SLOTS 1) python_test(FILE elc_vs_analytic.py MAX_NUM_PROC 2 GPU_SLOTS 1) python_test(FILE rotation.py MAX_NUM_PROC 1) @@ -402,6 +396,12 @@ python_test(FILE ek_indexed_reactions.py MAX_NUM_PROC 1) python_test(FILE ek_fixeddensity.py MAX_NUM_PROC 1) python_test(FILE ek_boundary.py MAX_NUM_PROC 2) python_test(FILE ek_slice.py MAX_NUM_PROC 2) +python_test(FILE propagation_newton.py MAX_NUM_PROC 4) +python_test(FILE propagation_langevin.py MAX_NUM_PROC 1) +python_test(FILE propagation_brownian.py MAX_NUM_PROC 1) +python_test(FILE propagation_lb.py MAX_NUM_PROC 2 GPU_SLOTS 1) +python_test(FILE propagation_npt.py MAX_NUM_PROC 4 GPU_SLOTS 1) +python_test(FILE propagation_stokesian.py MAX_NUM_PROC 2) set(ESPRESSO_CTEST_RESOURCE_SPEC_FILE resources.json) configure_file( diff --git a/testsuite/python/analyze_chains.py b/testsuite/python/analyze_chains.py index 0fe3a7a34a0..0669485e81b 100644 --- a/testsuite/python/analyze_chains.py +++ b/testsuite/python/analyze_chains.py @@ -212,9 +212,10 @@ def test_exceptions(self): with self.assertRaisesRegex(ValueError, "needs at least 1 chain"): method(chain_start=0, number_of_chains=-1, chain_length=1) self.assertIsNone(analysis.call_method("unknown")) - if espressomd.has_features("VIRTUAL_SITES"): + if espressomd.has_features("VIRTUAL_SITES_RELATIVE"): with self.assertRaisesRegex(RuntimeError, "Center of mass is not well-defined"): - self.system.part.by_id(0).virtual = True + p = self.system.part.by_id(0) + p.vs_relative = (1, 0.01, (1., 0., 0., 0.)) analysis.calc_rg(chain_start=0, number_of_chains=num_poly, chain_length=num_mono) with self.assertRaisesRegex(RuntimeError, "Parameter 'analysis' is read-only"): diff --git a/testsuite/python/analyze_mass_related.py b/testsuite/python/analyze_mass_related.py index 6b114ad9332..daf24659fb7 100644 --- a/testsuite/python/analyze_mass_related.py +++ b/testsuite/python/analyze_mass_related.py @@ -21,6 +21,7 @@ import numpy as np import espressomd import espressomd.galilei +import espressomd.propagation class AnalyzeMassRelated(ut.TestCase): @@ -44,12 +45,13 @@ def setUpClass(cls): pos=np.random.random((10, 3)) * cls.box_l, type=[0] * 10) cls.system.part.add( pos=np.random.random((10, 3)) * cls.box_l, type=[1] * 10) - if espressomd.has_features("VIRTUAL_SITES"): + if espressomd.has_features("VIRTUAL_SITES_INERTIALESS_TRACERS"): + Propagation = espressomd.propagation.Propagation cls.system.part.add( - pos=np.random.random((10, 3)) * cls.box_l, type=[0] * 10, virtual=[True] * 10) + pos=np.random.random((10, 3)) * cls.box_l, type=[0] * 10, + propagation=[Propagation.TRANS_LB_TRACER] * 10) all_partcls = cls.system.part.all() - all_partcls.v = np.random.random( - (len(all_partcls), 3)) + 0.1 + all_partcls.v = np.random.random((len(all_partcls), 3)) + 0.1 if espressomd.has_features("MASS"): all_partcls.mass = 0.5 + \ np.random.random(len(all_partcls)) @@ -76,7 +78,7 @@ def i_tensor(self, ids): def test_itensor(self): # Particles of type 0 I0 = self.i_tensor(self.system.part.select( - lambda p: (not p.virtual) and p.type == 0).id) + lambda p: (not p.is_virtual()) and p.type == 0).id) np.testing.assert_allclose( I0, self.system.analysis.moment_of_inertia_matrix(p_type=0), atol=1E-9) # Particles of type 1 @@ -86,18 +88,17 @@ def test_itensor(self): def test_center_of_mass(self): no_virtual_type_0 = self.system.part.select( - lambda p: (not p.virtual) and p.type == 0) - com = np.zeros(3) + lambda p: (not p.is_virtual()) and p.type == 0) + com_ref = np.zeros(3) for p in no_virtual_type_0: - com += p.pos * p.mass - com /= np.sum(no_virtual_type_0.mass) + com_ref += p.pos * p.mass + com_ref /= np.sum(no_virtual_type_0.mass) + com = self.system.analysis.center_of_mass(p_type=0) - np.testing.assert_allclose( - com, - self.system.analysis.center_of_mass(p_type=0)) + np.testing.assert_allclose(com, com_ref) def test_galilei_transform(self): - no_virtual = self.system.part.select(virtual=False) + no_virtual = self.system.part.select(lambda p: not p.is_virtual()) # Center of mass np.testing.assert_allclose( @@ -110,7 +111,7 @@ def test_galilei_transform(self): def test_angularmomentum(self): no_virtual_type_0 = self.system.part.select( - lambda p: (not p.virtual) and p.type == 0) + lambda p: (not p.is_virtual()) and p.type == 0) am = np.zeros(3) for p in no_virtual_type_0: am += p.mass * np.cross(p.pos, p.v) @@ -120,14 +121,14 @@ def test_angularmomentum(self): self.system.analysis.angular_momentum(p_type=0)) def test_kinetic_energy(self): - no_virtual = self.system.part.select(virtual=False) + no_virtual = self.system.part.select(lambda p: not p.is_virtual()) E_kin = 0.5 * \ np.sum(no_virtual.mass * np.sum(no_virtual.v**2, axis=1), axis=0) np.testing.assert_allclose( E_kin, self.system.analysis.energy()["kinetic"]) def test_kinetic_pressure(self): - no_virtual = self.system.part.select(virtual=False) + no_virtual = self.system.part.select(lambda p: not p.is_virtual()) P_kin = np.sum( no_virtual.mass * np.sum(no_virtual.v**2, axis=1), axis=0) / (3 * self.system.volume()) @@ -142,7 +143,7 @@ def test_kinetic_pressure(self): expected_pressure_tensor, analyze_pressure_tensor) def test_gyration_radius(self): - if self.system.part.select(virtual=True): + if self.system.part.select(lambda p: p.is_virtual()): with self.assertRaisesRegex(RuntimeError, "not well-defined"): self.system.analysis.calc_rg(chain_start=0, number_of_chains=1, chain_length=len(self.system.part)) diff --git a/testsuite/python/collision_detection.py b/testsuite/python/collision_detection.py index 336e1debd48..1f67d676f4c 100644 --- a/testsuite/python/collision_detection.py +++ b/testsuite/python/collision_detection.py @@ -20,7 +20,7 @@ import unittest_decorators as utx import espressomd import espressomd.interactions -import espressomd.virtual_sites +import espressomd.propagation import numpy as np import random @@ -32,8 +32,6 @@ class CollisionDetection(ut.TestCase): system = espressomd.System(box_l=[1.0, 1.0, 1.0]) np.random.seed(seed=42) - if espressomd.has_features("VIRTUAL_SITES_RELATIVE"): - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() bond_center = espressomd.interactions.HarmonicBond(k=5000, r_0=0.1) bond_vs = espressomd.interactions.HarmonicBond(k=25000, r_0=0.02) @@ -142,6 +140,7 @@ def verify_state_after_bind_at_poc(self, expected_np): def verify_state_after_bind_at_poc_pair(self, expected_np): system = self.system self.assertEqual(len(system.part), expected_np) + Propagation = espressomd.propagation.Propagation # At the end of test, this list should be empty parts_not_accounted_for = list(range(expected_np)) @@ -150,7 +149,7 @@ def verify_state_after_bind_at_poc_pair(self, expected_np): # From the two vs we find the two non-virtual particles for p in system.part: # Skip non-virtual - if not p.virtual: + if not p.propagation & Propagation.TRANS_VS_RELATIVE: continue # Skip vs that doesn't have a bond if p.bonds == (): @@ -162,7 +161,7 @@ def verify_state_after_bind_at_poc_pair(self, expected_np): # get partner p2 = system.part.by_id(p.bonds[0][1]) # Is that really a vs - self.assertTrue(p2.virtual) + self.assertTrue(p2.is_virtual()) # Get base particles base_p1 = system.part.by_id(p.vs_relative[0]) base_p2 = system.part.by_id(p2.vs_relative[0]) @@ -173,7 +172,7 @@ def verify_state_after_bind_at_poc_pair(self, expected_np): # Check particle that did not take part in collision. self.assertEqual(len(parts_not_accounted_for), 1) p = system.part.by_id(parts_not_accounted_for[0]) - self.assertFalse(p.virtual) + self.assertFalse(p.propagation & Propagation.TRANS_VS_RELATIVE) self.assertEqual(p.bonds, ()) parts_not_accounted_for.remove(p.id) self.assertEqual(parts_not_accounted_for, []) @@ -189,7 +188,7 @@ def verify_state_after_bind_at_poc_triplet(self, expected_np): # From the two vs we find the two non-virtual particles for p in system.part: # Skip non-virtual - if not p.virtual: + if not p.is_virtual(): continue # Skip vs that doesn't have a bond if p.bonds == (): @@ -206,7 +205,7 @@ def verify_state_after_bind_at_poc_triplet(self, expected_np): # Bond type self.assertEqual(p.bonds[0][0], self.bond_angle_vs) # Is that really a vs - self.assertTrue(p.virtual) + self.assertTrue(p.is_virtual()) # Get base particles base_p1 = system.part.by_id(p.bonds[0][1]) base_p2 = system.part.by_id(p.bonds[0][2]) @@ -219,7 +218,7 @@ def verify_state_after_bind_at_poc_triplet(self, expected_np): # Check particle that did not take part in collision. self.assertEqual(len(parts_not_accounted_for), 1) p = system.part.by_id(parts_not_accounted_for[0]) - self.assertFalse(p.virtual) + self.assertFalse(p.is_virtual()) self.assertEqual(p.bonds, ()) parts_not_accounted_for.remove(p.id) self.assertEqual(parts_not_accounted_for, []) @@ -245,8 +244,8 @@ def verify_bind_at_poc(self, p1, p2, vs1, vs2): self.assertTrue(vs1.bonds == bond_vs1 or vs2.bonds == bond_vs2) # Vs properties - self.assertTrue(vs1.virtual) - self.assertTrue(vs2.virtual) + self.assertTrue(vs1.is_virtual()) + self.assertTrue(vs2.is_virtual()) # vs_relative properties seen = [] @@ -370,8 +369,8 @@ def test_bind_at_point_of_collision_random(self): system.integrator.run(5000) # Analysis - virtual_sites = system.part.select(virtual=True) - non_virtual = system.part.select(virtual=False) + virtual_sites = system.part.select(lambda p: p.is_virtual() == True) + non_virtual = system.part.select(lambda p: p.is_virtual() == False) # Check bonds on non-virtual particles bonds = [] @@ -462,6 +461,7 @@ def run_test_glue_to_surface_for_pos(self, *positions): def verify_state_after_glue_to_surface(self, expected_np): system = self.system self.assertEqual(len(system.part), expected_np) + Propagation = espressomd.propagation.Propagation # At the end of test, this list should be empty parts_not_accounted_for = list(range(expected_np)) @@ -470,7 +470,7 @@ def verify_state_after_glue_to_surface(self, expected_np): # and partner particle via bonds for p in system.part: # Skip non-virtual - if not p.virtual: + if not p.propagation & Propagation.TRANS_VS_RELATIVE: continue # The vs shouldn't have bonds self.assertEqual(p.bonds, ()) @@ -504,7 +504,7 @@ def verify_state_after_glue_to_surface(self, expected_np): # Check particle that did not take part in collision. self.assertEqual(len(parts_not_accounted_for), 1) p = system.part.by_id(parts_not_accounted_for[0]) - self.assertFalse(p.virtual) + self.assertFalse(p.propagation & Propagation.TRANS_VS_RELATIVE) self.assertEqual(p.type, self.other_type) self.assertEqual(p.bonds, ()) parts_not_accounted_for.remove(p.id) @@ -512,6 +512,7 @@ def verify_state_after_glue_to_surface(self, expected_np): def verify_glue_to_surface_pair(self, base_p, vs, bound_p): system = self.system + Propagation = espressomd.propagation.Propagation # Check all types self.assertEqual(base_p.type, self.part_type_to_attach_vs_to) self.assertEqual(vs.type, self.part_type_vs) @@ -530,7 +531,7 @@ def verify_glue_to_surface_pair(self, base_p, vs, bound_p): self.assertEqual(vs.bonds, ()) # Vs properties - self.assertTrue(vs.virtual) + self.assertTrue(vs.propagation & Propagation.TRANS_VS_RELATIVE) self.assertEqual(vs.vs_relative[0], base_p.id) # Distance vs,bound_p @@ -572,6 +573,7 @@ def test_glue_to_surface_random(self): """ system = self.system system.part.clear() + Propagation = espressomd.propagation.Propagation # Add randomly placed particles system.part.add(pos=np.random.random((300, 3)), @@ -607,8 +609,10 @@ def test_glue_to_surface_random(self): system.integrator.run(500) # Analysis - virtual_sites = system.part.select(virtual=True) - non_virtual = system.part.select(virtual=False) + virtual_sites = system.part.select( + lambda p: p.propagation & Propagation.TRANS_VS_RELATIVE) + non_virtual = system.part.select(lambda p: not ( + p.propagation & Propagation.TRANS_VS_RELATIVE)) after_glueing = system.part.select(type=self.part_type_after_glueing) # One virtual site per glued particle? diff --git a/testsuite/python/collision_detection_interface.py b/testsuite/python/collision_detection_interface.py index bea2b7abe9d..c035a413bc2 100644 --- a/testsuite/python/collision_detection_interface.py +++ b/testsuite/python/collision_detection_interface.py @@ -20,7 +20,6 @@ import unittest_decorators as utx import espressomd import espressomd.interactions -import espressomd.virtual_sites import numpy as np @@ -62,8 +61,6 @@ class CollisionDetection(ut.TestCase): def tearDown(self): self.system.collision_detection.set_params(mode="off") - if espressomd.has_features("VIRTUAL_SITES"): - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesOff() def test_00_interface_and_defaults(self): # Is it off by default @@ -132,7 +129,6 @@ def test_bind_centers(self): @utx.skipIfMissingFeatures("VIRTUAL_SITES_RELATIVE") def test_bind_at_point_of_collision(self): self.set_coldet("bind_at_point_of_collision", distance=0.5) - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() with self.assertRaisesRegex(ValueError, "Parameter 'vs_placement' must be between 0 and 1"): 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"): diff --git a/testsuite/python/comfixed.py b/testsuite/python/comfixed.py index be161d6f14a..764f8eed15a 100644 --- a/testsuite/python/comfixed.py +++ b/testsuite/python/comfixed.py @@ -39,7 +39,7 @@ def test(self): system.time_step = dt system.cell_system.skin = skin - system.thermostat.set_langevin(kT=1., gamma=0.001, seed=41) + system.thermostat.set_langevin(kT=1., gamma=0.01, seed=41) for i in range(100): r = [0.5, 1., 1.] * system.box_l * np.random.random(3) @@ -65,7 +65,7 @@ def test(self): for j in range(3): self.assertAlmostEqual(com_0[j], com_i[j], places=10) - system.integrator.run(10) + system.integrator.run(100) if __name__ == "__main__": diff --git a/testsuite/python/engine_lb.py b/testsuite/python/engine_lb.py index 32d948b209a..95b371cc6b9 100644 --- a/testsuite/python/engine_lb.py +++ b/testsuite/python/engine_lb.py @@ -19,7 +19,6 @@ import espressomd import espressomd.lb -import espressomd.virtual_sites import espressomd.swimmer_helpers import unittest as ut import unittest_decorators as utx @@ -111,9 +110,6 @@ def add_all_types_of_swimmers(self, fix=False, rotation=False): return [p0, p1, p2, p3], [p0_dip, p1_dip, p2_dip, p3_dip] def setUp(self): - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative( - have_quaternion=True - ) self.set_cellsystem() self.lbf = self.lb_class(**self.LB_params, **self.lb_params) self.system.lb = self.lbf diff --git a/testsuite/python/field_test.py b/testsuite/python/field_test.py index 051fe326a58..476cd356e5d 100644 --- a/testsuite/python/field_test.py +++ b/testsuite/python/field_test.py @@ -24,6 +24,7 @@ import espressomd import espressomd.constraints +import espressomd.propagation class FieldTest(ut.TestCase): @@ -53,10 +54,8 @@ def test_gravity(self): self.system.constraints.add(gravity) - if espressomd.has_features("MASS"): - p = self.system.part.add(pos=[0, 0, 0], mass=3.1) - else: - p = self.system.part.add(pos=[0, 0, 0]) + mass = 3.1 if espressomd.has_features("MASS") else 1. + p = self.system.part.add(pos=[0, 0, 0], mass=mass) self.system.integrator.run(0) @@ -64,8 +63,8 @@ def test_gravity(self): self.assertAlmostEqual(self.system.analysis.energy()['total'], 0.) # Virtual sites don't feel gravity - if espressomd.has_features("VIRTUAL_SITES"): - p.virtual = True + if espressomd.has_features("VIRTUAL_SITES_INERTIALESS_TRACERS"): + p.propagation = espressomd.propagation.Propagation.TRANS_LB_TRACER self.system.integrator.run(0) np.testing.assert_allclose(np.copy(p.f), 0) diff --git a/testsuite/python/ibm.py b/testsuite/python/ibm.py index ed4bfd801f7..5c1b5ebaec2 100644 --- a/testsuite/python/ibm.py +++ b/testsuite/python/ibm.py @@ -19,14 +19,11 @@ import numpy as np import itertools import unittest as ut -import unittest_decorators as utx import espressomd import espressomd.interactions -import espressomd.virtual_sites -@utx.skipIfMissingFeatures(['VIRTUAL_SITES_INERTIALESS_TRACERS']) class IBM(ut.TestCase): '''Test IBM implementation with a Langevin thermostat.''' system = espressomd.System(box_l=3 * [8.]) @@ -56,7 +53,6 @@ def test_tribend(self): # move nodes, should relax back system = self.system - system.virtual_sites = espressomd.virtual_sites.VirtualSitesInertialessTracers() system.thermostat.set_langevin(kT=0, gamma=10, seed=1) # Add four particles @@ -99,7 +95,6 @@ def test_tribend(self): def test_triel(self): system = self.system - system.virtual_sites = espressomd.virtual_sites.VirtualSitesInertialessTracers() system.thermostat.set_langevin(kT=0, gamma=1, seed=1) # Add particles: 0-2 are not bonded, 3-5 are bonded @@ -137,7 +132,6 @@ def test_triel(self): def test_volcons(self): '''Check volume conservation forces on a simple mesh (cube).''' system = self.system - system.virtual_sites = espressomd.virtual_sites.VirtualSitesOff() system.thermostat.set_langevin(kT=0, gamma=1, seed=1) # Place particles on a cube. diff --git a/testsuite/python/integrate.py b/testsuite/python/integrate.py deleted file mode 100644 index 0cbd14e0bac..00000000000 --- a/testsuite/python/integrate.py +++ /dev/null @@ -1,74 +0,0 @@ -# -# Copyright (C) 2013-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 . -# -import espressomd -import numpy as np -import unittest as ut - - -class Integrate(ut.TestCase): - - """ - Tests integration of Newton's equations for a particle with and without - external forces and with time step changes on the way. - - """ - - def test(self): - system = espressomd.System(box_l=[10.0, 10.0, 10.0]) - system.cell_system.skin = 0 - - # Newton's 1st law with time step change on the way - p = system.part.add(pos=(0, 0, 0), v=(1, 2, 3)) - system.time_step = 0.01 - system.time = 12. - np.testing.assert_allclose(np.copy(p.v), (1, 2, 3)) - for i in range(10): - np.testing.assert_allclose(np.copy(p.pos), np.copy( - i * system.time_step * p.v), atol=1E-12) - system.integrator.run(1) - - # Check that the time has passed - np.testing.assert_allclose(system.time, 12. + 10 * system.time_step) - - v = p.v - pos1 = p.pos - system.time_step = 0.02 - np.testing.assert_allclose(np.copy(v), np.copy(p.v), atol=1E-12) - np.testing.assert_allclose(np.copy(pos1), np.copy(p.pos), atol=1E-12) - for i in range(10): - np.testing.assert_allclose(np.copy(p.pos), np.copy( - pos1 + i * system.time_step * p.v), atol=1E-12) - system.integrator.run(1) - - # Newton's 2nd law - if espressomd.has_features("EXTERNAL_FORCES"): - if espressomd.has_features("MASS"): - p.mass = 2.3 - p.pos = (0, 0, 0) - ext_force = np.array((-2, 1.3, 1)) - p.ext_force = ext_force - system.time_step = 0.03 - for i in range(10): - np.testing.assert_allclose(np.copy(p.pos), np.copy( - 0.5 * ext_force / p.mass * (i * system.time_step)**2 + v * i * system.time_step), atol=1E-12) - system.integrator.run(1) - - -if __name__ == '__main__': - ut.main() diff --git a/testsuite/python/brownian_dynamics_stats.py b/testsuite/python/integrator_brownian_stats.py similarity index 100% rename from testsuite/python/brownian_dynamics_stats.py rename to testsuite/python/integrator_brownian_stats.py diff --git a/testsuite/python/integrator_exceptions.py b/testsuite/python/integrator_exceptions.py index b5b5d8efa74..f02d6e06c19 100644 --- a/testsuite/python/integrator_exceptions.py +++ b/testsuite/python/integrator_exceptions.py @@ -20,6 +20,7 @@ import espressomd.interactions import espressomd.lees_edwards import espressomd.shapes +import espressomd.propagation import unittest as ut import unittest_decorators as utx @@ -66,6 +67,13 @@ def test_00_common_interface(self): with self.assertRaisesRegex(Exception, 'Constraint violated by particle 0'): self.system.analysis.energy() wca.set_params(epsilon=0., sigma=0.) + if espressomd.has_features(["ROTATION"]): + with self.assertRaisesRegex(Exception, "Rotating particles must have a rotation propagation mode enabled"): + Propagation = espressomd.propagation.Propagation + p = self.system.part.by_id(0) + p.propagation = Propagation.TRANS_LANGEVIN + p.rotation = [False, False, True] + self.system.integrator.run(0, recalc_forces=True) def test_vv_integrator(self): self.system.cell_system.skin = 0.4 diff --git a/testsuite/python/langevin_thermostat_stats.py b/testsuite/python/integrator_langevin_stats.py similarity index 81% rename from testsuite/python/langevin_thermostat_stats.py rename to testsuite/python/integrator_langevin_stats.py index 6b4f5cf6be9..882be1f7092 100644 --- a/testsuite/python/langevin_thermostat_stats.py +++ b/testsuite/python/integrator_langevin_stats.py @@ -117,6 +117,10 @@ def verify_diffusion(self, p, corr, kT, gamma): acf = c.result() tau = c.lag_times() + gamma = np.copy(gamma) + if not gamma.shape: + gamma = gamma * np.ones(3) + # Integrate with trapezoidal rule for i in range(3): I = np.trapz(acf[:, p.id, i], tau) @@ -130,21 +134,20 @@ def test_06__diffusion(self): kT = 1.37 dt = 0.1 system.time_step = dt + aniso = espressomd.has_features("PARTICLE_ANISOTROPY") # Translational gamma. We cannot test per-component, if rotation is on, # because body and space frames become different. gamma = 3.1 # Rotational gamma - gamma_rot_i = 4.7 - gamma_rot_a = [4.2, 1, 1.2] + gamma_rot = [4.2, 1, 1.2] if aniso else 4.7 # If we have langevin per particle: # Translation per_part_gamma = 1.63 # Rotational - per_part_gamma_rot_i = 2.6 - per_part_gamma_rot_a = [2.4, 3.8, 1.1] + per_part_gamma_rot = [2.4, 3.8, 1.1] if aniso else 2.6 # Particle with global thermostat params p_global = system.part.add(pos=(0, 0, 0)) @@ -155,25 +158,14 @@ def test_06__diffusion(self): if espressomd.has_features("THERMOSTAT_PER_PARTICLE"): p_gamma = system.part.add(pos=(0, 0, 0)) self.setup_diff_mass_rinertia(p_gamma) - if espressomd.has_features("PARTICLE_ANISOTROPY"): - p_gamma.gamma = 3 * [per_part_gamma] - if espressomd.has_features("ROTATION"): - p_gamma.gamma_rot = per_part_gamma_rot_a - else: - p_gamma.gamma = per_part_gamma - if espressomd.has_features("ROTATION"): - p_gamma.gamma_rot = per_part_gamma_rot_i + p_gamma.gamma = 3 * [per_part_gamma] if aniso else per_part_gamma + if espressomd.has_features("ROTATION"): + p_gamma.gamma_rot = per_part_gamma_rot # Thermostat setup if espressomd.has_features("ROTATION"): - if espressomd.has_features("PARTICLE_ANISOTROPY"): - # particle anisotropy and rotation - system.thermostat.set_langevin( - kT=kT, gamma=gamma, gamma_rotation=gamma_rot_a, seed=41) - else: - # Rotation without particle anisotropy - system.thermostat.set_langevin( - kT=kT, gamma=gamma, gamma_rotation=gamma_rot_i, seed=41) + system.thermostat.set_langevin( + kT=kT, gamma=gamma, gamma_rotation=gamma_rot, seed=41) else: # No rotation system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=41) @@ -215,31 +207,15 @@ def test_06__diffusion(self): corr_omega.finalize() # Verify diffusion - # Translation - # Cast gammas to vector, to make checks independent of - # PARTICLE_ANISOTROPY - gamma = np.ones(3) * gamma - per_part_gamma = np.ones(3) * per_part_gamma self.verify_diffusion(p_global, corr_vel, kT, gamma) if espressomd.has_features("THERMOSTAT_PER_PARTICLE"): self.verify_diffusion(p_gamma, corr_vel, kT, per_part_gamma) - # Rotation if espressomd.has_features("ROTATION"): - # Decide on effective gamma rotation, since for rotation it is - # direction dependent - eff_gamma_rot = None - if espressomd.has_features("PARTICLE_ANISOTROPY"): - eff_gamma_rot = gamma_rot_a - eff_per_part_gamma_rot = per_part_gamma_rot_a - else: - eff_gamma_rot = gamma_rot_i * np.ones(3) - eff_per_part_gamma_rot = per_part_gamma_rot_i * np.ones(3) - - self.verify_diffusion(p_global, corr_omega, kT, eff_gamma_rot) + self.verify_diffusion(p_global, corr_omega, kT, gamma_rot) if espressomd.has_features("THERMOSTAT_PER_PARTICLE"): self.verify_diffusion( - p_gamma, corr_omega, kT, eff_per_part_gamma_rot) + p_gamma, corr_omega, kT, per_part_gamma_rot) def test_08__noise_correlation(self): """Checks that the Langevin noise is uncorrelated""" diff --git a/testsuite/python/stokesian_dynamics.py b/testsuite/python/integrator_stokesian_stats.py similarity index 100% rename from testsuite/python/stokesian_dynamics.py rename to testsuite/python/integrator_stokesian_stats.py diff --git a/testsuite/python/langevin_thermostat.py b/testsuite/python/langevin_thermostat.py deleted file mode 100644 index 1d701e0eef6..00000000000 --- a/testsuite/python/langevin_thermostat.py +++ /dev/null @@ -1,234 +0,0 @@ -# -# Copyright (C) 2013-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 . -# -import unittest as ut -import unittest_decorators as utx -import espressomd -import numpy as np - - -class LangevinThermostat(ut.TestCase): - - """Test Langevin Dynamics""" - system = espressomd.System(box_l=[1.0, 1.0, 1.0]) - system.cell_system.set_regular_decomposition(use_verlet_lists=True) - system.cell_system.skin = 0 - system.periodicity = [False, False, False] - - def setUp(self): - np.random.seed(42) - self.system.time_step = 1e-12 - self.system.cell_system.skin = 0.0 - self.system.integrator.set_vv() - - def tearDown(self): - self.system.part.clear() - self.system.thermostat.turn_off() - - def check_rng(self, per_particle_gamma=False): - """Test for RNG consistency.""" - - kT = 1.1 - gamma = 3.5 - - def reset_particle(): - self.system.part.clear() - p = system.part.add(pos=[0, 0, 0]) - if espressomd.has_features("ROTATION"): - p.rotation = [True, True, True] - if per_particle_gamma: - assert espressomd.has_features("THERMOSTAT_PER_PARTICLE") - if espressomd.has_features("PARTICLE_ANISOTROPY"): - p.gamma = 3 * [gamma / 2] - else: - p.gamma = gamma / 2 - if espressomd.has_features("ROTATION"): - p.gamma_rot = p.gamma * 1.5 - return p - - system = self.system - system.time_step = 0.01 - - system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=41) - system.integrator.set_vv() - - # run(0) does not increase the philox counter and should give the same - # force - p = reset_particle() - system.integrator.run(0, recalc_forces=True) - force0 = np.copy(p.f) - if espressomd.has_features("ROTATION"): - torque0 = np.copy(p.torque_lab) - system.integrator.run(0, recalc_forces=True) - force1 = np.copy(p.f) - np.testing.assert_almost_equal(force0, force1) - if espressomd.has_features("ROTATION"): - torque1 = np.copy(p.torque_lab) - np.testing.assert_almost_equal(torque0, torque1) - - # run(1) should give a different force - p = reset_particle() - system.integrator.run(1) - force2 = np.copy(p.f) - self.assertTrue(np.all(np.not_equal(force1, force2))) - if espressomd.has_features("ROTATION"): - torque2 = np.copy(p.torque_lab) - self.assertTrue(np.all(np.not_equal(torque1, torque2))) - - # Different seed should give a different force with same counter state - # force2: langevin.rng_counter() = 1, langevin.rng_seed() = 41 - # force3: langevin.rng_counter() = 1, langevin.rng_seed() = 42 - p = reset_particle() - system.integrator.run(0, recalc_forces=True) - force2 = np.copy(p.f) - if espressomd.has_features("ROTATION"): - torque2 = np.copy(p.torque_lab) - system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=42) - system.integrator.run(0, recalc_forces=True) - force3 = np.copy(p.f) - self.assertTrue(np.all(np.not_equal(force2, force3))) - if espressomd.has_features("ROTATION"): - torque3 = np.copy(p.torque_lab) - self.assertTrue(np.all(np.not_equal(torque2, torque3))) - - # Same seed should not give the same force with different counter state - p = reset_particle() - system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=42) - system.integrator.run(1) - force4 = np.copy(p.f) - self.assertTrue(np.all(np.not_equal(force3, force4))) - if espressomd.has_features("ROTATION"): - torque4 = np.copy(p.torque_lab) - self.assertTrue(np.all(np.not_equal(torque3, torque4))) - - # Seed offset should not give the same force with a lag - # force4: langevin.rng_counter() = 2, langevin.rng_seed() = 42 - # force5: langevin.rng_counter() = 3, langevin.rng_seed() = 41 - reset_particle() - system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=41) - system.integrator.run(1) - p = reset_particle() - system.integrator.run(0, recalc_forces=True) - force5 = np.copy(p.f) - self.assertTrue(np.all(np.not_equal(force4, force5))) - if espressomd.has_features("ROTATION"): - torque5 = np.copy(p.torque_lab) - self.assertTrue(np.all(np.not_equal(torque4, torque5))) - - def test_01__rng(self): - """Test for RNG consistency.""" - # No seed should throw exception - with self.assertRaises(ValueError): - self.system.thermostat.set_langevin(kT=1, gamma=2) - self.check_rng() - - @utx.skipIfMissingFeatures("THERMOSTAT_PER_PARTICLE") - def test_01__rng_per_particle(self): - """Test for RNG consistency.""" - self.check_rng(True) - - def test_02__friction_trans(self): - """Tests the translational friction-only part of the thermostat.""" - - system = self.system - # Translation - gamma_t_i = 2 - gamma_t_a = np.array((0.5, 2, 1.5)) - v0 = np.array((5., 5., 5.)) - - system.time_step = 0.0005 - p = system.part.add(pos=(0, 0, 0), v=v0) - if espressomd.has_features("MASS"): - p.mass = 3 - if espressomd.has_features("PARTICLE_ANISOTROPY"): - system.thermostat.set_langevin(kT=0, gamma=gamma_t_a, seed=41) - else: - system.thermostat.set_langevin(kT=0, gamma=gamma_t_i, seed=41) - - system.time = 0 - for _ in range(100): - system.integrator.run(10) - if espressomd.has_features("PARTICLE_ANISOTROPY"): - np.testing.assert_allclose( - np.copy(p.v), - v0 * np.exp(-gamma_t_a / p.mass * system.time), - atol=4E-4) - else: - np.testing.assert_allclose( - np.copy(p.v), - v0 * np.exp(-gamma_t_i / p.mass * system.time), - atol=45E-4) - - @utx.skipIfMissingFeatures("ROTATION") - def test_03__friction_rot(self): - """Tests the rotational friction-only part of the thermostat.""" - - system = self.system - # Translation - if espressomd.has_features("PARTICLE_ANISOTROPY"): - gamma_t = [0.5, 2, 1.5] - gamma_r = np.array((1.5, 0.7, 1.2)) - else: - gamma_t = 2 - gamma_r = 3 - o0 = np.array((5., 5., 5.)) - - system.time = 0 - system.time_step = 0.0001 - p = system.part.add(pos=(0, 0, 0), omega_body=o0, rotation=3 * [True]) - if espressomd.has_features("ROTATIONAL_INERTIA"): - rinertia = np.array((2, 2, 2)) - p.rinertia = rinertia - else: - rinertia = np.array((1, 1, 1)) - system.thermostat.set_langevin( - kT=0, gamma=gamma_t, gamma_rotation=gamma_r, seed=41) - - system.time = 0 - for _ in range(100): - system.integrator.run(10) - ref_omega_body = o0 * np.exp(-gamma_r / rinertia * system.time) - np.testing.assert_allclose( - np.copy(p.omega_body), ref_omega_body, atol=5E-4) - - @utx.skipIfMissingFeatures("VIRTUAL_SITES") - def test_07__virtual(self): - system = self.system - system.time_step = 0.01 - - virtual = system.part.add(pos=[0, 0, 0], virtual=True, v=[1, 0, 0]) - physical = system.part.add(pos=[0, 0, 0], virtual=False, v=[1, 0, 0]) - - system.thermostat.set_langevin( - kT=0, gamma=1, gamma_rotation=1., act_on_virtual=False, seed=41) - - system.integrator.run(0) - - np.testing.assert_almost_equal(np.copy(virtual.f), [0, 0, 0]) - np.testing.assert_almost_equal(np.copy(physical.f), [-1, 0, 0]) - - system.thermostat.set_langevin( - kT=0, gamma=1, gamma_rotation=1., act_on_virtual=True, seed=41) - system.integrator.run(0) - - np.testing.assert_almost_equal(np.copy(virtual.f), [-1, 0, 0]) - np.testing.assert_almost_equal(np.copy(physical.f), [-1, 0, 0]) - - -if __name__ == "__main__": - ut.main() diff --git a/testsuite/python/lb_planar_couette.py b/testsuite/python/lb_planar_couette.py index dfdabd4eb72..bb72e47b73c 100644 --- a/testsuite/python/lb_planar_couette.py +++ b/testsuite/python/lb_planar_couette.py @@ -70,7 +70,7 @@ def setUp(self): def tearDown(self): self.system.lb = None - self.system.lees_edwards = espressomd.lees_edwards.LeesEdwards() + self.system.lees_edwards.protocol = None def check_profile(self, u_getter, **kwargs): system = self.system diff --git a/testsuite/python/lb_thermo_virtual.py b/testsuite/python/lb_thermo_virtual.py deleted file mode 100644 index ab9875f4df4..00000000000 --- a/testsuite/python/lb_thermo_virtual.py +++ /dev/null @@ -1,91 +0,0 @@ -# -# Copyright (C) 2010-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 . -# - -import espressomd.lb -import espressomd.shapes -import unittest as ut -import unittest_decorators as utx -import numpy as np - - -@utx.skipIfMissingFeatures(["VIRTUAL_SITES"]) -class LBBoundaryThermoVirtualTest(ut.TestCase): - - """Test slip velocity of boundaries. - - In this simple test, a wall with slip velocity is - added and the fluid is checked if it has the same velocity. - """ - - system = espressomd.System(box_l=[10.0, 10.0, 10.0]) - - system.time_step = 1.0 - system.cell_system.skin = 0.1 - - def tearDown(self): - self.system.lb = None - self.system.part.clear() - - def check_virtual(self, fluid_class): - system = self.system - lb_fluid = fluid_class( - agrid=1.0, density=1.0, kinematic_viscosity=1.0, tau=1.0) - system.lb = lb_fluid - - virtual = system.part.add(pos=[0, 0, 0], virtual=True, v=[1, 0, 0]) - physical = system.part.add(pos=[0, 0, 0], virtual=False, v=[1, 0, 0]) - - system.thermostat.set_lb( - LB_fluid=lb_fluid, - act_on_virtual=False, - gamma=1.0) - - system.integrator.run(1) - - np.testing.assert_almost_equal(np.copy(virtual.f), [0, 0, 0]) - np.testing.assert_almost_equal(np.copy(physical.f), [-1, 0, 0]) - - system.thermostat.set_lb(LB_fluid=lb_fluid, act_on_virtual=True) - - virtual.v = [1, 0, 0] - physical.v = [1, 0, 0] - - system.lb = None - lb_fluid = fluid_class( - agrid=1.0, density=1.0, kinematic_viscosity=1.0, tau=1.0) - system.lb = lb_fluid - system.thermostat.set_lb(LB_fluid=lb_fluid, gamma=1.0) - virtual.pos = physical.pos - virtual.v = [1, 0, 0] - physical.v = [1, 0, 0] - system.integrator.run(1) - - # The forces are not exactly -1 because the fluid is not at - # rest anymore because of the previous check. - np.testing.assert_almost_equal(np.copy(physical.f), np.copy(virtual.f)) - np.testing.assert_almost_equal(np.copy(physical.f), [-1, 0, 0]) - np.testing.assert_almost_equal(np.copy(virtual.f), [-1, 0, 0]) - - @utx.skipIfMissingFeatures(["WALBERLA"]) - def test_lb_walberla(self): - self.check_virtual(espressomd.lb.LBFluidWalberla) - - -if __name__ == "__main__": - ut.main() diff --git a/testsuite/python/lees_edwards.py b/testsuite/python/lees_edwards.py index 114afb8a4cf..ee90f514398 100644 --- a/testsuite/python/lees_edwards.py +++ b/testsuite/python/lees_edwards.py @@ -20,7 +20,6 @@ import espressomd.bond_breakage import espressomd.interactions import espressomd.lees_edwards -import espressomd.virtual_sites import unittest as ut import unittest_decorators as utx @@ -70,8 +69,6 @@ def tearDown(self): system = self.system system.part.clear() system.lees_edwards.protocol = None - if espressomd.has_features("VIRTUAL_SITES"): - system.virtual_sites = espressomd.virtual_sites.VirtualSitesOff() if espressomd.has_features("COLLISION_DETECTION"): system.collision_detection.set_params(mode="off") @@ -421,7 +418,6 @@ def test_virt_sites(self): """ system = self.system system.min_global_cut = 2.5 - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() tol = 1e-10 # Construct pair of VS across normal boundary @@ -479,7 +475,6 @@ def test_virt_sites_interaction(self): """ system = self.system - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() system.thermostat.set_dpd(kT=0.0, seed=1) system.non_bonded_inter[11, 11].dpd.set_params( @@ -621,7 +616,7 @@ def test_le_colldet(self): # generated VS. The other components are inherited from the real # particles. box_l = np.copy(system.box_l) - p_vs = system.part.select(virtual=True) + p_vs = system.part.select(lambda p: p.is_virtual()) np.testing.assert_array_almost_equal( np.minimum(np.abs(p_vs.pos[:, 0] - col_part1.pos[0]), np.abs(p_vs.pos[:, 0] - col_part2.pos[0])), 0.) @@ -634,7 +629,6 @@ def test_le_colldet(self): def test_le_breaking_bonds(self): system = self.system system.min_global_cut = 1.2 - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() protocol = espressomd.lees_edwards.LinearShear( shear_velocity=-1.0, initial_pos_offset=0.0) system.lees_edwards.set_boundary_conditions( diff --git a/testsuite/python/npt_thermostat.py b/testsuite/python/npt_thermostat.py index 70bdd088c1e..6a3efebc154 100644 --- a/testsuite/python/npt_thermostat.py +++ b/testsuite/python/npt_thermostat.py @@ -19,6 +19,7 @@ import unittest as ut import unittest_decorators as utx import espressomd +import espressomd.propagation import tests_common import numpy as np @@ -140,12 +141,13 @@ def test_02__direction(self): np.testing.assert_allclose(box_l_rel, box_l_rel_ref, atol=1e-10) self.assertGreater(np.max(box_l_rel), 2) - @utx.skipIfMissingFeatures("VIRTUAL_SITES") def test_07__virtual(self): system = self.system - virtual = system.part.add(pos=[0, 0, 0], virtual=True, v=[1, 0, 0]) - physical = system.part.add(pos=[0, 0, 0], virtual=False, v=[1, 0, 0]) + Propagation = espressomd.propagation.Propagation + virtual = system.part.add(pos=[0, 0, 0], v=[1, 0, 0], + propagation=Propagation.NONE) + physical = system.part.add(pos=[0, 0, 0], v=[1, 0, 0]) system.thermostat.set_npt(kT=1.0, gamma0=2.0, gammav=0.04, seed=42) system.integrator.set_isotropic_npt(ext_pressure=2.0, piston=0.01) diff --git a/testsuite/python/observables.py b/testsuite/python/observables.py index eef1a9e8b6d..a0d637529c4 100644 --- a/testsuite/python/observables.py +++ b/testsuite/python/observables.py @@ -15,23 +15,25 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# import unittest as ut import unittest_decorators as utx import espressomd import numpy as np import espressomd.observables +import espressomd.propagation -def calc_com_x(system, x, id_list): +def calc_com_x(system, attr, id_list): """Mass-weighted average, skipping virtual sites""" partcls = system.part.by_ids(id_list) masses = partcls.mass # Filter out virtual particles by using mass=0 for them - virtual = partcls.virtual + virtual = [p.is_virtual() for p in partcls] masses[np.nonzero(virtual)] = 0. - return np.average(getattr(partcls, x), weights=masses, axis=0) + return np.average(getattr(partcls, attr), weights=masses, axis=0) class Observables(ut.TestCase): @@ -66,9 +68,9 @@ class Observables(ut.TestCase): if espressomd.has_features("ELECTROSTATICS"): partcls.q = np.random.random(N_PART) - if espressomd.has_features("VIRTUAL_SITES"): + if espressomd.has_features("VIRTUAL_SITES_INERTIALESS_TRACERS"): p = system.part.by_id(partcls.id[8]) - p.virtual = True + p.propagation = espressomd.propagation.Propagation.TRANS_LB_TRACER def generate_test_for_pid_observable( _obs_class, _pprop_name, _agg_type=None): @@ -96,7 +98,7 @@ def func(self): # Get data from particles if pprop_name == "f": for p_id in id_list: - if self.system.part.by_id(p_id).virtual: + if self.system.part.by_id(p_id).is_virtual(): id_list.remove(p_id) part_data = getattr(self.system.part.by_ids(id_list), pprop_name) @@ -229,12 +231,11 @@ def test_com_force(self): id_list = sorted( np.random.choice( self.partcls.id, - size=int( - self.N_PART * .9), + size=int(self.N_PART * .9), replace=False)) particles = self.system.part.select( - lambda p: p.id in id_list and not p.virtual) + lambda p: p.id in id_list and not p.is_virtual()) np.testing.assert_allclose( np.sum(particles.f, axis=0), diff --git a/testsuite/python/particle.py b/testsuite/python/particle.py index 9abe3e395af..d887bc9814a 100644 --- a/testsuite/python/particle.py +++ b/testsuite/python/particle.py @@ -21,6 +21,7 @@ import espressomd import espressomd.interactions import espressomd.particle_data +import espressomd.propagation import numpy as np import collections import itertools @@ -99,6 +100,20 @@ def func(self): test_f = generateTestForVectorProperty("f", np.array([0.2, 0.3, 0.7])) test_type = generateTestForScalarProperty("type", int(3)) test_mol_id = generateTestForScalarProperty("mol_id", int(3)) + test_propagation = generateTestForScalarProperty( + "propagation", int(espressomd.propagation.Propagation.TRANS_LANGEVIN)) + + def test_invalid_propagation(self): + Propagation = espressomd.propagation.Propagation + with self.assertRaisesRegex(ValueError, "propagation combination not accepted"): + self.partcl.propagation = ( + Propagation.TRANS_LANGEVIN | Propagation.TRANS_LANGEVIN_NPT | Propagation.TRANS_LB_MOMENTUM_EXCHANGE) + + def test_propagation_enum(self): + Propagation = espressomd.propagation.Propagation + flags_core = self.system.call_method("get_propagation_modes_enum") + flags_si = {e.name: e.value for e in Propagation} + self.assertEqual(flags_si, flags_core) test_bonds_property = generateTestForScalarProperty( "bonds", ((f1, 1), (f2, 2))) @@ -179,15 +194,14 @@ def test_gamma_rot_single(self): "dip", np.array([0.5, -0.5, 3])) test_dipm = generateTestForScalarProperty("dipm", -9.7) - if espressomd.has_features(["VIRTUAL_SITES"]): - test_virtual = generateTestForScalarProperty("virtual", True) - @utx.skipIfMissingFeatures(["VIRTUAL_SITES_RELATIVE"]) def test_vs_relative(self): self.system.part.add(id=0, pos=(0, 0, 0)) p1 = self.system.part.add(id=1, pos=(0, 0, 0)) + self.assertFalse(p1.is_virtual()) p1.vs_relative = (0, 5.0, (0.5, -0.5, -0.5, -0.5)) p1.vs_quat = [1, 2, 3, 4] + self.assertTrue(p1.is_virtual()) np.testing.assert_array_equal(p1.vs_quat, [1, 2, 3, 4]) res = p1.vs_relative self.assertEqual(res[0], 0, f"vs_relative: {res}") @@ -388,7 +402,6 @@ def check(feature, prop, throwing_values, valid_value=None): check("MASS", "mass", [1.1, 0., -1.], 1.) check("ELECTROSTATICS", "q", [1., -1.], 0.) - check("VIRTUAL_SITES", "virtual", [True], False) def test_parallel_property_setters(self): system = self.system diff --git a/testsuite/python/brownian_dynamics.py b/testsuite/python/propagation_brownian.py similarity index 56% rename from testsuite/python/brownian_dynamics.py rename to testsuite/python/propagation_brownian.py index 6c87186eff5..1c2620ea69d 100644 --- a/testsuite/python/brownian_dynamics.py +++ b/testsuite/python/propagation_brownian.py @@ -19,6 +19,8 @@ import unittest as ut import unittest_decorators as utx import espressomd +import espressomd.propagation +import itertools import numpy as np @@ -127,16 +129,17 @@ def test_01__rng_per_particle(self): """Test for RNG consistency.""" self.check_rng(True) - @utx.skipIfMissingFeatures("VIRTUAL_SITES") def test_07__virtual(self): system = self.system system.time_step = 0.01 + Propagation = espressomd.propagation.Propagation - virtual = system.part.add(pos=[0, 0, 0], virtual=True, v=[1, 0, 0]) - physical = system.part.add(pos=[0, 0, 0], virtual=False, v=[1, 0, 0]) + virtual = system.part.add(pos=[0, 0, 0], v=[1, 0, 0], + propagation=Propagation.NONE) + physical = system.part.add(pos=[0, 0, 0], v=[1, 0, 0]) system.thermostat.set_brownian( - kT=0, gamma=1, gamma_rotation=1., act_on_virtual=False, seed=41) + kT=0, gamma=1, gamma_rotation=1., seed=41) system.integrator.set_brownian_dynamics() system.integrator.run(1) @@ -144,14 +147,6 @@ def test_07__virtual(self): np.testing.assert_almost_equal(np.copy(virtual.v), [1, 0, 0]) np.testing.assert_almost_equal(np.copy(physical.v), [0, 0, 0]) - virtual.pos = physical.pos = [0, 0, 0] - system.thermostat.set_brownian( - kT=0, gamma=1, gamma_rotation=1., act_on_virtual=True, seed=41) - system.integrator.run(1) - - np.testing.assert_almost_equal(np.copy(virtual.v), [0, 0, 0]) - np.testing.assert_almost_equal(np.copy(physical.v), [0, 0, 0]) - @utx.skipIfMissingFeatures(["ROTATION", "EXTERNAL_FORCES"]) def test_fix_rotation(self): system = self.system @@ -164,20 +159,114 @@ def test_fix_rotation(self): # torque only part.ext_torque = [0, 0, 1.3] system.thermostat.set_brownian( - kT=0, gamma=1, gamma_rotation=1.5, act_on_virtual=False, seed=41) + kT=0, gamma=1, gamma_rotation=1.5, seed=41) system.integrator.set_brownian_dynamics() system.integrator.run(3) np.testing.assert_allclose( - part.omega_lab, [ - 0, 0, 1.3 / 1.5], atol=1e-14) + part.omega_lab, [0, 0, 1.3 / 1.5], atol=1e-14) # noise only part.ext_torque = 3 * [0.] system.thermostat.set_brownian( - kT=1, gamma=1, gamma_rotation=1.5, act_on_virtual=False, seed=41) + kT=1, gamma=1, gamma_rotation=1.5, seed=41) system.integrator.run(3) self.assertGreater(np.linalg.norm(part.omega_lab), 0.) + @utx.skipIfMissingFeatures(["MASS", + "ROTATIONAL_INERTIA", + "EXTERNAL_FORCES"]) + def test_propagation(self): + """ + Check integration of Brownian's equations of motion and rotation + for various combinations of propagation modes. + """ + Propagation = espressomd.propagation.Propagation + aniso = espressomd.has_features("PARTICLE_ANISOTROPY") + gamma_trans = np.array([1.2, 1.2, 1.2]) if aniso else 1.2 + gamma_rot = np.array([1.5, 1.5, 1.5]) if aniso else 1.5 + x0 = np.array([0., 1., 2.]) + v0 = np.array([1., 2., 3.]) + o0 = np.array([2., 3., 4.]) + ext_force = np.array([-1., +2., -4.]) + ext_torque = np.array([+1., -3., +5.]) + + def has_gamma(gamma): + return gamma[0] > 0. if np.shape(gamma) else gamma > 0. + + def calc_trajectory(p): + t = self.system.time + gamma_t = gamma_trans + gamma_r = gamma_rot + if espressomd.has_features("THERMOSTAT_PER_PARTICLE") and \ + p.propagation & Propagation.SYSTEM_DEFAULT: + if has_gamma(p.gamma): + gamma_t = p.gamma + if has_gamma(p.gamma_rot): + gamma_r = p.gamma_rot + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.TRANS_BROWNIAN)): + ref_vel = p.ext_force / gamma_t + ref_pos = x0 + p.ext_force / gamma_t * t + elif p.propagation & Propagation.TRANS_NEWTON: + ref_vel = v0 + p.ext_force / p.mass * t + ref_pos = x0 + v0 * t + 0.5 * p.ext_force / p.mass * t**2 + else: + ref_vel = v0 + ref_pos = x0 + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.ROT_BROWNIAN)): + ref_rot = p.ext_torque / gamma_r + elif p.propagation & Propagation.ROT_EULER: + ref_rot = o0 + p.ext_torque / p.rinertia * t + else: + ref_rot = o0 + return np.copy(ref_pos), np.copy(ref_vel), np.copy(ref_rot) + + system = self.system + system.time_step = 0.0001 + system.thermostat.set_brownian( + kT=0., gamma=gamma_trans, gamma_rotation=gamma_rot, seed=42) + system.integrator.set_brownian_dynamics() + modes_trans = [ + Propagation.NONE, + Propagation.SYSTEM_DEFAULT, + Propagation.TRANS_NEWTON, + Propagation.TRANS_BROWNIAN, + ] + modes_rot = [ + Propagation.NONE, + Propagation.SYSTEM_DEFAULT, + Propagation.ROT_EULER, + Propagation.ROT_BROWNIAN, + ] + for mode_trans, mode_rot in itertools.product(modes_trans, modes_rot): + try: + rotation = (mode_rot != Propagation.NONE) or \ + (mode_trans == Propagation.SYSTEM_DEFAULT) + system.part.add(pos=x0, v=v0, omega_lab=o0, + ext_force=ext_force, ext_torque=ext_torque, + rotation=3 * [rotation], + mass=0.5, rinertia=[0.5, 0.5, 0.5], + propagation=mode_trans | mode_rot) + except BaseException: + continue + assert len(system.part) > 3 + if espressomd.has_features("THERMOSTAT_PER_PARTICLE"): + ps = system.part.select(propagation=Propagation.SYSTEM_DEFAULT) + list(ps)[1].gamma = 0.8 * gamma_trans + list(ps)[2].gamma_rot = 0.7 * gamma_rot + system.time = 0. + for i in range(10): + system.integrator.run(2**i) + for p in system.part.all(): + pos = np.copy(p.pos) + vel = np.copy(p.v) + rot = np.copy(p.omega_lab) + ref_pos, ref_vel, ref_rot = calc_trajectory(p) + np.testing.assert_allclose(pos, ref_pos, rtol=1e-10) + np.testing.assert_allclose(vel, ref_vel, rtol=1e-10) + np.testing.assert_allclose(rot, ref_rot, rtol=1e-10) + if __name__ == "__main__": ut.main() diff --git a/testsuite/python/propagation_langevin.py b/testsuite/python/propagation_langevin.py new file mode 100644 index 00000000000..05785c738f2 --- /dev/null +++ b/testsuite/python/propagation_langevin.py @@ -0,0 +1,409 @@ +# +# Copyright (C) 2013-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 . +# +import unittest as ut +import unittest_decorators as utx +import espressomd +import espressomd.propagation +import itertools +import numpy as np + + +class LangevinThermostat(ut.TestCase): + + """Test Langevin Dynamics""" + system = espressomd.System(box_l=[1.0, 1.0, 1.0]) + system.cell_system.set_regular_decomposition(use_verlet_lists=True) + system.cell_system.skin = 0 + system.periodicity = [False, False, False] + + def setUp(self): + np.random.seed(42) + self.system.time_step = 1e-12 + self.system.cell_system.skin = 0.0 + self.system.integrator.set_vv() + + def tearDown(self): + self.system.part.clear() + self.system.thermostat.turn_off() + + def check_rng(self, per_particle_gamma=False): + """Test for RNG consistency.""" + + kT = 1.1 + gamma = 3.5 + + def reset_particle(): + self.system.part.clear() + p = system.part.add(pos=[0, 0, 0]) + if espressomd.has_features("ROTATION"): + p.rotation = [True, True, True] + if per_particle_gamma: + assert espressomd.has_features("THERMOSTAT_PER_PARTICLE") + if espressomd.has_features("PARTICLE_ANISOTROPY"): + p.gamma = 3 * [gamma / 2] + else: + p.gamma = gamma / 2 + if espressomd.has_features("ROTATION"): + p.gamma_rot = p.gamma * 1.5 + return p + + system = self.system + system.time_step = 0.01 + + system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=41) + system.integrator.set_vv() + + # run(0) does not increase the philox counter and should give the same + # force + p = reset_particle() + system.integrator.run(0, recalc_forces=True) + force0 = np.copy(p.f) + if espressomd.has_features("ROTATION"): + torque0 = np.copy(p.torque_lab) + system.integrator.run(0, recalc_forces=True) + force1 = np.copy(p.f) + np.testing.assert_almost_equal(force0, force1) + if espressomd.has_features("ROTATION"): + torque1 = np.copy(p.torque_lab) + np.testing.assert_almost_equal(torque0, torque1) + + # run(1) should give a different force + p = reset_particle() + system.integrator.run(1) + force2 = np.copy(p.f) + self.assertTrue(np.all(np.not_equal(force1, force2))) + if espressomd.has_features("ROTATION"): + torque2 = np.copy(p.torque_lab) + self.assertTrue(np.all(np.not_equal(torque1, torque2))) + + # Different seed should give a different force with same counter state + # force2: langevin.rng_counter() = 1, langevin.rng_seed() = 41 + # force3: langevin.rng_counter() = 1, langevin.rng_seed() = 42 + p = reset_particle() + system.integrator.run(0, recalc_forces=True) + force2 = np.copy(p.f) + if espressomd.has_features("ROTATION"): + torque2 = np.copy(p.torque_lab) + system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=42) + system.integrator.run(0, recalc_forces=True) + force3 = np.copy(p.f) + self.assertTrue(np.all(np.not_equal(force2, force3))) + if espressomd.has_features("ROTATION"): + torque3 = np.copy(p.torque_lab) + self.assertTrue(np.all(np.not_equal(torque2, torque3))) + + # Same seed should not give the same force with different counter state + p = reset_particle() + system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=42) + system.integrator.run(1) + force4 = np.copy(p.f) + self.assertTrue(np.all(np.not_equal(force3, force4))) + if espressomd.has_features("ROTATION"): + torque4 = np.copy(p.torque_lab) + self.assertTrue(np.all(np.not_equal(torque3, torque4))) + + # Seed offset should not give the same force with a lag + # force4: langevin.rng_counter() = 2, langevin.rng_seed() = 42 + # force5: langevin.rng_counter() = 3, langevin.rng_seed() = 41 + reset_particle() + system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=41) + system.integrator.run(1) + p = reset_particle() + system.integrator.run(0, recalc_forces=True) + force5 = np.copy(p.f) + self.assertTrue(np.all(np.not_equal(force4, force5))) + if espressomd.has_features("ROTATION"): + torque5 = np.copy(p.torque_lab) + self.assertTrue(np.all(np.not_equal(torque4, torque5))) + + def test_01__rng(self): + """Test for RNG consistency.""" + # No seed should throw exception + with self.assertRaises(ValueError): + self.system.thermostat.set_langevin(kT=1, gamma=2) + self.check_rng() + + @utx.skipIfMissingFeatures("THERMOSTAT_PER_PARTICLE") + def test_01__rng_per_particle(self): + """Test for RNG consistency.""" + self.check_rng(True) + + def test_02__friction_trans(self): + """Test the translational friction-only part of the thermostat.""" + + system = self.system + v0 = np.array([5., 5., 5.]) + if espressomd.has_features("PARTICLE_ANISOTROPY"): + gamma = np.array([0.5, 2., 1.5]) + atol = 3e-5 + else: + gamma = 2. + atol = 3e-4 + + system.time = 0. + system.time_step = 0.0001 + p = system.part.add(pos=(0, 0, 0), v=v0) + if espressomd.has_features("MASS"): + p.mass = 3 + system.thermostat.set_langevin(kT=0, gamma=gamma, seed=41) + + system.time = 0 + for _ in range(100): + system.integrator.run(10) + v_ref = v0 * np.exp(-gamma / p.mass * system.time) + np.testing.assert_allclose(np.copy(p.v), v_ref, atol=atol) + + @utx.skipIfMissingFeatures("ROTATION") + def test_03__friction_rot(self): + """Test the rotational friction-only part of the thermostat.""" + + system = self.system + aniso = espressomd.has_features("PARTICLE_ANISOTROPY") + o0 = np.array([5., 5., 5.]) + gamma_t = np.array([0.5, 2., 1.5]) if aniso else 2. + gamma_r = np.array([1.5, 0.7, 1.2]) if aniso else 3. + rinertia = np.array([1., 1., 1.]) + + system.time = 0. + system.time_step = 0.0001 + p = system.part.add(pos=(0, 0, 0), omega_body=o0, rotation=3 * [True]) + if espressomd.has_features("ROTATIONAL_INERTIA"): + rinertia = np.array([2., 2., 2.]) + p.rinertia = rinertia + system.thermostat.set_langevin( + kT=0, gamma=gamma_t, gamma_rotation=gamma_r, seed=41) + + system.time = 0 + for _ in range(100): + system.integrator.run(10) + ref_omega_body = o0 * np.exp(-gamma_r / rinertia * system.time) + np.testing.assert_allclose( + np.copy(p.omega_body), ref_omega_body, atol=5E-4) + + @utx.skipIfMissingFeatures("VIRTUAL_SITES_RELATIVE") + def test_07__virtual(self): + system = self.system + system.time_step = dt = 0.03 + + v0 = np.array([2, 0, 0]) + virtual = system.part.add(pos=[0, 0, 0], v=[1, 0, 0]) + physical = system.part.add(pos=[0, 0, 0], v=v0) + virtual.vs_relative = (physical.id, 0.01, (1., 0., 0., 0.)) + + system.thermostat.set_langevin( + kT=0, gamma=1, gamma_rotation=1., seed=41) + + system.integrator.run(0) + + np.testing.assert_almost_equal(np.copy(virtual.f), [0, 0, 0]) + np.testing.assert_almost_equal(np.copy(physical.f), -v0) + + system.integrator.run(1) + + np.testing.assert_almost_equal(np.copy(virtual.f), [0, 0, 0]) + np.testing.assert_almost_equal(np.copy(physical.f), dt * v0 / 2. - v0) + + @utx.skipIfMissingFeatures(["VIRTUAL_SITES_RELATIVE"]) + def test_virtual_sites_relative(self): + Propagation = espressomd.propagation.Propagation + system = self.system + o0 = np.array([0, 0, 0]) + + system.thermostat.set_langevin( + kT=0., gamma=1., gamma_rotation=1., seed=42) + + real = system.part.add(pos=[1, 1, 1], v=[3, 4, 5], omega_lab=o0) + virt_lb = system.part.add(pos=[2, 0, 0], v=[1, 2, 3], omega_lab=o0) + virt_lg = system.part.add(pos=[2, 0, 0], v=[4, 5, 6], omega_lab=o0) + virt_lx = system.part.add(pos=[2, 0, 0], v=[7, 8, 9], omega_lab=o0) + + refs = { + "real.f": [-3, -4, -5], "real.torque_lab": [0, 0, 0], + "virt_lb.f": [-1, -2, -3], "virt_lb.torque_lab": [0, 0, 0], + "virt_lg.f": [-4, -5, -6], "virt_lg.torque_lab": [0, 0, 0], + "virt_lx.f": [-7, -8, -9], "virt_lx.torque_lab": [0, 0, 0], + } + + def check(): + assertion = np.testing.assert_almost_equal + system.integrator.run(0, recalc_forces=True) + assertion(np.copy(real.f), refs["real.f"]) + assertion(np.copy(virt_lg.f), refs["virt_lg.f"]) + assertion(np.copy(virt_lb.f), refs["virt_lb.f"]) + assertion(np.copy(virt_lx.f), refs["virt_lx.f"]) + assertion(np.copy(real.torque_lab), refs["real.torque_lab"]) + assertion(np.copy(virt_lg.torque_lab), refs["virt_lg.torque_lab"]) + assertion(np.copy(virt_lb.torque_lab), refs["virt_lb.torque_lab"]) + assertion(np.copy(virt_lx.torque_lab), refs["virt_lx.torque_lab"]) + + check() + + virt_lg.vs_auto_relate_to(real, couple_to_langevin=True) + self.assertEqual(virt_lg.propagation, + Propagation.TRANS_VS_RELATIVE | + Propagation.TRANS_LANGEVIN | + Propagation.ROT_VS_RELATIVE | + Propagation.ROT_LANGEVIN) + refs["real.f"] = [-6, -8, -10] + refs["real.torque_lab"] = [1, 8, -7] + refs["virt_lg.f"] = [-3, -4, -5] + check() + + virt_lb.vs_auto_relate_to(real, couple_to_lb=True) + self.assertEqual(virt_lb.propagation, + Propagation.TRANS_LB_MOMENTUM_EXCHANGE | + Propagation.TRANS_VS_RELATIVE | + Propagation.ROT_VS_RELATIVE) + refs["virt_lb.f"] = [0, 0, 0] + check() + + virt_lx.vs_auto_relate_to( + real, couple_to_lb=True, couple_to_langevin=True) + self.assertEqual(virt_lx.propagation, + Propagation.TRANS_LB_MOMENTUM_EXCHANGE | + Propagation.TRANS_VS_RELATIVE | + Propagation.ROT_VS_RELATIVE | + Propagation.ROT_LANGEVIN) + refs["virt_lx.f"] = [0, 0, 0] + check() + + virt_lb.omega_lab = [1, 0, 0] + refs["virt_lb.torque_lab"] = [0, 0, 0] + refs["real.torque_lab"] = [1, 8, -7] + check() + + virt_lg.omega_lab = [1, 0, 0] + refs["virt_lg.torque_lab"] = [-1, 0, 0] + refs["real.torque_lab"] = [0, 8, -7] + check() + + virt_lx.omega_lab = [1, 0, 0] + refs["virt_lx.torque_lab"] = [-1, 0, 0] + refs["real.torque_lab"] = [-1, 8, -7] + check() + + real.omega_lab = [1, 0, 0] + refs["real.f"] = [-6, -9, -9] + refs["real.torque_lab"] = [-4, 7, -8] + refs["virt_lg.f"] = [-3, -5, -4] + check() + + @utx.skipIfMissingFeatures(["MASS", + "ROTATIONAL_INERTIA", + "EXTERNAL_FORCES"]) + def test_propagation(self): + """ + Check integration of Langevin's equations of motion and rotation + for various combinations of propagation modes. + """ + Propagation = espressomd.propagation.Propagation + aniso = espressomd.has_features("PARTICLE_ANISOTROPY") + gamma_trans = np.array([1.2, 1.2, 1.2]) if aniso else 1.2 + gamma_rot = np.array([0.6, 0.6, 0.6]) if aniso else 0.6 + x0 = np.array([0., 1., 2.]) + v0 = np.array([1., 2., 3.]) + o0 = np.array([1., 2., 1.]) + ext_force = np.array([-2., +2., -3.]) + ext_torque = np.array([+0.25, -0.5, +1.]) + + def has_gamma(gamma): + return gamma[0] > 0. if np.shape(gamma) else gamma > 0. + + def calc_trajectory(p): + t = self.system.time + gamma_t = gamma_trans + gamma_r = gamma_rot + if espressomd.has_features("THERMOSTAT_PER_PARTICLE") and \ + p.propagation & Propagation.SYSTEM_DEFAULT: + if has_gamma(p.gamma): + gamma_t = p.gamma + if has_gamma(p.gamma_rot): + gamma_r = p.gamma_rot + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.TRANS_LANGEVIN)): + friction = np.exp(-gamma_t / p.mass * t) + v_term = p.ext_force / gamma_t + x_drift = (v0 - v_term) * p.mass / gamma_t + ref_vel = v_term + (v0 - v_term) * friction + ref_pos = x0 + v_term * t + x_drift * (1. - friction) + elif p.propagation & Propagation.TRANS_NEWTON: + ref_vel = v0 + p.ext_force / p.mass * t + ref_pos = x0 + v0 * t + 0.5 * p.ext_force / p.mass * t**2 + else: + ref_vel = v0 + ref_pos = x0 + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.ROT_LANGEVIN)): + friction = np.exp(-gamma_r / p.rinertia * t) + o_term = p.ext_torque / gamma_r + ref_rot = o_term + (o0 - o_term) * friction + elif p.propagation & Propagation.ROT_EULER: + ref_rot = o0 + p.ext_torque / p.rinertia * t + else: + ref_rot = o0 + return np.copy(ref_pos), np.copy(ref_vel), np.copy(ref_rot) + + system = self.system + system.time_step = 0.00001 + system.thermostat.set_langevin( + kT=0., gamma=gamma_trans, gamma_rotation=gamma_rot, seed=42) + system.integrator.set_vv() + modes_trans = [ + Propagation.NONE, + Propagation.SYSTEM_DEFAULT, + Propagation.TRANS_NEWTON, + Propagation.TRANS_LANGEVIN, + ] + modes_rot = [ + Propagation.NONE, + Propagation.SYSTEM_DEFAULT, + Propagation.ROT_EULER, + Propagation.ROT_LANGEVIN, + ] + for mode_trans, mode_rot in itertools.product(modes_trans, modes_rot): + try: + rotation = (mode_rot != Propagation.NONE) or \ + (mode_trans == Propagation.SYSTEM_DEFAULT) + system.part.add(pos=x0, v=v0, omega_lab=o0, + ext_force=ext_force, ext_torque=ext_torque, + rotation=3 * [rotation], + mass=4., rinertia=[1.5, 1.5, 1.5], + propagation=mode_trans | mode_rot) + except BaseException: + continue + assert len(system.part) > 3 + if espressomd.has_features("THERMOSTAT_PER_PARTICLE"): + ps = system.part.select(propagation=Propagation.SYSTEM_DEFAULT) + list(ps)[1].gamma = 0.8 * gamma_trans + list(ps)[2].gamma_rot = 0.7 * gamma_rot + system.time = 0. + for i in range(10): + system.integrator.run(2**i) + for p in system.part.all(): + pos = np.copy(p.pos) + vel = np.copy(p.v) + rot = np.copy(p.omega_lab) + ref_pos, ref_vel, ref_rot = calc_trajectory(p) + np.testing.assert_allclose(pos, ref_pos, rtol=1e-7) + np.testing.assert_allclose(vel, ref_vel, rtol=1e-7) + np.testing.assert_allclose(rot, ref_rot, rtol=1e-7) + + +if __name__ == "__main__": + ut.main() diff --git a/testsuite/python/propagation_lb.py b/testsuite/python/propagation_lb.py new file mode 100644 index 00000000000..09bd1710cd5 --- /dev/null +++ b/testsuite/python/propagation_lb.py @@ -0,0 +1,191 @@ +# +# Copyright (C) 2010-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 . +# + +import espressomd.lb +import espressomd.shapes +import espressomd.propagation +import unittest as ut +import unittest_decorators as utx +import numpy as np + + +class LBThermostatCommon: + + system = espressomd.System(box_l=[6., 6., 6.]) + system.time_step = 0.01 + system.cell_system.skin = 0.1 + system.min_global_cut = 2. + + def setUp(self): + self.system.time_step = 0.01 + self.system.integrator.set_vv() + + def tearDown(self): + self.system.lb = None + self.system.thermostat.turn_off() + self.system.part.clear() + + @utx.skipIfMissingFeatures(["VIRTUAL_SITES_RELATIVE", + "VIRTUAL_SITES_INERTIALESS_TRACERS"]) + def test_virtual_sites_relative(self): + Propagation = espressomd.propagation.Propagation + + system = self.system + lb_fluid = self.lb_class(agrid=1., tau=0.01, density=1., + kinematic_viscosity=1., **self.lb_params) + system.lb = lb_fluid + + particle_inert = system.part.add(pos=[1, 1, 1], v=[0, 0, 0]) + particle_skip = system.part.add(pos=[1, 1, 1], v=[1, 0, 0], + propagation=Propagation.NONE) + tracer = system.part.add(pos=[0, 0, 0], v=[1, 0, 0], + propagation=Propagation.TRANS_LB_TRACER) + physical = system.part.add(pos=[0, 0, 0], v=[1, 0, 0]) + central_coupled = system.part.add(pos=[1, 1, 1], v=[1, 0, 0]) + virtual_coupled = system.part.add(pos=[2, 0, 0], v=[1, 0, 0]) + virtual_coupled.vs_auto_relate_to(central_coupled, couple_to_lb=True) + virtual_decoupled = system.part.add(pos=[2, 0, 0], v=[5, 0, 0]) + virtual_decoupled.vs_auto_relate_to( + central_coupled, couple_to_lb=False) + central_recouple = system.part.add(pos=[1, 1, 1], v=[3, 0, 0], + propagation=Propagation.NONE) + virtual_coupler = system.part.add(pos=[2, 0, 0], v=[1, 0, 0]) + virtual_coupler.vs_auto_relate_to(central_recouple, couple_to_lb=True) + + system.thermostat.set_lb(LB_fluid=lb_fluid, gamma=1.) + system.integrator.run(1) + + np.testing.assert_almost_equal(np.copy(particle_inert.f), [0, 0, 0]) + np.testing.assert_almost_equal(np.copy(particle_skip.f), [0, 0, 0]) + np.testing.assert_almost_equal(np.copy(tracer.f), [0, 0, 0]) + np.testing.assert_almost_equal(np.copy(physical.f), [-1, 0, 0]) + # vs relative: setup with additive forces + np.testing.assert_almost_equal(np.copy(central_coupled.f), [-2, 0, 0]) + np.testing.assert_almost_equal(np.copy(virtual_coupled.f), [-1, 0, 0]) + np.testing.assert_almost_equal(np.copy(virtual_decoupled.f), [0, 0, 0]) + # vs relative: setup with overriden forces + np.testing.assert_almost_equal(np.copy(central_recouple.f), [-3, 0, 0]) + np.testing.assert_almost_equal(np.copy(virtual_coupler.f), [-3, 0, 0]) + + @utx.skipIfMissingFeatures(["MASS", + "ROTATIONAL_INERTIA", + "EXTERNAL_FORCES"]) + def test_propagation(self): + Propagation = espressomd.propagation.Propagation + np.random.seed(42) + rtol_vel = 2e-4 if self.lb_params["single_precision"] else 1e-6 + v0 = np.array([-2., 2., 4.]) + o0 = np.array([2., 3., 4.]) + gamma_trans = 1.2 + gamma_rot = 0.6 + gamma_lbf = 4. + positions = [] + + def calc_trajectory(p, r0): + t = self.system.time + if p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.TRANS_LB_MOMENTUM_EXCHANGE): + friction_vel = np.exp(-gamma_lbf / p.mass * (t - tau / 2.)) + friction_pos = np.exp(-gamma_lbf / p.mass * t) + x_drift = v0 * p.mass / gamma_lbf + ref_vel = v0 * friction_vel + ref_pos = r0 + x_drift * (1. - friction_pos) + elif p.propagation & Propagation.TRANS_LANGEVIN: + friction = np.exp(-gamma_trans / p.mass * t) + x_drift = v0 * p.mass / gamma_trans + ref_vel = v0 * friction + ref_pos = r0 + x_drift * (1. - friction) + elif p.propagation & Propagation.TRANS_NEWTON: + ref_vel = v0 + ref_pos = r0 + v0 * t + else: + ref_vel = v0 + ref_pos = r0 + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.ROT_EULER)): + ref_rot = o0 + p.ext_torque / p.rinertia * t + elif p.propagation & Propagation.ROT_LANGEVIN: + friction = np.exp(-gamma_rot / p.rinertia * t) + o_term = p.ext_torque / gamma_rot + ref_rot = o_term + (o0 - o_term) * friction + else: + ref_rot = o0 + return np.copy(ref_pos), np.copy(ref_vel), np.copy(ref_rot) + + def make_particle(propagation): + r0 = np.random.random(3) * self.system.box_l + positions.append(r0) + rotation = (propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.ROT_EULER | Propagation.ROT_LANGEVIN)) != 0 + self.system.part.add(pos=r0, v=v0, propagation=propagation, + omega_lab=o0, rotation=3 * [rotation], + mass=4., rinertia=[1.5, 1.5, 1.5], + ext_torque=[+0.25, -0.5, +1.]) + + system = self.system + system.time_step = tau = 0.00001 + system.thermostat.set_langevin( + kT=0., gamma=gamma_trans, gamma_rotation=gamma_rot, seed=42) + lb_fluid = self.lb_class(agrid=1., tau=tau, density=200., + kinematic_viscosity=1., **self.lb_params) + system.lb = lb_fluid + system.thermostat.set_lb(LB_fluid=lb_fluid, gamma=gamma_lbf) + system.integrator.set_vv() + make_particle(Propagation.NONE) + make_particle(Propagation.SYSTEM_DEFAULT) + make_particle(Propagation.TRANS_LANGEVIN | Propagation.ROT_LANGEVIN) + make_particle(Propagation.TRANS_NEWTON | Propagation.ROT_EULER) + make_particle(Propagation.TRANS_LB_MOMENTUM_EXCHANGE) + make_particle(Propagation.TRANS_LB_MOMENTUM_EXCHANGE | + Propagation.ROT_EULER) + make_particle(Propagation.TRANS_LB_MOMENTUM_EXCHANGE | + Propagation.ROT_LANGEVIN) + system.time = 0. + for i in range(10): + system.integrator.run(2**i) + for p, r0 in zip(system.part.all(), positions): + pos = np.copy(p.pos) + vel = np.copy(p.v) + rot = np.copy(p.omega_lab) + ref_pos, ref_vel, ref_rot = calc_trajectory(p, r0) + np.testing.assert_allclose(pos, ref_pos, rtol=1e-6) + np.testing.assert_allclose(vel, ref_vel, rtol=rtol_vel) + np.testing.assert_allclose(rot, ref_rot, rtol=1e-7) + + +@utx.skipIfMissingFeatures(["WALBERLA"]) +class LBWalberlaThermostatDoublePrecisionCPU(LBThermostatCommon, ut.TestCase): + + """Test for the CPU implementation of the LB.""" + + lb_class = espressomd.lb.LBFluidWalberla + lb_params = {"single_precision": False} + + +@utx.skipIfMissingFeatures(["WALBERLA"]) +class LBWalberlaThermostatSinglePrecisionCPU(LBThermostatCommon, ut.TestCase): + + """Test for the CPU implementation of the LB in single-precision.""" + + lb_class = espressomd.lb.LBFluidWalberla + lb_params = {"single_precision": True} + + +if __name__ == "__main__": + ut.main() diff --git a/testsuite/python/propagation_newton.py b/testsuite/python/propagation_newton.py new file mode 100644 index 00000000000..b9a5b563bfc --- /dev/null +++ b/testsuite/python/propagation_newton.py @@ -0,0 +1,176 @@ +# +# Copyright (C) 2013-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 . +# +import espressomd +import espressomd.propagation +import itertools +import numpy as np +import unittest as ut +import unittest_decorators as utx + + +class VelocityVerlet(ut.TestCase): + + system = espressomd.System(box_l=[10., 10., 10.]) + system.cell_system.skin = 0.4 + system.time_step = 0.01 + + def tearDown(self): + self.system.part.clear() + self.system.thermostat.turn_off() + self.system.time_step = 0.01 + + def calc_pos(self, p, x0, v0): + t = self.system.time + return np.copy(0.5 * p.ext_force / p.mass * t**2 + v0 * t + x0) + + def calc_vel(self, p, v0): + t = self.system.time + return np.copy(p.ext_force / p.mass * t + v0) + + def calc_rot(self, p, o0): + t = self.system.time + return np.copy(p.ext_torque / p.rinertia * t + o0) + + def test_newton_laws(self): + """ + Tests integration of Newton's equations for a particle with and without + external forces and with time step changes on the way. + + """ + system = self.system + system.integrator.set_vv() + + # Newton's 1st law with time step change on the way + p = system.part.add(pos=(0, 0, 0), v=(1, 2, 3)) + system.time_step = 0.01 + system.time = 12. + np.testing.assert_allclose(np.copy(p.v), (1, 2, 3)) + for i in range(10): + np.testing.assert_allclose(np.copy(p.pos), np.copy( + i * system.time_step * p.v), atol=1E-12) + system.integrator.run(1) + + # Check that the time has passed + np.testing.assert_allclose(system.time, 12. + 10 * system.time_step) + + v0 = np.copy(p.v) + pos0 = np.copy(p.pos) + system.time_step = 0.02 + for i in range(10): + np.testing.assert_allclose(np.copy(p.pos), np.copy( + pos0 + i * system.time_step * p.v), atol=1E-12) + system.integrator.run(1) + + # Newton's 2nd law + if espressomd.has_features("EXTERNAL_FORCES"): + system.time = 0. + if espressomd.has_features("MASS"): + p.mass = 2.3 + p.pos = (0, 0, 0) + pos0 = np.copy(p.pos) + ext_force = np.array([-2., 1.3, 1.]) + p.ext_force = ext_force + system.time_step = 0.03 + for i in range(10): + ref_pos = self.calc_pos(p, pos0, v0) + np.testing.assert_allclose(np.copy(p.pos), ref_pos, atol=1E-12) + system.integrator.run(1) + + @utx.skipIfMissingFeatures("VIRTUAL_SITES_RELATIVE") + def test_07__virtual(self): + system = self.system + system.time_step = 0.01 + + virtual = system.part.add(pos=[0., 0., 0.], v=[-1., 0., 0.]) + physical = system.part.add(pos=[0., 0., 0.], v=[1., 0., 0.]) + virtual.vs_relative = (physical.id, 0.3, (1., 0., 0., 0.)) + + system.integrator.set_vv() + system.integrator.run(1) + + np.testing.assert_allclose(np.copy(physical.pos), [0.01, 0., 0.]) + np.testing.assert_allclose(np.copy(virtual.pos), [0.01, 0., 0.3]) + np.testing.assert_allclose(np.copy(physical.v), [1., 0., 0.]) + np.testing.assert_allclose(np.copy(virtual.v), [1., 0., 0.]) + + @utx.skipIfMissingFeatures(["MASS", + "ROTATIONAL_INERTIA", + "EXTERNAL_FORCES"]) + def test_propagation(self): + """ + Check integration of Newton's equations of motion and Euler's equations + of rotation for various combinations of propagation modes. + """ + Propagation = espressomd.propagation.Propagation + system = self.system + system.time_step = 0.01 + system.integrator.set_vv() + pos0 = [0., 1., 2.] + v0 = np.array([1., 2., 3.]) + o0 = np.array([2., 3., 4.]) + ext_force = np.array([-1., +2., -4.]) + ext_torque = np.array([+1., -3., +5.]) + modes_trans = [ + Propagation.NONE, + Propagation.SYSTEM_DEFAULT, + Propagation.TRANS_NEWTON, + ] + modes_rot = [ + Propagation.NONE, + Propagation.SYSTEM_DEFAULT, + Propagation.ROT_EULER, + ] + for mode_trans, mode_rot in itertools.product(modes_trans, modes_rot): + try: + rotation = (mode_rot != Propagation.NONE) or \ + (mode_trans == Propagation.SYSTEM_DEFAULT) + system.part.add(pos=pos0, v=v0, omega_lab=o0, + ext_force=ext_force, ext_torque=ext_torque, + rotation=3 * [rotation], + mass=0.5, rinertia=[0.5, 0.5, 0.5], + propagation=mode_trans | mode_rot) + except BaseException: + continue + assert len(system.part) > 3 + system.time = 0. + for _ in range(10): + system.integrator.run(1) + for p in system.part.all(): + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.TRANS_NEWTON)): + ref_pos = self.calc_pos(p, pos0, v0) + ref_vel = self.calc_vel(p, v0) + else: + ref_pos = pos0 + ref_vel = v0 + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.ROT_EULER)): + ref_rot = self.calc_rot(p, o0) + else: + ref_rot = o0 + pos = np.copy(p.pos) + vel = np.copy(p.v) + rot = np.copy(p.omega_lab) + np.testing.assert_allclose(pos, ref_pos, rtol=1e-9) + np.testing.assert_allclose(vel, ref_vel, rtol=1e-9) + np.testing.assert_allclose(rot, ref_rot, rtol=1e-9) + + +if __name__ == "__main__": + ut.main() diff --git a/testsuite/python/integrator_npt.py b/testsuite/python/propagation_npt.py similarity index 63% rename from testsuite/python/integrator_npt.py rename to testsuite/python/propagation_npt.py index 919c926d360..032444ade89 100644 --- a/testsuite/python/integrator_npt.py +++ b/testsuite/python/propagation_npt.py @@ -18,12 +18,14 @@ # import unittest as ut import unittest_decorators as utx +import tests_common import numpy as np import espressomd import espressomd.integrate +import espressomd.propagation -@utx.skipIfMissingFeatures(["NPT", "LENNARD_JONES"]) +@utx.skipIfMissingFeatures(["NPT"]) class IntegratorNPT(ut.TestCase): """This tests the NpT integrator interface.""" @@ -40,8 +42,20 @@ def tearDown(self): self.system.electrostatics.clear() if espressomd.has_features(["DIPOLES"]): self.system.magnetostatics.clear() + self.reset_rng_counter() self.system.thermostat.turn_off() self.system.integrator.set_vv() + if espressomd.has_features(["LENNARD_JONES"]): + self.system.non_bonded_inter[0, 0].lennard_jones.deactivate() + + def reset_rng_counter(self): + # reset RNG counter to make tests independent of execution order + self.system.thermostat.set_npt(kT=0., gamma0=0., gammav=1e-6, seed=42) + thmst_list = self.system.thermostat.get_state() + for thmst in thmst_list: + if thmst["type"] == "NPT_ISO": + thmst["counter"] = 0 + self.system.thermostat.__setstate__(thmst_list) def test_integrator_exceptions(self): # invalid parameters should throw exceptions @@ -58,7 +72,83 @@ def test_integrator_exceptions(self): self.system.integrator.set_isotropic_npt( ext_pressure=1, piston=1, direction=[True, False]) - def test_00_integrator_recovery(self): + @utx.skipIfMissingFeatures(["MASS", "EXTERNAL_FORCES"]) + def test_00_propagation(self): + """ + Check integration of the equations of motion. + """ + Propagation = espressomd.propagation.Propagation + gamma0 = 1.2 + v0 = np.array([1., 2., 3.]) + ext_force = np.array([-2., +2., -3.]) + + def calc_trajectory(p, x0): + t = self.system.time + gamma_t = gamma0 + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.TRANS_LANGEVIN_NPT)): + friction = np.exp(-gamma_t / p.mass * t) + v_term = p.ext_force / gamma_t + x_drift = (v0 - v_term) * p.mass / gamma_t + ref_vel = v_term + (v0 - v_term) * friction + ref_pos = x0 + v_term * t + x_drift * (1. - friction) + elif p.propagation & Propagation.TRANS_NEWTON: + ref_vel = v0 + p.ext_force / p.mass * t + ref_pos = x0 + v0 * t + 0.5 * p.ext_force / p.mass * t**2 + else: + ref_vel = v0 + ref_pos = x0 + return np.copy(ref_pos), np.copy(ref_vel) + + system = self.system + system.time_step = 0.00001 + system.thermostat.set_npt(kT=0., gamma0=gamma0, gammav=1e-6, seed=42) + system.integrator.set_isotropic_npt(ext_pressure=0.01, piston=1e6) + positions = [] + modes_trans = [ + Propagation.NONE, + Propagation.SYSTEM_DEFAULT, + Propagation.TRANS_NEWTON, + Propagation.TRANS_LANGEVIN_NPT, + ] + for mode_trans in modes_trans: + x0 = np.random.random(3) * np.copy(system.box_l) + system.part.add(pos=x0, v=v0, ext_force=ext_force, + mass=4., propagation=mode_trans) + positions.append(x0) + system.time = 0. + for i in range(10): + system.integrator.run(2**i) + for p, x0 in zip(system.part.all(), positions): + pos = np.copy(p.pos) + vel = np.copy(p.v) + ref_pos, ref_vel = calc_trajectory(p, x0) + np.testing.assert_allclose(pos, ref_pos, rtol=1e-7) + np.testing.assert_allclose(vel, ref_vel, rtol=1e-7) + + @utx.skipIfMissingFeatures(["VIRTUAL_SITES_RELATIVE"]) + def test_07__virtual(self): + system = self.system + system.time_step = 0.01 + + virtual = system.part.add(pos=[0, 0, 0], v=[1, 0, 0]) + physical = system.part.add(pos=[0, 0, 0], v=[2, 0, 0]) + virtual.vs_relative = (physical.id, 0.1, (1., 0., 0., 0.)) + + system.thermostat.set_npt(kT=0., gamma0=2., gammav=1e-6, seed=42) + system.integrator.set_isotropic_npt(ext_pressure=0.01, piston=1e6) + + system.integrator.run(1) + + np.testing.assert_almost_equal(np.copy(physical.f), [0., 0., 0.]) + np.testing.assert_almost_equal(np.copy(physical.v), [1.9602, 0., 0.]) + np.testing.assert_almost_equal(np.copy(physical.pos), [0.0198, 0., 0.]) + np.testing.assert_almost_equal(np.copy(virtual.f), [0., 0., 0.]) + np.testing.assert_almost_equal(np.copy(virtual.v), [1.9602, 0., 0.]) + np.testing.assert_almost_equal(np.copy(virtual.pos), [0.0198, 0., 0.1]) + + @utx.skipIfMissingFeatures(["LENNARD_JONES"]) + def test_09_integrator_recovery(self): # the system is still in a valid state after a failure system = self.system np.random.seed(42) @@ -122,14 +212,6 @@ def test_00_integrator_recovery(self): np.copy(system.part.all().pos), positions_start + np.array([[-1.2e-3, 0, 0], [1.2e-3, 0, 0]])) - if espressomd.has_features("ROTATION"): - # propagator doesn't handle angular velocities - system.part.all().rotation = [3 * [False], [True, False, False]] - system.thermostat.set_npt(kT=1., gamma0=2., gammav=0.04, seed=42) - system.integrator.set_isotropic_npt(**npt_params) - with self.assertRaisesRegex(Exception, "The isotropic NpT integrator doesn't propagate angular velocities"): - system.integrator.run(1) - def run_with_p3m(self, container, p3m, method): system = self.system npt_kwargs = {"ext_pressure": 0.001, "piston": 0.001} @@ -142,6 +224,8 @@ def run_with_p3m(self, container, p3m, method): pos=np.random.uniform(0, system.box_l[0], (11, 3))) if espressomd.has_features("P3M"): partcl.q = np.sign(np.arange(-5, 6)) + if espressomd.has_features("DP3M"): + partcl.dip = tests_common.random_dipoles(11) system.non_bonded_inter[0, 0].lennard_jones.set_params( epsilon=1, sigma=1, cutoff=2**(1 / 6), shift=0.25) system.integrator.set_steepest_descent( @@ -169,7 +253,15 @@ def run_with_p3m(self, container, p3m, method): with self.assertRaisesRegex(Exception, err_msg): system.integrator.run(0, recalc_forces=True) - @utx.skipIfMissingFeatures(["P3M"]) + @utx.skipIfMissingFeatures(["DP3M", "LENNARD_JONES"]) + def test_npt_dp3m_cpu(self): + import espressomd.magnetostatics + dp3m = espressomd.magnetostatics.DipolarP3M( + prefactor=1.0, accuracy=1e-2, mesh=3 * [36], cao=7, r_cut=1.0, + alpha=2.995, tune=False) + self.run_with_p3m(self.system.magnetostatics, dp3m, "magnetostatics") + + @utx.skipIfMissingFeatures(["P3M", "LENNARD_JONES"]) def test_npt_p3m_cpu(self): import espressomd.electrostatics p3m = espressomd.electrostatics.P3M( @@ -178,7 +270,7 @@ def test_npt_p3m_cpu(self): self.run_with_p3m(self.system.electrostatics, p3m, "electrostatics") @utx.skipIfMissingGPU() - @utx.skipIfMissingFeatures(["P3M"]) + @utx.skipIfMissingFeatures(["P3M", "LENNARD_JONES"]) def test_npt_p3m_gpu(self): import espressomd.electrostatics p3m = espressomd.electrostatics.P3MGPU( diff --git a/testsuite/python/stokesian_thermostat.py b/testsuite/python/propagation_stokesian.py similarity index 54% rename from testsuite/python/stokesian_thermostat.py rename to testsuite/python/propagation_stokesian.py index 3e43d1c17f1..9a7ac25393f 100644 --- a/testsuite/python/stokesian_thermostat.py +++ b/testsuite/python/propagation_stokesian.py @@ -19,13 +19,15 @@ import unittest as ut import unittest_decorators as utx import espressomd +import espressomd.propagation +import itertools import numpy as np @utx.skipIfMissingFeatures("STOKESIAN_DYNAMICS") class StokesianThermostat(ut.TestCase): - """Test Stokesian thermostat""" + """Test Stokesian Dynamics""" system = espressomd.System(box_l=[1.0, 1.0, 1.0]) system.cell_system.skin = 0 system.periodicity = [False, False, False] @@ -39,6 +41,7 @@ def tearDown(self): self.system.part.clear() self.system.thermostat.turn_off() self.system.integrator.set_vv() + self.system.periodicity = [False, False, False] def test_01__rng(self): """Test for RNG consistency.""" @@ -61,7 +64,7 @@ def reset_particle(): system.thermostat.set_stokesian(kT=1, seed=-1) system.thermostat.set_stokesian(kT=kT, seed=41) - self.system.integrator.set_stokesian_dynamics( + system.integrator.set_stokesian_dynamics( viscosity=viscosity, radii={0: radius}) pos2force = np.sqrt(24 * kT * system.time_step) @@ -126,6 +129,97 @@ def test_integrator_exceptions(self): with self.assertRaisesRegex(Exception, r"Stokesian Dynamics requires periodicity \(False, False, False\)"): self.system.periodicity = [False, True, False] + @utx.skipIfMissingFeatures(["MASS", + "ROTATIONAL_INERTIA", + "EXTERNAL_FORCES"]) + def test_propagation(self): + """ + Check integration of the equations of motion and rotation + for various combinations of propagation modes. + The exact values of Stokesian trajectories are not tested; instead + the Stokesian thermostat parameters are chosen such that positions, + velocities and angular velocities are in the range [-100, +100] + in simulation units. + """ + Propagation = espressomd.propagation.Propagation + np.random.seed(42) + x0 = [0., 1., 2.] + v0 = np.array([1., 2., 3.]) + o0 = np.array([2., 3., 4.]) + ext_force = np.array([-1., +2., -4.]) + ext_torque = np.array([+1., -3., +5.]) + + def calc_trajectory(p): + t = self.system.time + atol = 1e-6 + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.TRANS_STOKESIAN)): + atol = 1e3 + ref_pos = np.array([0., 0., 0.]) + ref_vel = np.array([0., 0., 0.]) + elif p.propagation & Propagation.TRANS_NEWTON: + ref_vel = v0 + p.ext_force / p.mass * t + ref_pos = x0 + v0 * t + 0.5 * p.ext_force / p.mass * t**2 + else: + ref_vel = v0 + ref_pos = x0 + if (p.propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.ROT_STOKESIAN)): + atol = 1e3 + ref_rot = np.array([0., 0., 0.]) + elif p.propagation & Propagation.ROT_EULER: + ref_rot = o0 + p.ext_torque / p.rinertia * t + else: + ref_rot = o0 + return np.copy(ref_pos), np.copy(ref_vel), np.copy(ref_rot), atol + + system = self.system + system.time_step = 0.0001 + system.thermostat.set_stokesian(kT=1., seed=42) + system.integrator.set_stokesian_dynamics(viscosity=2.4, radii={0: 1.5}) + modes_trans = [ + Propagation.NONE, + Propagation.SYSTEM_DEFAULT, + Propagation.TRANS_NEWTON, + Propagation.TRANS_STOKESIAN, + ] + modes_rot = [ + Propagation.NONE, + Propagation.SYSTEM_DEFAULT, + Propagation.ROT_EULER, + Propagation.ROT_STOKESIAN, + ] + for mode_trans, mode_rot in itertools.product(modes_trans, modes_rot): + try: + propagation = mode_trans | mode_rot + rotation = (mode_rot != Propagation.NONE) or \ + (mode_trans == Propagation.SYSTEM_DEFAULT) + if propagation & (Propagation.SYSTEM_DEFAULT | + Propagation.TRANS_STOKESIAN | + Propagation.ROT_STOKESIAN): + pos = x0 + 100. * np.random.random(3) + else: + pos = x0 + system.part.add(pos=pos, v=v0, omega_lab=o0, + ext_force=ext_force, ext_torque=ext_torque, + rotation=3 * [rotation], + mass=0.5, rinertia=[0.5, 0.5, 0.5], + propagation=propagation) + except BaseException: + continue + assert len(system.part) > 3 + system.time = 0. + for i in range(10): + system.integrator.run(2**i) + for p in system.part.all(): + pos = np.copy(p.pos) + vel = np.copy(p.v) + rot = np.copy(p.omega_lab) + ref_pos, ref_vel, ref_rot, atol = calc_trajectory(p) + np.testing.assert_allclose(pos, ref_pos, rtol=0., atol=atol) + np.testing.assert_allclose(vel, ref_vel, rtol=0., atol=atol) + np.testing.assert_allclose(rot, ref_rot, rtol=0., atol=atol) + if __name__ == "__main__": ut.main() diff --git a/testsuite/python/rotate_system.py b/testsuite/python/rotate_system.py index 66a6903a9da..ded7eda4e18 100644 --- a/testsuite/python/rotate_system.py +++ b/testsuite/python/rotate_system.py @@ -70,12 +70,14 @@ def test_no_mass(self): # Check that virtual sites do not influence the center of mass # calculation - if espressomd.has_features("VIRTUAL_SITES"): - p2 = system.part.add(pos=p1.pos, virtual=True) + if espressomd.has_features("VIRTUAL_SITES_RELATIVE"): + vs_dist = 0.01 + p2 = system.part.add(pos=p1.pos) + p2.vs_relative = (p1.id, vs_dist, (1., 0., 0., 0.)) system.rotate_system(phi=pi / 2., theta=pi / 2., alpha=-pi / 2.) np.testing.assert_allclose(np.copy(p0.pos), [6, 4, 4]) np.testing.assert_allclose(np.copy(p1.pos), [4, 6, 6]) - np.testing.assert_allclose(np.copy(p2.pos), [4, 6, 6]) + np.testing.assert_allclose(np.copy(p2.pos), [4, 6, 6 + vs_dist]) if __name__ == "__main__": diff --git a/testsuite/python/save_checkpoint.py b/testsuite/python/save_checkpoint.py index 62d883e21e3..13bb2740ae5 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -31,7 +31,6 @@ import espressomd.interactions import espressomd.lees_edwards import espressomd.drude_helpers -import espressomd.virtual_sites import espressomd.accumulators import espressomd.observables import espressomd.io.writer @@ -239,10 +238,6 @@ pair_mobility=False, self_mobility=True) if espressomd.has_features(['VIRTUAL_SITES', 'VIRTUAL_SITES_RELATIVE']): - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative( - have_quaternion=True) - system.virtual_sites.have_quaternion = True - system.virtual_sites.override_cutoff_check = True p2.vs_auto_relate_to(p1) # non-bonded interactions @@ -318,6 +313,11 @@ system.collision_detection.set_params( mode="bind_centers", distance=0.11, bond_centers=harmonic_bond) +particle_propagation0 = p1.propagation +particle_propagation1 = p2.propagation +checkpoint.register("particle_propagation0") +checkpoint.register("particle_propagation1") + if espressomd.has_features('DP3M') and 'DP3M' in modes: dp3m = espressomd.magnetostatics.DipolarP3M( prefactor=1., diff --git a/testsuite/python/test_checkpoint.py b/testsuite/python/test_checkpoint.py index d9effd289b5..93054bbe6eb 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -32,12 +32,12 @@ import espressomd.magnetostatics import espressomd.io.writer # pylint: disable=unused-import import espressomd.lees_edwards -import espressomd.virtual_sites import espressomd.integrate import espressomd.shapes import espressomd.constraints import espressomd.lb import espressomd.electrokinetics +import espressomd.propagation with contextlib.suppress(ImportError): import espressomd.io.vtk @@ -376,6 +376,8 @@ def test_particle_properties(self): np.testing.assert_allclose(np.copy(p2.pos), np.array([1.0, 1.0, 2.0])) np.testing.assert_allclose(np.copy(p1.f), particle_force0) np.testing.assert_allclose(np.copy(p2.f), particle_force1) + self.assertEqual(p1.propagation, particle_propagation0) + self.assertEqual(p2.propagation, particle_propagation1) self.assertEqual(p1.type, 0) self.assertEqual(p2.type, 0) self.assertEqual(p3.type, 1) @@ -509,7 +511,6 @@ def test_thermostat_Langevin(self): self.assertEqual(thmst['kT'], 1.0) self.assertEqual(thmst['seed'], 42) self.assertEqual(thmst['counter'], 0) - self.assertFalse(thmst['act_on_virtual']) np.testing.assert_array_equal(thmst['gamma'], 3 * [2.0]) if espressomd.has_features('ROTATION'): np.testing.assert_array_equal(thmst['gamma_rotation'], 3 * [2.0]) @@ -522,7 +523,6 @@ def test_thermostat_Brownian(self): self.assertEqual(thmst['kT'], 1.0) self.assertEqual(thmst['seed'], 42) self.assertEqual(thmst['counter'], 0) - self.assertFalse(thmst['act_on_virtual']) np.testing.assert_array_equal(thmst['gamma'], 3 * [2.0]) if espressomd.has_features('ROTATION'): np.testing.assert_array_equal(thmst['gamma_rotation'], 3 * [2.0]) @@ -706,15 +706,12 @@ def test_drude_helpers(self): @utx.skipIfMissingFeatures(['VIRTUAL_SITES', 'VIRTUAL_SITES_RELATIVE']) def test_virtual_sites(self): - self.assertIsInstance( - system.virtual_sites, - espressomd.virtual_sites.VirtualSitesRelative) - self.assertTrue(system.virtual_sites.have_quaternion) - self.assertTrue(system.virtual_sites.override_cutoff_check) + Propagation = espressomd.propagation.Propagation p_real = system.part.by_id(0) p_virt = system.part.by_id(1) - self.assertTrue(p_virt.virtual) - self.assertFalse(p_real.virtual) + self.assertEqual(p_real.propagation, Propagation.SYSTEM_DEFAULT) + self.assertEqual(p_virt.propagation, Propagation.TRANS_VS_RELATIVE | + Propagation.ROT_VS_RELATIVE) self.assertEqual(p_real.vs_relative[0], -1) self.assertEqual(p_virt.vs_relative[0], p_real.id) self.assertEqual(p_real.vs_relative[1], 0.) diff --git a/testsuite/python/virtual_sites_relative.py b/testsuite/python/virtual_sites_relative.py index 6a575542d83..dc01d655dc6 100644 --- a/testsuite/python/virtual_sites_relative.py +++ b/testsuite/python/virtual_sites_relative.py @@ -19,7 +19,7 @@ import unittest as ut import unittest_decorators as utx import espressomd -import espressomd.virtual_sites +import espressomd.propagation import numpy as np import tests_common @@ -40,7 +40,6 @@ def tearDown(self): self.system.thermostat.turn_off() self.system.integrator.set_vv() self.system.non_bonded_inter[0, 0].lennard_jones.deactivate() - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesOff() def multiply_quaternions(self, a, b): return np.array( @@ -58,8 +57,10 @@ def director_from_quaternion(self, quat): def verify_vs(self, vs, verify_velocity=True): """Verify virtual site position and velocity.""" - self.assertTrue(vs.virtual) - + Propagation = espressomd.propagation.Propagation + self.assertTrue(vs.is_virtual()) + self.assertTrue(vs.propagation & Propagation.TRANS_VS_RELATIVE) + self.assertTrue(vs.propagation & Propagation.ROT_VS_RELATIVE) vs_r = vs.vs_relative # Get related particle @@ -81,49 +82,19 @@ def verify_vs(self, vs, verify_velocity=True): v_d - vs_r[1] * self.director_from_quaternion( self.multiply_quaternions(rel.quat, vs_r[2]))), 1E-6) - def test_aa_method_switching(self): - # Virtual sites should be disabled by default - self.assertIsInstance( - self.system.virtual_sites, - espressomd.virtual_sites.VirtualSitesOff) - self.assertFalse(self.system.virtual_sites.have_quaternion) - self.assertFalse(self.system.virtual_sites.override_cutoff_check) - - # Set properties - self.system.virtual_sites.have_quaternion = True - self.system.virtual_sites.override_cutoff_check = True - self.assertTrue(self.system.virtual_sites.have_quaternion) - self.assertTrue(self.system.virtual_sites.override_cutoff_check) - - # Switch implementation - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() - self.assertIsInstance( - self.system.virtual_sites, - espressomd.virtual_sites.VirtualSitesRelative) - self.assertFalse(self.system.virtual_sites.have_quaternion) - self.assertFalse(self.system.virtual_sites.override_cutoff_check) - def test_vs_quat(self): self.system.time_step = 0.01 self.system.min_global_cut = 0.23 - # First check that quaternion of virtual particle is unchanged if - # have_quaternion is false. - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative( - have_quaternion=False) - self.assertFalse(self.system.virtual_sites.have_quaternion) + Propagation = espressomd.propagation.Propagation p1 = self.system.part.add(pos=[1, 1, 1], rotation=3 * [True], omega_lab=[1, 1, 1]) p2 = self.system.part.add(pos=[1, 1, 1], rotation=3 * [True]) - p2.vs_auto_relate_to(p1) - np.testing.assert_array_equal(np.copy(p2.quat), [1, 0, 0, 0]) - self.system.integrator.run(1) - np.testing.assert_array_equal(np.copy(p2.quat), [1, 0, 0, 0]) - # Now check that quaternion of the virtual particle gets updated. - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative( - have_quaternion=True) - self.system.integrator.run(1) - self.assertRaises(AssertionError, np.testing.assert_array_equal, - np.copy(p2.quat), [1, 0, 0, 0]) + p2.vs_auto_relate_to(p1, couple_to_lb=True) + np.testing.assert_array_almost_equal(np.copy(p1.quat), [1, 0, 0, 0]) + np.testing.assert_array_almost_equal(np.copy(p2.quat), [1, 0, 0, 0]) + self.assertEqual(p1.propagation, Propagation.SYSTEM_DEFAULT) + self.assertEqual(p2.propagation, Propagation.TRANS_VS_RELATIVE | + Propagation.ROT_VS_RELATIVE | Propagation.TRANS_LB_MOMENTUM_EXCHANGE) # co-aligned case p2.vs_quat = (1, 0, 0, 0) @@ -149,17 +120,17 @@ def test_vs_quat(self): def test_vs_exceptions(self): system = self.system - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() system.time_step = 0.01 system.cell_system.skin = 0.1 system.min_global_cut = 0.1 + Propagation = espressomd.propagation.Propagation p1 = system.part.add(pos=[0.0, 0.0, 0.0], rotation=3 * [True], id=1) p2 = system.part.add(pos=[1.0, 1.0, 1.0], rotation=3 * [True], id=2) p3 = system.part.add(pos=[1.0, 1.0, 1.0], rotation=3 * [True], id=3) p4 = system.part.add(pos=[1.0, 1.0, 1.0], rotation=3 * [True], id=4) # dangling virtual sites are not allowed with self.assertRaisesRegex(Exception, "Particle with id 4 is a dangling virtual site"): - p4.virtual = True + p4.propagation = Propagation.TRANS_VS_RELATIVE | Propagation.ROT_VS_RELATIVE self.assertEqual(p4.vs_relative[0], -1) system.integrator.run(0, recalc_forces=True) p4.remove() @@ -174,19 +145,18 @@ def test_vs_exceptions(self): # relating to a deleted particle is not allowed with self.assertRaisesRegex(Exception, "No real particle with id 3 for virtual site with id 2"): p2.vs_auto_relate_to(p3) + p2.propagation = Propagation.TRANS_VS_RELATIVE | Propagation.ROT_VS_RELATIVE p3.remove() system.integrator.run(0, recalc_forces=True) if system.cell_system.get_state()["n_nodes"] > 1: with self.assertRaisesRegex(Exception, r"The distance between virtual and non-virtual particle \([0-9\.]+\) is larger than the minimum global cutoff"): p2.vs_auto_relate_to(p1) # If overridden this check should not raise an exception - system.virtual_sites.override_cutoff_check = True - p2.vs_auto_relate_to(p1) + p2.vs_auto_relate_to(p1, override_cutoff_check=True) def test_pos_vel_forces(self): system = self.system system.cell_system.skin = 0.3 - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() system.time_step = 0.004 # Check setting of min_global_cut @@ -203,7 +173,7 @@ def test_pos_vel_forces(self): p = system.part.add(rotation=3 * [True], pos=pos) p.vs_auto_relate_to(p1) # Was the particle made virtual - self.assertTrue(p.virtual) + self.assertTrue(p.is_virtual()) # Are vs relative to id and vs_r = p.vs_relative # id @@ -260,7 +230,6 @@ def run_test_lj(self): get lost or are outdated in the short range loop. """ system = self.system - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() # Parameters n = 40 phi = 0.6 @@ -370,7 +339,6 @@ def test_zz_pressure_tensor(self): system.cell_system.skin = 0.1 system.min_global_cut = 0.2 # Should not have a pressure - system.virtual_sites = espressomd.virtual_sites.VirtualSitesOff() pressure_tensor_vs = system.analysis.pressure_tensor()[ "virtual_sites", 0] p_vs = system.analysis.pressure()["virtual_sites", 0] @@ -378,7 +346,6 @@ def test_zz_pressure_tensor(self): np.testing.assert_allclose(p_vs, 0., atol=1e-10) # vs relative contrib - system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() p0 = system.part.add(id=0, pos=(0.0, 0.0, 0.0)) p1 = system.part.add(id=1, pos=(0.1, 0.1, 0.1), ext_force=(1, 2, 3)) p2 = system.part.add(id=2, pos=(0.1, 0.0, 0.0), ext_force=(-1, 0, 0)) diff --git a/testsuite/python/virtual_sites_relative_pbc.py b/testsuite/python/virtual_sites_relative_pbc.py index 745dc6c7bd6..530d96e2793 100644 --- a/testsuite/python/virtual_sites_relative_pbc.py +++ b/testsuite/python/virtual_sites_relative_pbc.py @@ -20,7 +20,7 @@ import unittest as ut import unittest_decorators as utx import espressomd -import espressomd.virtual_sites +import espressomd.propagation import espressomd.lees_edwards import numpy as np @@ -42,15 +42,12 @@ class Test(ut.TestCase): system.min_global_cut = 1.5 * vs_dist system.cell_system.skin = 0.1 - def setUp(self): - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesRelative() - def tearDown(self): self.system.part.clear() - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesOff() self.system.lees_edwards.protocol = None def check_pbc(self): + Propagation = espressomd.propagation.Propagation system = self.system vs_dist = self.vs_dist box_l = system.box_l[0] @@ -102,7 +99,8 @@ def check_pos(p, ref_image_box, ref_pos): (eps * 2.0 + vs_dist * 1.0), 1. - le_dir * le_offset, 1.] pos_away = pos_near - [le_dir * vs_dist, 0., 0.] real_kwargs = {"v": le_dir * v * director, "director": director} - virt_kwargs = {"virtual": True} + virt_kwargs = { + "propagation": Propagation.TRANS_VS_RELATIVE | Propagation.ROT_VS_RELATIVE} # case 1: virtual site goes through boundary before real particle # case 2: virtual site goes through boundary after real particle # In the second time step, the virtual site and real particle are @@ -182,10 +180,12 @@ def test_pbc(self): def test_image_box_update(self): system = self.system + Propagation = espressomd.propagation.Propagation # virtual site image box is overriden by the real particle image box real_part = system.part.add(pos=[0., 0., +self.vs_dist / 2.]) virt_part = system.part.add( - pos=[0., 0., -self.vs_dist / 2. + 4. * system.box_l[2]], virtual=True) + pos=[0., 0., -self.vs_dist / 2. + 4. * system.box_l[2]], + propagation=Propagation.TRANS_VS_RELATIVE | Propagation.ROT_VS_RELATIVE) virt_part.vs_auto_relate_to(real_part) np.testing.assert_array_equal(np.copy(real_part.image_box), [0, 0, 0]) np.testing.assert_array_equal(np.copy(virt_part.image_box), [0, 0, 3]) diff --git a/testsuite/python/virtual_sites_tracers_common.py b/testsuite/python/virtual_sites_tracers_common.py index 3de7dfa0d8b..06aca47ae80 100644 --- a/testsuite/python/virtual_sites_tracers_common.py +++ b/testsuite/python/virtual_sites_tracers_common.py @@ -21,8 +21,8 @@ import espressomd import espressomd.lb import espressomd.shapes -import espressomd.virtual_sites import espressomd.utils +import espressomd.propagation import tests_common import unittest_decorators as utx @@ -49,10 +49,7 @@ def set_lb(self, ext_force_density=(0, 0, 0), dir_walls=2): kT=0.0, agrid=1., density=1., kinematic_viscosity=1.8, tau=self.system.time_step, ext_force_density=ext_force_density) self.system.lb = self.lbf - self.system.thermostat.set_lb( - LB_fluid=self.lbf, - act_on_virtual=False, - gamma=1) + self.system.thermostat.set_lb(LB_fluid=self.lbf, gamma=1.) # Setup boundaries normal = [0, 0, 0] @@ -66,22 +63,10 @@ def set_lb(self, ext_force_density=(0, 0, 0), dir_walls=2): espressomd.utils.handle_errors("setup") - def test_aa_method_switching(self): - # Virtual sites should be disabled by default - self.assertIsInstance( - self.system.virtual_sites, - espressomd.virtual_sites.VirtualSitesOff) - - # Switch implementation - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesInertialessTracers() - self.assertIsInstance( - self.system.virtual_sites, espressomd.virtual_sites.VirtualSitesInertialessTracers) - @utx.skipIfMissingFeatures("EXTERNAL_FORCES") def test_ab_single_step(self): self.set_lb() self.system.part.clear() - self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesInertialessTracers() # Random velocities self.lbf[:, :, :].velocity = np.random.random((*self.lbf.shape, 3)) @@ -91,7 +76,10 @@ def test_ab_single_step(self): self.system.box_l * 0.49, self.system.box_l, self.system.box_l * 0.99]: - p = self.system.part.add(pos=pos, ext_force=force, virtual=True) + p = self.system.part.add( + pos=pos, + ext_force=force, + propagation=espressomd.propagation.Propagation.TRANS_LB_TRACER) coupling_pos = p.pos # Nodes to which forces will be interpolated @@ -131,7 +119,6 @@ def test_advection(self): for direction in [0, 1, 2]: # System setup system = self.system - system.virtual_sites = espressomd.virtual_sites.VirtualSitesInertialessTracers() # LB setup with walls ext_force = [0., 0., 0.] @@ -148,7 +135,9 @@ def test_advection(self): # Add tracer in the fluid domain pos_initial = [3.5, 3.5, 3.5] pos_initial[direction] = 0.5 - p = system.part.add(pos=pos_initial, virtual=True) + p = system.part.add( + pos=pos_initial, + propagation=espressomd.propagation.Propagation.TRANS_LB_TRACER) # Perform integration system.time = 0 @@ -163,17 +152,15 @@ def test_advection(self): system.lb = None def test_zz_exceptions_without_lb(self): - """Check behaviour without lb. Ignore non-virtual particles, complain on - virtual ones. - + """ + Check behaviour without LB. Ignore real particles, complain on tracers. """ self.set_lb() system = self.system - system.virtual_sites = espressomd.virtual_sites.VirtualSitesInertialessTracers() system.lb = None system.part.clear() p = system.part.add(pos=(0, 0, 0)) system.integrator.run(1) - p.virtual = True + p.propagation = espressomd.propagation.Propagation.TRANS_LB_TRACER with self.assertRaisesRegex(Exception, "LB needs to be active for inertialess tracers"): system.integrator.run(1)