diff --git a/.github/actions/build_and_check/action.yml b/.github/actions/build_and_check/action.yml index 6ba63992839..8b41a8ad119 100644 --- a/.github/actions/build_and_check/action.yml +++ b/.github/actions/build_and_check/action.yml @@ -6,7 +6,7 @@ runs: - run: | brew install boost boost-mpi fftw brew install hdf5-mpi - pip3 install -c requirements.txt numpy cython h5py scipy + pip3 install -c requirements.txt numpy "cython<3.0" h5py scipy shell: bash if: runner.os == 'macOS' - run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index cbc4d8712fc..7c40cb72a7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,7 +247,7 @@ endif() # Python interpreter and Cython interface library if(ESPRESSO_BUILD_WITH_PYTHON) find_package(Python 3.9 REQUIRED COMPONENTS Interpreter Development NumPy) - find_package(Cython 0.29.21...<3.0 REQUIRED) + find_package(Cython 0.29.21...<3.0.8 REQUIRED) find_program(IPYTHON_EXECUTABLE NAMES jupyter ipython3 ipython) endif() diff --git a/doc/doxygen/Doxyfile.in b/doc/doxygen/Doxyfile.in index e98956f1bd0..e4cc02ded2e 100644 --- a/doc/doxygen/Doxyfile.in +++ b/doc/doxygen/Doxyfile.in @@ -319,7 +319,7 @@ TOC_INCLUDE_HEADINGS = 5 # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. -AUTOLINK_SUPPORT = YES +AUTOLINK_SUPPORT = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this diff --git a/doc/sphinx/integration.rst b/doc/sphinx/integration.rst index 9e2a4e3967f..2c82f184043 100644 --- a/doc/sphinx/integration.rst +++ b/doc/sphinx/integration.rst @@ -401,15 +401,14 @@ To add a thermostat, call the appropriate setter:: The different thermostats available in |es| will be described in the following subsections. -You may combine different thermostats at your own risk by turning them on -one by one. The list of active thermostats can be cleared at any time with +You may combine different thermostats by turning them on sequentially. +Not all combinations of thermostats are sensible, though, and some +integrators only work with a specific thermostat. The list of possible +combinations of integrators and thermostats is hardcoded and automatically +check against at the start of integration. +Note that there is only one temperature for all thermostats. +The list of active thermostats can be cleared at any time with :py:meth:`system.thermostat.turn_off() `. -Not all combinations of thermostats are allowed, though (see -:py:func:`espressomd.thermostat.AssertThermostatType` for details). -Some integrators only work with a specific thermostat and throw an -error otherwise. Note that there is only one temperature for all -thermostats, although for some thermostats like the Langevin thermostat, -particles can be assigned individual temperatures. Since |es| does not enforce a particular unit system, it cannot know about the current value of the Boltzmann constant. Therefore, when specifying diff --git a/doc/sphinx/inter_bonded.rst b/doc/sphinx/inter_bonded.rst index e56a710222b..890224f3458 100644 --- a/doc/sphinx/inter_bonded.rst +++ b/doc/sphinx/inter_bonded.rst @@ -197,8 +197,9 @@ A thermalized bond can be instantiated via thermalized_bond = espressomd.interactions.ThermalizedBond( temp_com=, gamma_com=, temp_distance=, gamma_distance=, - r_cut=, seed=) + r_cut=) system.bonded_inter.add(thermalized_bond) + system.thermostat.set_thermalized_bond(seed=) This bond can be used to apply Langevin thermalization on the centre of mass and the distance of a particle pair. Each thermostat can have its own diff --git a/doc/tutorials/polymers/polymers.ipynb b/doc/tutorials/polymers/polymers.ipynb index ed3ed3bb8d3..18320e7aeea 100644 --- a/doc/tutorials/polymers/polymers.ipynb +++ b/doc/tutorials/polymers/polymers.ipynb @@ -307,7 +307,7 @@ " kinematic_viscosity=5, tau=system.time_step,\n", " single_precision=True)\n", " system.lb = lbf\n", - " system.thermostat.set_lb(LB_fluid=lbf, gamma=gamma, seed=42)" + " system.thermostat.set_lb(LB_fluid=lbf, gamma=gamma, seed=0)" ] }, { @@ -326,6 +326,7 @@ "outputs": [], "source": [ "import logging\n", + "import tqdm\n", "import sys\n", "\n", "import numpy as np\n", @@ -419,7 +420,7 @@ " rhs = np.zeros(LOOPS)\n", " rfs = np.zeros(LOOPS)\n", " rgs = np.zeros(LOOPS)\n", - " for i in range(LOOPS):\n", + " for i in tqdm.trange(LOOPS):\n", " system.integrator.run(STEPS)\n", " rhs[i] = system.analysis.calc_rh(\n", " chain_start=0,\n", diff --git a/maintainer/CI/build_cmake.sh b/maintainer/CI/build_cmake.sh index 66cc2671f5a..7a95caaae96 100755 --- a/maintainer/CI/build_cmake.sh +++ b/maintainer/CI/build_cmake.sh @@ -404,7 +404,7 @@ if [ "${with_coverage}" = true ] || [ "${with_coverage_python}" = true ]; then lcov --gcov-tool "${GCOV:-gcov}" -q --remove coverage.info '/usr/*' --output-file coverage.info # filter out system lcov --gcov-tool "${GCOV:-gcov}" -q --remove coverage.info '*/doc/*' --output-file coverage.info # filter out docs if [ -d _deps/ ]; then - lcov --gcov-tool "${GCOV:-gcov}" -q --remove coverage.info $(realpath _deps/)'/*' --output-file coverage.info # filter out docs + lcov --gcov-tool "${GCOV:-gcov}" -q --remove coverage.info $(realpath _deps/)'/*' --output-file coverage.info # filter out external projects fi fi if [ "${with_coverage_python}" = true ]; then diff --git a/requirements.txt b/requirements.txt index 039abc76e21..ea77f9d6c09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # build system -cython>=0.29.21,<3.0 +cython>=0.29.21,<=3.0.7 setuptools>=59.6.0 # required scientific packages numpy>=1.23 diff --git a/samples/dancing.py b/samples/dancing.py index 3404d1bd7eb..923334bfe67 100644 --- a/samples/dancing.py +++ b/samples/dancing.py @@ -49,6 +49,7 @@ system.cell_system.skin = 0.4 system.periodicity = [False, False, False] +system.thermostat.set_stokesian(kT=0.) system.integrator.set_stokesian_dynamics( viscosity=1.0, radii={0: 1.0}, approximation_method=sd_method) diff --git a/samples/drude_bmimpf6.py b/samples/drude_bmimpf6.py index 8579c480573..fa07f15643d 100644 --- a/samples/drude_bmimpf6.py +++ b/samples/drude_bmimpf6.py @@ -258,10 +258,11 @@ def combination_rule_sigma(rule, sig1, sig2): if args.drude: print("-->Adding Drude related bonds") + system.thermostat.set_thermalized_bond(seed=123) thermalized_dist_bond = espressomd.interactions.ThermalizedBond( temp_com=temperature_com, gamma_com=gamma_com, temp_distance=temperature_drude, gamma_distance=gamma_drude, - r_cut=min(lj_sigmas.values()) * 0.5, seed=123) + r_cut=min(lj_sigmas.values()) * 0.5) harmonic_bond = espressomd.interactions.HarmonicBond( k=k_drude, r_0=0.0, r_cut=1.0) system.bonded_inter.add(thermalized_dist_bond) diff --git a/samples/load_checkpoint.py b/samples/load_checkpoint.py index 662d95e9c0d..0c6f845dfdf 100644 --- a/samples/load_checkpoint.py +++ b/samples/load_checkpoint.py @@ -60,10 +60,6 @@ print("\n### system.part test ###") print(f"system.part.all().pos = {system.part.all().pos}") -# test "system.thermostat" -print("\n### system.thermostat test ###") -print(f"system.thermostat.get_state() = {system.thermostat.get_state()}") - # test "p3m" print("\n### p3m test ###") print(f"p3m.get_params() = {p3m.get_params()}") diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6a53208beae..6f93be1162b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -32,7 +32,6 @@ add_library( errorhandling.cpp forces.cpp ghosts.cpp - global_ghost_flags.cpp immersed_boundaries.cpp integrate.cpp npt.cpp diff --git a/src/core/PropagationMode.hpp b/src/core/PropagationMode.hpp index ff87400f10a..61ef7c70b7d 100644 --- a/src/core/PropagationMode.hpp +++ b/src/core/PropagationMode.hpp @@ -20,9 +20,7 @@ #pragma once namespace PropagationMode { -/** - * @brief Flags to create bitmasks for propagation modes. - */ +/** @brief Flags to create bitmasks for propagation modes. */ enum PropagationMode : int { NONE = 0, SYSTEM_DEFAULT = 1 << 0, @@ -42,11 +40,23 @@ enum PropagationMode : int { }; } // namespace PropagationMode -/** \name Integrator switches */ +/** @brief Integrator identifier. */ 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, + INTEG_METHOD_SD = 4, +}; + +/** @brief Thermostat flags. */ +enum ThermostatFlags : int { + THERMO_OFF = 0, + THERMO_LANGEVIN = 1 << 0, + THERMO_BROWNIAN = 1 << 1, + THERMO_NPT_ISO = 1 << 2, + THERMO_LB = 1 << 3, + THERMO_SD = 1 << 4, + THERMO_DPD = 1 << 5, + THERMO_BOND = 1 << 6, }; diff --git a/src/core/TabulatedPotential.hpp b/src/core/TabulatedPotential.hpp index 6354a9d4da8..ef0412a3bcc 100644 --- a/src/core/TabulatedPotential.hpp +++ b/src/core/TabulatedPotential.hpp @@ -38,7 +38,7 @@ struct TabulatedPotential { double minval = -1.0; /** Position on the x-axis of the last tabulated value. */ double maxval = -1.0; - /** %Distance on the x-axis between tabulated values. */ + /** Distance on the x-axis between tabulated values. */ double invstepsize = 0.0; /** Tabulated forces. */ std::vector force_tab; diff --git a/src/core/bonded_interactions/CMakeLists.txt b/src/core/bonded_interactions/CMakeLists.txt index 3c4ad3afa5d..08e44c9a8ea 100644 --- a/src/core/bonded_interactions/CMakeLists.txt +++ b/src/core/bonded_interactions/CMakeLists.txt @@ -17,13 +17,4 @@ # along with this program. If not, see . # -target_sources( - espresso_core - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/angle_cosine.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/angle_cossquare.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/bonded_interaction_data.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/bonded_tab.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/fene.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rigid_bond.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/thermalized_bond.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/thermalized_bond_utils.cpp) +target_sources(espresso_core PRIVATE bonded_interaction_data.cpp) diff --git a/src/core/bonded_interactions/angle_common.hpp b/src/core/bonded_interactions/angle_common.hpp index 8c12849be3f..be54f82fbf1 100644 --- a/src/core/bonded_interactions/angle_common.hpp +++ b/src/core/bonded_interactions/angle_common.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 ANGLE_COMMON_H -#define ANGLE_COMMON_H + +#pragma once + /** \file * Common code for functions calculating angle forces. */ @@ -95,5 +96,3 @@ angle_generic_force(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2, auto f_mid = -(f_left + f_right); return std::make_tuple(f_mid, f_left, f_right); } - -#endif /* ANGLE_COMMON_H */ diff --git a/src/core/bonded_interactions/angle_cosine.cpp b/src/core/bonded_interactions/angle_cosine.cpp deleted file mode 100644 index 80d0bc818ef..00000000000 --- a/src/core/bonded_interactions/angle_cosine.cpp +++ /dev/null @@ -1,35 +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 . - */ -/** \file - * - * Implementation of \ref angle_cosine.hpp - */ -#include "angle_cosine.hpp" - -#include - -AngleCosineBond::AngleCosineBond(double bend, double phi0) { - - this->bend = bend; - this->phi0 = phi0; - this->cos_phi0 = cos(phi0); - this->sin_phi0 = sin(phi0); -} diff --git a/src/core/bonded_interactions/angle_cosine.hpp b/src/core/bonded_interactions/angle_cosine.hpp index 4e455b0b1fc..c67e76ee9c4 100644 --- a/src/core/bonded_interactions/angle_cosine.hpp +++ b/src/core/bonded_interactions/angle_cosine.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 ANGLE_COSINE_H -#define ANGLE_COSINE_H + +#pragma once + /** \file * Routines to calculate the angle energy or/and and force * for a particle triple using the potential described in @@ -50,7 +51,12 @@ struct AngleCosineBond { static constexpr int num = 2; - AngleCosineBond(double bend, double phi0); + AngleCosineBond(double bend, double phi0) { + this->bend = bend; + this->phi0 = phi0; + this->cos_phi0 = cos(phi0); + this->sin_phi0 = sin(phi0); + } std::tuple forces(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; @@ -98,5 +104,3 @@ inline double AngleCosineBond::energy(Utils::Vector3d const &vec1, // trig identity: cos(phi - phi0) = cos(phi)cos(phi0) + sin(phi)sin(phi0) return bend * (1 - (cos_phi * cos_phi0 + sin_phi * sin_phi0)); } - -#endif /* ANGLE_COSINE_H */ diff --git a/src/core/bonded_interactions/angle_cossquare.cpp b/src/core/bonded_interactions/angle_cossquare.cpp deleted file mode 100644 index 27ab9c3b2d3..00000000000 --- a/src/core/bonded_interactions/angle_cossquare.cpp +++ /dev/null @@ -1,33 +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 . - */ -/** \file - * - * Implementation of \ref angle_cossquare.hpp - */ -#include "angle_cossquare.hpp" - -#include - -AngleCossquareBond::AngleCossquareBond(double bend, double phi0) { - this->bend = bend; - this->phi0 = phi0; - this->cos_phi0 = cos(phi0); -} diff --git a/src/core/bonded_interactions/angle_cossquare.hpp b/src/core/bonded_interactions/angle_cossquare.hpp index b29d6c9cbeb..1b59aa53acc 100644 --- a/src/core/bonded_interactions/angle_cossquare.hpp +++ b/src/core/bonded_interactions/angle_cossquare.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 ANGLE_COSSQUARE_H -#define ANGLE_COSSQUARE_H + +#pragma once + /** \file * Routines to calculate the angle energy or/and and force * for a particle triple using the potential described in @@ -31,6 +32,7 @@ #include #include +#include #include /** Parameters for three-body angular potential (cossquare). */ @@ -46,7 +48,11 @@ struct AngleCossquareBond { static constexpr int num = 2; - AngleCossquareBond(double bend, double phi0); + AngleCossquareBond(double bend, double phi0) { + this->bend = bend; + this->phi0 = phi0; + this->cos_phi0 = cos(phi0); + } std::tuple forces(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; @@ -87,5 +93,3 @@ inline double AngleCossquareBond::energy(Utils::Vector3d const &vec1, auto const cos_phi = calc_cosine(vec1, vec2, true); return 0.5 * bend * Utils::sqr(cos_phi - cos_phi0); } - -#endif /* ANGLE_COSSQUARE_H */ diff --git a/src/core/bonded_interactions/angle_harmonic.hpp b/src/core/bonded_interactions/angle_harmonic.hpp index f0c813d3766..407209e525c 100644 --- a/src/core/bonded_interactions/angle_harmonic.hpp +++ b/src/core/bonded_interactions/angle_harmonic.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 ANGLE_HARMONIC_H -#define ANGLE_HARMONIC_H + +#pragma once + /** \file * Routines to calculate the angle energy or/and and force * for a particle triple using the potential described in @@ -91,5 +92,3 @@ inline double AngleHarmonicBond::energy(Utils::Vector3d const &vec1, auto const phi = acos(cos_phi); return 0.5 * bend * Utils::sqr(phi - phi0); } - -#endif /* ANGLE_HARMONIC_H */ diff --git a/src/core/bonded_interactions/bonded_coulomb.hpp b/src/core/bonded_interactions/bonded_coulomb.hpp index 9665bffe422..d34496047a3 100644 --- a/src/core/bonded_interactions/bonded_coulomb.hpp +++ b/src/core/bonded_interactions/bonded_coulomb.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_BN_IA_BONDED_COULOMB_HPP -#define CORE_BN_IA_BONDED_COULOMB_HPP + +#pragma once + /** \file * Routines to calculate the bonded Coulomb potential between * particle pairs. @@ -33,9 +34,9 @@ #include -/** Parameters for %Coulomb bond Potential */ +/** Parameters for Coulomb bond Potential */ struct BondedCoulomb { - /** %Coulomb prefactor */ + /** Coulomb prefactor */ double prefactor; double cutoff() const { return 0.; } @@ -58,7 +59,7 @@ struct BondedCoulomb { /** Compute the bonded Coulomb pair force. * @param[in] q1q2 Product of the particle charges. - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional BondedCoulomb::force(double const q1q2, Utils::Vector3d const &dx) const { @@ -74,7 +75,7 @@ BondedCoulomb::force(double const q1q2, Utils::Vector3d const &dx) const { /** Compute the bonded Coulomb pair energy. * @param[in] q1q2 Product of the particle charges. - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional BondedCoulomb::energy(double const q1q2, Utils::Vector3d const &dx) const { @@ -85,5 +86,3 @@ BondedCoulomb::energy(double const q1q2, Utils::Vector3d const &dx) const { return .0; #endif } - -#endif diff --git a/src/core/bonded_interactions/bonded_coulomb_sr.hpp b/src/core/bonded_interactions/bonded_coulomb_sr.hpp index d59f865c11e..0e4023b8c17 100644 --- a/src/core/bonded_interactions/bonded_coulomb_sr.hpp +++ b/src/core/bonded_interactions/bonded_coulomb_sr.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_BN_IA_BONDED_COULOMB_SR_HPP -#define CORE_BN_IA_BONDED_COULOMB_SR_HPP + +#pragma once + /** \file * Routines to calculate the short-range part of the bonded Coulomb potential * between particle pairs. Can be used to subtract certain intramolecular @@ -37,7 +38,7 @@ #include #include -/** Parameters for %Coulomb bond short-range Potential */ +/** Parameters for Coulomb bond short-range Potential */ struct BondedCoulombSR { /** charge factor */ double q1q2; @@ -67,8 +68,8 @@ struct BondedCoulombSR { }; /** Compute the short-range bonded Coulomb pair force. - * @param[in] dx %Distance between the particles. - * @param[in] kernel %Coulomb force kernel. + * @param[in] dx Distance between the particles. + * @param[in] kernel Coulomb force kernel. */ inline boost::optional BondedCoulombSR::force( Utils::Vector3d const &dx, @@ -84,8 +85,8 @@ inline boost::optional BondedCoulombSR::force( /** Compute the short-range bonded Coulomb pair energy. * @param[in] p1 First particle. * @param[in] p2 Second particle. - * @param[in] dx %Distance between the particles. - * @param[in] kernel %Coulomb energy kernel. + * @param[in] dx Distance between the particles. + * @param[in] kernel Coulomb energy kernel. */ inline boost::optional BondedCoulombSR::energy( Particle const &p1, Particle const &p2, Utils::Vector3d const &dx, @@ -98,5 +99,3 @@ inline boost::optional BondedCoulombSR::energy( return 0.; #endif } - -#endif diff --git a/src/core/bonded_interactions/bonded_interaction_data.cpp b/src/core/bonded_interactions/bonded_interaction_data.cpp index af0e876bc38..ab4750e9b57 100644 --- a/src/core/bonded_interactions/bonded_interaction_data.cpp +++ b/src/core/bonded_interactions/bonded_interaction_data.cpp @@ -17,7 +17,9 @@ * along with this program. If not, see . */ #include "bonded_interaction_data.hpp" +#include "rigid_bond.hpp" #include "system/System.hpp" +#include "thermalized_bond.hpp" #include #include @@ -59,7 +61,23 @@ double maximal_cutoff_bonded() { } void BondedInteractionsMap::on_ia_change() { + n_thermalized_bonds = 0; +#ifdef BOND_CONSTRAINT + n_rigid_bonds = 0; +#endif + for (auto &kv : *this) { + if (boost::get(&(*kv.second)) != nullptr) { + ++n_thermalized_bonds; + } +#ifdef BOND_CONSTRAINT + if (boost::get(&(*kv.second)) != nullptr) { + ++n_rigid_bonds; + } +#endif + } if (System::is_system_set()) { - System::get_system().on_short_range_ia_change(); + auto &system = System::get_system(); + system.on_short_range_ia_change(); + system.on_thermostat_param_change(); // thermalized bonds } } diff --git a/src/core/bonded_interactions/bonded_interaction_data.hpp b/src/core/bonded_interactions/bonded_interaction_data.hpp index 6cccd6749fd..77e3ccd2f0c 100644 --- a/src/core/bonded_interactions/bonded_interaction_data.hpp +++ b/src/core/bonded_interactions/bonded_interaction_data.hpp @@ -143,10 +143,18 @@ class BondedInteractionsMap { auto get_zero_based_type(int bond_id) const { return contains(bond_id) ? at(bond_id)->which() : 0; } + auto get_n_thermalized_bonds() const { return n_thermalized_bonds; } +#ifdef BOND_CONSTRAINT + auto get_n_rigid_bonds() const { return n_rigid_bonds; } +#endif private: container_type m_params = {}; key_type next_key = static_cast(0); + int n_thermalized_bonds = 0; +#ifdef BOND_CONSTRAINT + int n_rigid_bonds = 0; +#endif void on_ia_change(); }; diff --git a/src/core/bonded_interactions/bonded_interaction_utils.hpp b/src/core/bonded_interactions/bonded_interaction_utils.hpp index b68f2481b0f..9f0f60c9a30 100644 --- a/src/core/bonded_interactions/bonded_interaction_utils.hpp +++ b/src/core/bonded_interactions/bonded_interaction_utils.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 CORE_BN_IA_BONDED_INTERACTION_UTILS_HPP -#define CORE_BN_IA_BONDED_INTERACTION_UTILS_HPP + +#pragma once #include "bonded_interaction_data.hpp" @@ -67,5 +67,3 @@ inline bool pair_bond_enum_exists_between(Particle const &p1, return pair_bond_enum_exists_on(p1, p2) or pair_bond_enum_exists_on(p2, p1); } - -#endif diff --git a/src/core/bonded_interactions/bonded_interactions.dox b/src/core/bonded_interactions/bonded_interactions.dox index b559a6c6996..a7d454ce1cf 100644 --- a/src/core/bonded_interactions/bonded_interactions.dox +++ b/src/core/bonded_interactions/bonded_interactions.dox @@ -34,21 +34,21 @@ * calculation functions. * - Register the new bond type. * * ScriptInterface: - * - Define the %ScriptInterface class of the new bond type, which serves as + * - Define the @c ScriptInterface class of the new bond type, which serves as * the connection between the C++ core and the Python representation of the * bond. * * Python interface: * - Import the definition of the interaction struct from the core * - Implement a class for the bonded interaction derived from the Python - * \c BondedInteraction base class + * @c BondedInteraction base class * * @subsection bondedIA_new_struct Defining the new interaction * - * Every interaction resides in its own source .cpp and .hpp files. A simple - * example for a bonded interaction is the FENE bond in @ref fene.hpp and - * @ref fene.cpp. Use these two files as templates for your interaction. + * Every interaction resides in its own source .hpp file. A simple + * example for a bonded interaction is the FENE bond in @ref fene.hpp. + * Use this file as template for your new interaction. * - * The first step is to create a new \c struct which represents your new + * The first step is to create a new @c struct which represents your new * bond type inside the .hpp file. It needs to have the following members: * * * @code{.cpp} @@ -59,7 +59,7 @@ * * @code{.cpp} * double cutoff() const { return r0 + drmax; } * @endcode - * The return value of \c cutoff() should be as large as the interaction + * The return value of @c cutoff() should be as large as the interaction * range of the new interaction. This is only relevant to pairwise bonds. * In all other cases, the return value should be 0, namely angle bonds, * dihedral bonds as well as other bonds that don't have an interaction @@ -70,9 +70,9 @@ * boost::optional force(Utils::Vector3d const &dx) const; * @endcode * This function returns the bond force. If it is a bond involving three - * or four particles, a \c std::tuple with three or four force vectors + * or four particles, a @c std::tuple with three or four force vectors * has to be returned, respectively. - * - The returned value is in a \c boost::optional container if the bond is + * - The returned value is in a @c boost::optional container if the bond is * breakable. If the bond is broken, the returned object is empty; this * will stop the integrator with a runtime error. * - The function can make use of a pre-calculated distance vector (\p dx) @@ -95,7 +95,7 @@ * @endcode * All values the bond needs to function properly should be passed as * arguments to this constructor. - * * A template function for serialization called \c serialize. This is for + * * A template function for serialization called @c serialize. This is for * communication between nodes in parallel computations. * The following function can serve as a starting point. * @code{.cpp} @@ -142,7 +142,7 @@ * of the functions @ref calc_bond_pair_force(), @ref * calc_bonded_three_body_force() or @ref calc_bonded_four_body_force(), * depending on how many bond partners there are. - * - Add the new entry to the \c if - \c else chain, like in the following + * - Add the new entry to the @c if - @c else chain, like in the following * example * @code{.cpp} * // ... @@ -153,9 +153,9 @@ * @endcode * * In energy_inline.hpp: * - A call to the new bond's force calculation needs to be placed in @ref - * calc_bonded_energy. Find the \c if - \c else chain that corresponds + * calc_bonded_energy. Find the @c if - @c else chain that corresponds * to the correct number of bond partners. - * - Add the new entry to the \c if - \c else chain, like in the following + * - Add the new entry to the @c if - @c else chain, like in the following * example * @code{.cpp} * // ... @@ -177,53 +177,47 @@ * @subsection bondedIA_new_script_interface Registering the new interaction in the ScriptInterface * * * In src/script_interface/interactions/BondedInteraction.hpp: - * Add a new class representing your new bond type in the %ScriptInterface. + * Add a new class representing your new bond type in the @c ScriptInterface. * - We recommend that the new class has the same name as the interaction in the core. - * - You can use ScriptInterface::Interactions::FeneBond as a template. - * - The class must be derived from ScriptInterface::Interactions::BondedInteraction. + * - You can use @ref ScriptInterface::Interactions::FeneBond as a template. + * - The class must be derived from @ref ScriptInterface::Interactions::BondedInteraction. * - It is recommended to include the statement * @code{.cpp} * using CoreBondedInteraction = ::YourNewBond; * @endcode - * where \c YourNewBond is the core type you defined. + * where @c YourNewBond is the core type you defined. * - Implement a member function with the signature * @code{.cpp} * void construct_bond(VariantMap const ¶ms) override { /* ... */ } * @endcode - * In this function, the member \c m_bonded_ia shall be initialized using - * the parameters that are given in params. Use the constructor - * of \c %FeneBond as a template. An instance of your core type - * \c %YourNewBond should be initialized, which is then used to initialize - * a \c std::shared_ptr to a \ref Bonded_IA_Parameters, which is then - * assigned to \c m_bonded_ia. - * The values of the parameters are extracted from \c params using + * In this function, the member @c m_bonded_ia shall be initialized using + * the parameters that are given in @c params. Use the @c construct_bond() + * method of @c FeneBond as a template. + * An instance of the core type @c YourNewBond should be initialized, + * which is then used to initialize + * a @c std::shared_ptr to a @ref Bonded_IA_Parameters, which is then + * assigned to @c m_bonded_ia. + * The values of the parameters are extracted from @c params using * @code{.cpp} * get_value(params, "parameter_name") * @endcode - * where \c parameter_type is the type of the parameter, e.g. \c double or - * \c int or even \c std::string, and "parameter_name" must be + * where @c parameter_type is the type of the parameter, e.g. @c double or + * @c int or even @c std::string, and "parameter_name" must be * replaced by the name of the respective parameter. This name must be the * same as in the Python interface, but may differ from the name in the * core interaction type. It is, however, recommended to use the same * names for both the Python interface and the ESPResSo core for * consistency whenever possible. - * - Implement a member function called get_struct(), which returns - * the bond parameters, which are stored in m_bonded_ia. - * The return type should be a reference to an object of the new type - * YourNewBond that you defined in the core. If you included the - * above using statement, you can simply copy the routine from - * \c %FeneBond, since CoreBondedInteraction is already set to - * the correct core type. - * - Implement the constructor. We recommend to adapt it from \c %FeneBond. + * - Implement the constructor. We recommend to adapt it from @c FeneBond. * All it needs to do is to register its parameters so they can be set * from Python. For this purpose, call * @code{.cpp} * add_parameters(/* ... */); * @endcode - * inside the constructors. It expects a vector of - * \ref ScriptInterface::AutoParameter. Usually, this vector is initialized + * inside the constructor. It expects a vector of + * @ref ScriptInterface::AutoParameter. Usually, this vector is initialized * using an initializer list, each element of which is in itself a list which - * initializes one instance of \c AutoParameter (see @ref AutoParameter.hpp). + * initializes one instance of @c AutoParameter (see @ref AutoParameter.hpp). * One of many ways to initialize these parameters is to pass the parameter name * as a string, a custom setter and a custom getter function. The parameters * are typically made read-only by passing AutoParameter::read_only @@ -232,13 +226,13 @@ * * * In src/script_interface/interactions/initialize.cpp: * Your new interaction type needs to be registered here so that the - * %ScriptInterface can find it by its name. In the function \c initialize + * @c ScriptInterface can find it by its name. In the function @c initialize * add a new line of the form * @code{.cpp} * om->register_new("Interactions::YourNewBond"); * @endcode - * where \c YourNewBond must be replaced by the name of your new bond type. - * The string is used to match the %ScriptInterface class with the Python + * where @c YourNewBond must be replaced by the name of your new bond type. + * The string is used to match the @c ScriptInterface class with the Python * class (see below). * * @@ -246,7 +240,7 @@ * @subsection bondedIA_new_interface Adding the interaction in the Python interface * * * In file src/python/espressomd/interactions.py: - * - Add the bonded interaction to \c BONDED_IA. + * - Add the bonded interaction to @c BONDED_IA. * The order of the enum values must match the order of types in * @ref Bonded_IA_Parameters exactly: * @code{.py} @@ -263,9 +257,9 @@ * _so_name = "Interactions::YourNewBond" * _type_number = BONDED_IA.YOURNEWBOND * @endcode - * where you put the name of your bond instead of \c YourNewBond. - * This connects the %ScriptInterface class with the Python class. - * The type number is the new enum value from \c BONDED_IA. + * where you put the name of your bond instead of @c YourNewBond. + * This connects the @c ScriptInterface class with the Python class. + * The type number is the new enum value from @c BONDED_IA. * * In file testsuite/python/interactions_bonded_interface.py: * - Add a test case to verify that parameters set and gotten from the * interaction are consistent. diff --git a/src/core/bonded_interactions/bonded_tab.cpp b/src/core/bonded_interactions/bonded_tab.cpp deleted file mode 100644 index 74762482f91..00000000000 --- a/src/core/bonded_interactions/bonded_tab.cpp +++ /dev/null @@ -1,61 +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 . - */ -#include "bonded_interactions/bonded_tab.hpp" - -#include "errorhandling.hpp" - -#include - -#include -#include - -TabulatedBond::TabulatedBond(double min, double max, - std::vector const &energy, - std::vector const &force) { - pot = std::make_shared(min, max, force, energy); -} - -TabulatedDistanceBond::TabulatedDistanceBond(double min, double max, - std::vector const &energy, - std::vector const &force) - : TabulatedBond(min, max, energy, force) { - /* set table limits */ - this->pot->minval = min; - this->pot->maxval = max; -} - -TabulatedAngleBond::TabulatedAngleBond(double min, double max, - std::vector const &energy, - std::vector const &force) - : TabulatedBond(min, max, energy, force) { - /* set table limits */ - this->pot->minval = 0.0; - this->pot->maxval = Utils::pi() + ROUND_ERROR_PREC; -} - -TabulatedDihedralBond::TabulatedDihedralBond(double min, double max, - std::vector const &energy, - std::vector const &force) - : TabulatedBond(min, max, energy, force) { - /* set table limits */ - this->pot->minval = 0.0; - this->pot->maxval = 2.0 * Utils::pi() + ROUND_ERROR_PREC; -} diff --git a/src/core/bonded_interactions/bonded_tab.hpp b/src/core/bonded_interactions/bonded_tab.hpp index 70739fad20d..130461b5d1c 100644 --- a/src/core/bonded_interactions/bonded_tab.hpp +++ b/src/core/bonded_interactions/bonded_tab.hpp @@ -18,14 +18,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_BONDED_INTERACTIONS_TABULATED_HPP -#define CORE_BONDED_INTERACTIONS_TABULATED_HPP + +#pragma once /** \file * Routines to calculate the energy and/or force for particle bonds, angles * and dihedrals via interpolation of lookup tables. - * - * Implementation in \ref bonded_tab.cpp. */ #include "config/config.hpp" @@ -35,6 +33,7 @@ #include "bonded_interactions/dihedral.hpp" #include +#include #include #include @@ -59,7 +58,9 @@ struct TabulatedBond { * @param force @copybrief TabulatedPotential::force_tab */ TabulatedBond(double min, double max, std::vector const &energy, - std::vector const &force); + std::vector const &force) { + pot = std::make_shared(min, max, force, energy); + } private: friend boost::serialization::access; @@ -77,7 +78,11 @@ struct TabulatedDistanceBond : public TabulatedBond { TabulatedDistanceBond(double min, double max, std::vector const &energy, - std::vector const &force); + std::vector const &force) + : TabulatedBond(min, max, energy, force) { + this->pot->minval = min; + this->pot->maxval = max; + } boost::optional force(Utils::Vector3d const &dx) const; boost::optional energy(Utils::Vector3d const &dx) const; @@ -90,7 +95,12 @@ struct TabulatedAngleBond : public TabulatedBond { static constexpr int num = 2; TabulatedAngleBond(double min, double max, std::vector const &energy, - std::vector const &force); + std::vector const &force) + : TabulatedBond(min, max, energy, force) { + this->pot->minval = 0.; + this->pot->maxval = Utils::pi() + ROUND_ERROR_PREC; + } + std::tuple forces(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; double energy(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; @@ -104,7 +114,12 @@ struct TabulatedDihedralBond : public TabulatedBond { TabulatedDihedralBond(double min, double max, std::vector const &energy, - std::vector const &force); + std::vector const &force) + : TabulatedBond(min, max, energy, force) { + this->pot->minval = 0.; + this->pot->maxval = 2. * Utils::pi() + ROUND_ERROR_PREC; + } + boost::optional> forces(Utils::Vector3d const &v12, Utils::Vector3d const &v23, @@ -120,7 +135,7 @@ struct TabulatedDihedralBond : public TabulatedBond { * particles. For distances smaller than the tabulated range it uses a linear * extrapolation based on the first two tabulated force values. * - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional TabulatedDistanceBond::force(Utils::Vector3d const &dx) const { @@ -139,7 +154,7 @@ TabulatedDistanceBond::force(Utils::Vector3d const &dx) const { * extrapolation based on the first two tabulated force values and the first * tabulated energy value. * - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional TabulatedDistanceBond::energy(Utils::Vector3d const &dx) const { @@ -266,5 +281,3 @@ TabulatedDihedralBond::energy(Utils::Vector3d const &v12, return pot->energy(phi); } - -#endif diff --git a/src/core/bonded_interactions/dihedral.hpp b/src/core/bonded_interactions/dihedral.hpp index f5281dd0061..64fa9fde841 100644 --- a/src/core/bonded_interactions/dihedral.hpp +++ b/src/core/bonded_interactions/dihedral.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 DIHEDRAL_H -#define DIHEDRAL_H + +#pragma once + /** \file * Routines to calculate the dihedral energy or/and * force for a particle quadruple. Note that usage of dihedrals @@ -210,5 +211,3 @@ DihedralBond::energy(Utils::Vector3d const &v12, Utils::Vector3d const &v23, return bend * (1. - cos(mult * phi - phase)); } - -#endif diff --git a/src/core/bonded_interactions/fene.cpp b/src/core/bonded_interactions/fene.cpp deleted file mode 100644 index 352cf03650c..00000000000 --- a/src/core/bonded_interactions/fene.cpp +++ /dev/null @@ -1,37 +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 . - */ -/** \file - * - * Implementation of \ref fene.hpp - */ - -#include "fene.hpp" - -#include - -FeneBond::FeneBond(double k, double drmax, double r0) { - this->k = k; - this->drmax = drmax; - this->r0 = r0; - - this->drmax2 = Utils::sqr(this->drmax); - this->drmax2i = 1.0 / this->drmax2; -} diff --git a/src/core/bonded_interactions/fene.hpp b/src/core/bonded_interactions/fene.hpp index 68091784336..0866f9ea663 100644 --- a/src/core/bonded_interactions/fene.hpp +++ b/src/core/bonded_interactions/fene.hpp @@ -18,18 +18,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_BN_IA_FENE_HPP -#define CORE_BN_IA_FENE_HPP + +#pragma once + /** \file * Routines to calculate the FENE potential between particle pairs. - * - * Implementation in \ref fene.cpp. */ #include "config/config.hpp" #include "errorhandling.hpp" #include +#include #include @@ -52,7 +52,14 @@ struct FeneBond { static constexpr int num = 1; - FeneBond(double k, double drmax, double r0); + FeneBond(double k, double drmax, double r0) { + this->k = k; + this->drmax = drmax; + this->r0 = r0; + + this->drmax2 = Utils::sqr(this->drmax); + this->drmax2i = 1. / this->drmax2; + } boost::optional force(Utils::Vector3d const &dx) const; boost::optional energy(Utils::Vector3d const &dx) const; @@ -70,7 +77,7 @@ struct FeneBond { }; /** Compute the FENE bond force. - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional FeneBond::force(Utils::Vector3d const &dx) const { @@ -95,7 +102,7 @@ FeneBond::force(Utils::Vector3d const &dx) const { } /** Compute the FENE bond energy. - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional FeneBond::energy(Utils::Vector3d const &dx) const { @@ -109,5 +116,3 @@ FeneBond::energy(Utils::Vector3d const &dx) const { return -0.5 * k * drmax2 * log(1.0 - dr * dr * drmax2i); } - -#endif diff --git a/src/core/bonded_interactions/harmonic.hpp b/src/core/bonded_interactions/harmonic.hpp index d4af01869f3..ea091a3a559 100644 --- a/src/core/bonded_interactions/harmonic.hpp +++ b/src/core/bonded_interactions/harmonic.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_BN_IA_HARMONIC_HPP -#define CORE_BN_IA_HARMONIC_HPP + +#pragma once + /** \file * Routines to calculate the harmonic bond potential between particle pairs. */ @@ -65,7 +66,7 @@ struct HarmonicBond { }; /** Compute the harmonic bond force. - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional HarmonicBond::force(Utils::Vector3d const &dx) const { @@ -89,7 +90,7 @@ HarmonicBond::force(Utils::Vector3d const &dx) const { } /** Compute the harmonic bond energy. - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional HarmonicBond::energy(Utils::Vector3d const &dx) const { @@ -101,5 +102,3 @@ HarmonicBond::energy(Utils::Vector3d const &dx) const { return 0.5 * k * Utils::sqr(dist - r); } - -#endif diff --git a/src/core/bonded_interactions/quartic.hpp b/src/core/bonded_interactions/quartic.hpp index 24b556d26fc..9d724c58d40 100644 --- a/src/core/bonded_interactions/quartic.hpp +++ b/src/core/bonded_interactions/quartic.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_BN_IA_QUARTIC_HPP -#define CORE_BN_IA_QUARTIC_HPP + +#pragma once + /** \file * Routines to calculate the quartic potential between particle pairs. */ @@ -63,7 +64,7 @@ struct QuarticBond { }; /** Compute the quartic bond force. - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional QuarticBond::force(Utils::Vector3d const &dx) const { @@ -88,7 +89,7 @@ QuarticBond::force(Utils::Vector3d const &dx) const { } /** Compute the quartic bond energy. - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. */ inline boost::optional QuarticBond::energy(Utils::Vector3d const &dx) const { @@ -102,5 +103,3 @@ QuarticBond::energy(Utils::Vector3d const &dx) const { return 0.5 * k0 * dr2 + 0.25 * k1 * Utils::sqr(dr2); } - -#endif diff --git a/src/core/bonded_interactions/rigid_bond.cpp b/src/core/bonded_interactions/rigid_bond.cpp deleted file mode 100644 index 782cdfb8220..00000000000 --- a/src/core/bonded_interactions/rigid_bond.cpp +++ /dev/null @@ -1,36 +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 . - */ -/** \file - * - * Implementation of \ref rigid_bond.hpp - */ - -#include "rigid_bond.hpp" - -int n_rigidbonds = 0; - -RigidBond::RigidBond(double d, double p_tol, double v_tol) { - this->d2 = d * d; - this->p_tol = 2.0 * p_tol; - this->v_tol = v_tol; - - n_rigidbonds++; -} diff --git a/src/core/bonded_interactions/rigid_bond.hpp b/src/core/bonded_interactions/rigid_bond.hpp index 97ab70e2edb..9767def295d 100644 --- a/src/core/bonded_interactions/rigid_bond.hpp +++ b/src/core/bonded_interactions/rigid_bond.hpp @@ -18,22 +18,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef RIGID_BOND_HPP -#define RIGID_BOND_HPP + +#pragma once + /** \file - * Definition of the rigid bond data type. It is utilized by the - * Rattle algorithm. - * - * Implementation in \ref rigid_bond.cpp. + * Definition of the rigid bond data type for the Rattle algorithm. */ #include #include -/** Number of rigid bonds. */ -extern int n_rigidbonds; - /** Parameters for the rigid_bond/SHAKE/RATTLE ALGORITHM */ struct RigidBond { /** Square of the length of Constrained Bond */ @@ -51,7 +46,11 @@ struct RigidBond { static constexpr int num = 1; - RigidBond(double d, double p_tol, double v_tol); + RigidBond(double d, double p_tol, double v_tol) { + this->d2 = d * d; + this->p_tol = 2.0 * p_tol; + this->v_tol = v_tol; + } private: friend boost::serialization::access; @@ -62,5 +61,3 @@ struct RigidBond { ar &v_tol; } }; - -#endif diff --git a/src/core/bonded_interactions/thermalized_bond.cpp b/src/core/bonded_interactions/thermalized_bond.cpp deleted file mode 100644 index 8158d8bfb33..00000000000 --- a/src/core/bonded_interactions/thermalized_bond.cpp +++ /dev/null @@ -1,47 +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 . - */ -/** \file - * - * Implementation of \ref thermalized_bond.hpp - */ - -#include "thermalized_bond.hpp" -#include "system/System.hpp" - -int n_thermalized_bonds = 0; - -ThermalizedBond::ThermalizedBond(double temp_com, double gamma_com, - double temp_distance, double gamma_distance, - double r_cut) { - this->temp_com = temp_com; - this->gamma_com = gamma_com; - this->temp_distance = temp_distance; - this->gamma_distance = gamma_distance; - this->r_cut = r_cut; - - pref1_com = -1.; - pref2_com = -1.; - pref1_dist = -1.; - pref2_dist = -1.; - - n_thermalized_bonds++; - System::get_system().on_thermostat_param_change(); -} diff --git a/src/core/bonded_interactions/thermalized_bond.hpp b/src/core/bonded_interactions/thermalized_bond.hpp index 4cbb9e7e91a..d315fce101e 100644 --- a/src/core/bonded_interactions/thermalized_bond.hpp +++ b/src/core/bonded_interactions/thermalized_bond.hpp @@ -1,4 +1,3 @@ - /* * Copyright (C) 2010-2022 The ESPResSo project * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010 @@ -20,23 +19,19 @@ * along with this program. If not, see . */ -#ifndef THERMALIZED_DIST_H -#define THERMALIZED_DIST_H +#pragma once + /** \file * Routines to thermalize the center of mass and distance of a particle pair. - * - * Implementation in \ref thermalized_bond.cpp. */ -/** number of thermalized bonds */ -extern int n_thermalized_bonds; - #include "Particle.hpp" #include #include +#include #include /** Parameters for Thermalized bond */ @@ -56,7 +51,26 @@ struct ThermalizedBond { static constexpr int num = 1; ThermalizedBond(double temp_com, double gamma_com, double temp_distance, - double gamma_distance, double r_cut); + double gamma_distance, double r_cut) { + this->temp_com = temp_com; + this->gamma_com = gamma_com; + this->temp_distance = temp_distance; + this->gamma_distance = gamma_distance; + this->r_cut = r_cut; + + pref1_com = -1.; + pref2_com = -1.; + pref1_dist = -1.; + pref2_dist = -1.; + } + + void recalc_prefactors(double time_step) { + pref1_com = gamma_com; + pref2_com = std::sqrt(24. * gamma_com / time_step * temp_com); + pref1_dist = gamma_distance; + pref2_dist = std::sqrt(24. * gamma_distance / time_step * temp_distance); + } + boost::optional> forces(Particle const &p1, Particle const &p2, Utils::Vector3d const &dx) const; @@ -76,5 +90,3 @@ struct ThermalizedBond { ar &pref2_dist; } }; - -#endif diff --git a/src/core/bonded_interactions/thermalized_bond_kernel.hpp b/src/core/bonded_interactions/thermalized_bond_kernel.hpp index c6301e2e6d4..6f0dc132f2a 100644 --- a/src/core/bonded_interactions/thermalized_bond_kernel.hpp +++ b/src/core/bonded_interactions/thermalized_bond_kernel.hpp @@ -19,13 +19,13 @@ * along with this program. If not, see . */ -#ifndef THERMALIZED_DIST_KERNEL_H -#define THERMALIZED_DIST_KERNEL_H +#pragma once #include "thermalized_bond.hpp" #include "Particle.hpp" #include "random.hpp" +#include "system/System.hpp" #include "thermostat.hpp" #include @@ -38,7 +38,7 @@ /** Separately thermalizes the com and distance of a particle pair. * @param[in] p1 First particle. * @param[in] p2 Second particle. - * @param[in] dx %Distance between the particles. + * @param[in] dx Distance between the particles. * @return the forces on @p p1 and @p p2 */ inline boost::optional> @@ -55,8 +55,9 @@ ThermalizedBond::forces(Particle const &p1, Particle const &p2, auto const sqrt_mass_red = sqrt(p1.mass() * p2.mass() / mass_tot); auto const com_vel = mass_tot_inv * (p1.mass() * p1.v() + p2.mass() * p2.v()); auto const dist_vel = p2.v() - p1.v(); + auto const &thermalized_bond = + *::System::get_system().thermostat->thermalized_bond; - extern ThermalizedBondThermostat thermalized_bond; Utils::Vector3d force1{}; Utils::Vector3d force2{}; auto const noise = Random::noise_uniform( @@ -88,5 +89,3 @@ ThermalizedBond::forces(Particle const &p1, Particle const &p2, return std::make_tuple(force1, force2); } - -#endif diff --git a/src/core/bonded_interactions/thermalized_bond_utils.cpp b/src/core/bonded_interactions/thermalized_bond_utils.cpp deleted file mode 100644 index 1b87b98c063..00000000000 --- a/src/core/bonded_interactions/thermalized_bond_utils.cpp +++ /dev/null @@ -1,39 +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 . - */ - -#include "thermalized_bond_utils.hpp" -#include "thermalized_bond.hpp" - -#include "bonded_interactions/bonded_interaction_data.hpp" - -#include - -void thermalized_bond_init(double time_step) { - for (auto &kv : bonded_ia_params) { - if (auto *t = boost::get(&(*kv.second))) { - t->pref1_com = t->gamma_com; - t->pref2_com = sqrt(24.0 * t->gamma_com / time_step * t->temp_com); - t->pref1_dist = t->gamma_distance; - t->pref2_dist = - sqrt(24.0 * t->gamma_distance / time_step * t->temp_distance); - } - } -} diff --git a/src/core/bonded_interactions/thermalized_bond_utils.hpp b/src/core/bonded_interactions/thermalized_bond_utils.hpp deleted file mode 100644 index d8a2da821cd..00000000000 --- a/src/core/bonded_interactions/thermalized_bond_utils.hpp +++ /dev/null @@ -1,31 +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 THERMALIZED_BOND_UTILS_H -#define THERMALIZED_BOND_UTILS_H -/** \file - * Initialization routine for all thermalized bonds. - * - * Implementation in \ref thermalized_bond_utils.cpp. - */ -void thermalized_bond_init(double time_step); - -#endif diff --git a/src/core/cell_system/CellStructure.cpp b/src/core/cell_system/CellStructure.cpp index 8b3f35d5e12..8913d29376a 100644 --- a/src/core/cell_system/CellStructure.cpp +++ b/src/core/cell_system/CellStructure.cpp @@ -171,33 +171,33 @@ unsigned map_data_parts(unsigned data_parts) { /* clang-format off */ return GHOSTTRANS_NONE - | ((DATA_PART_PROPERTIES & data_parts) ? GHOSTTRANS_PROPRTS : 0u) - | ((DATA_PART_POSITION & data_parts) ? GHOSTTRANS_POSITION : 0u) - | ((DATA_PART_MOMENTUM & data_parts) ? GHOSTTRANS_MOMENTUM : 0u) - | ((DATA_PART_FORCE & data_parts) ? GHOSTTRANS_FORCE : 0u) + | ((data_parts & DATA_PART_PROPERTIES) ? GHOSTTRANS_PROPRTS : 0u) + | ((data_parts & DATA_PART_POSITION) ? GHOSTTRANS_POSITION : 0u) + | ((data_parts & DATA_PART_MOMENTUM) ? GHOSTTRANS_MOMENTUM : 0u) + | ((data_parts & DATA_PART_FORCE) ? GHOSTTRANS_FORCE : 0u) #ifdef BOND_CONSTRAINT - | ((DATA_PART_RATTLE & data_parts) ? GHOSTTRANS_RATTLE : 0u) + | ((data_parts & DATA_PART_RATTLE) ? GHOSTTRANS_RATTLE : 0u) #endif - | ((DATA_PART_BONDS & data_parts) ? GHOSTTRANS_BONDS : 0u); + | ((data_parts & DATA_PART_BONDS) ? GHOSTTRANS_BONDS : 0u); /* clang-format on */ } void CellStructure::ghosts_count() { ghost_communicator(decomposition().exchange_ghosts_comm(), - GHOSTTRANS_PARTNUM); + *get_system().box_geo, GHOSTTRANS_PARTNUM); } void CellStructure::ghosts_update(unsigned data_parts) { ghost_communicator(decomposition().exchange_ghosts_comm(), - map_data_parts(data_parts)); + *get_system().box_geo, map_data_parts(data_parts)); } void CellStructure::ghosts_reduce_forces() { ghost_communicator(decomposition().collect_ghost_force_comm(), - GHOSTTRANS_FORCE); + *get_system().box_geo, GHOSTTRANS_FORCE); } #ifdef BOND_CONSTRAINT void CellStructure::ghosts_reduce_rattle_correction() { ghost_communicator(decomposition().collect_ghost_force_comm(), - GHOSTTRANS_RATTLE); + *get_system().box_geo, GHOSTTRANS_RATTLE); } #endif @@ -265,8 +265,9 @@ void CellStructure::set_hybrid_decomposition(double cutoff_regular, auto &local_geo = *system.local_geo; auto const &box_geo = *system.box_geo; set_particle_decomposition(std::make_unique( - ::comm_cart, cutoff_regular, m_verlet_skin, box_geo, local_geo, - n_square_types)); + ::comm_cart, cutoff_regular, m_verlet_skin, + [&system]() { return system.get_global_ghost_flags(); }, box_geo, + local_geo, n_square_types)); m_type = CellStructureType::HYBRID; local_geo.set_cell_structure_type(m_type); system.on_cell_structure_change(); diff --git a/src/core/cell_system/HybridDecomposition.cpp b/src/core/cell_system/HybridDecomposition.cpp index a503f77e5e0..a54002f8df2 100644 --- a/src/core/cell_system/HybridDecomposition.cpp +++ b/src/core/cell_system/HybridDecomposition.cpp @@ -26,7 +26,6 @@ #include "BoxGeometry.hpp" #include "LocalBox.hpp" -#include "global_ghost_flags.hpp" #include #include @@ -36,12 +35,14 @@ #include #include +#include #include #include #include HybridDecomposition::HybridDecomposition(boost::mpi::communicator comm, double cutoff_regular, double skin, + std::function get_ghost_flags, BoxGeometry const &box_geo, LocalBox const &local_box, std::set n_square_types) @@ -49,7 +50,8 @@ HybridDecomposition::HybridDecomposition(boost::mpi::communicator comm, m_regular_decomposition(RegularDecomposition( m_comm, cutoff_regular + skin, m_box, local_box)), m_n_square(AtomDecomposition(m_comm, m_box)), - m_n_square_types(std::move(n_square_types)) { + m_n_square_types(std::move(n_square_types)), + m_get_global_ghost_flags(std::move(get_ghost_flags)) { /* Vector containing cells of both child decompositions */ m_local_cells = m_regular_decomposition.get_local_cells(); @@ -155,11 +157,11 @@ void HybridDecomposition::resort(bool global, m_n_square.resort(global, diff); /* basically do CellStructure::ghost_count() */ - ghost_communicator(exchange_ghosts_comm(), GHOSTTRANS_PARTNUM); + ghost_communicator(exchange_ghosts_comm(), m_box, GHOSTTRANS_PARTNUM); /* basically do CellStructure::ghost_update(unsigned data_parts) */ - ghost_communicator(exchange_ghosts_comm(), - map_data_parts(global_ghost_flags())); + ghost_communicator(exchange_ghosts_comm(), m_box, + map_data_parts(m_get_global_ghost_flags())); } std::size_t HybridDecomposition::count_particles( diff --git a/src/core/cell_system/HybridDecomposition.hpp b/src/core/cell_system/HybridDecomposition.hpp index c8993027b7d..ce73c8cd7f5 100644 --- a/src/core/cell_system/HybridDecomposition.hpp +++ b/src/core/cell_system/HybridDecomposition.hpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -69,14 +70,17 @@ class HybridDecomposition : public ParticleDecomposition { /** Set containing the types that should be handled using n_square */ std::set const m_n_square_types; + std::function m_get_global_ghost_flags; + bool is_n_square_type(int type_id) const { return (m_n_square_types.find(type_id) != m_n_square_types.end()); } public: HybridDecomposition(boost::mpi::communicator comm, double cutoff_regular, - double skin, BoxGeometry const &box_geo, - LocalBox const &local_box, std::set n_square_types); + double skin, std::function get_ghost_flags, + BoxGeometry const &box_geo, LocalBox const &local_box, + std::set n_square_types); auto get_cell_grid() const { return m_regular_decomposition.cell_grid; } diff --git a/src/core/constraints/ShapeBasedConstraint.cpp b/src/core/constraints/ShapeBasedConstraint.cpp index 0122f37d8c6..555a22fc0dd 100644 --- a/src/core/constraints/ShapeBasedConstraint.cpp +++ b/src/core/constraints/ShapeBasedConstraint.cpp @@ -101,11 +101,12 @@ ParticleForce ShapeBasedConstraint::force(Particle const &p, calc_non_central_force(p, part_rep, ia_params, dist_vec, dist); #ifdef DPD - if (thermo_switch & THERMO_DPD) { - dpd_force = - dpd_pair_force(p, part_rep, ia_params, dist_vec, dist, dist * dist); + if (m_system.thermostat->thermo_switch & THERMO_DPD) { + dpd_force = dpd_pair_force(p, part_rep, *m_system.thermostat->dpd, + *m_system.box_geo, ia_params, dist_vec, dist, + dist * dist); // Additional use of DPD here requires counter increase - dpd.rng_increment(); + m_system.thermostat->dpd->rng_increment(); } #endif } else if (m_penetrable && (dist <= 0)) { @@ -117,11 +118,12 @@ ParticleForce ShapeBasedConstraint::force(Particle const &p, calc_non_central_force(p, part_rep, ia_params, dist_vec, -dist); #ifdef DPD - if (thermo_switch & THERMO_DPD) { - dpd_force = dpd_pair_force(p, part_rep, ia_params, dist_vec, dist, - dist * dist); + if (m_system.thermostat->thermo_switch & THERMO_DPD) { + dpd_force = dpd_pair_force(p, part_rep, *m_system.thermostat->dpd, + *m_system.box_geo, ia_params, dist_vec, + dist, dist * dist); // Additional use of DPD here requires counter increase - dpd.rng_increment(); + m_system.thermostat->dpd->rng_increment(); } #endif } diff --git a/src/core/dpd.cpp b/src/core/dpd.cpp index 9ee236f798c..5885eeb53cd 100644 --- a/src/core/dpd.cpp +++ b/src/core/dpd.cpp @@ -55,7 +55,7 @@ * 3. Two particle IDs (order-independent, decorrelates particles, gets rid of * seed-per-node) */ -Utils::Vector3d dpd_noise(int pid1, int pid2) { +Utils::Vector3d dpd_noise(DPDThermostat const &dpd, int pid1, int pid2) { return Random::noise_uniform( dpd.rng_counter(), dpd.rng_seed(), (pid1 < pid2) ? pid2 : pid1, (pid1 < pid2) ? pid1 : pid2); @@ -99,21 +99,19 @@ Utils::Vector3d dpd_pair_force(DPDParameters const ¶ms, return {}; } -Utils::Vector3d dpd_pair_force(Particle const &p1, Particle const &p2, - IA_parameters const &ia_params, - Utils::Vector3d const &d, double dist, - double dist2) { +Utils::Vector3d +dpd_pair_force(Particle const &p1, Particle const &p2, DPDThermostat const &dpd, + BoxGeometry const &box_geo, IA_parameters const &ia_params, + Utils::Vector3d const &d, double dist, double dist2) { if (ia_params.dpd.radial.cutoff <= 0.0 && ia_params.dpd.trans.cutoff <= 0.0) { return {}; } - auto const &box_geo = *System::get_system().box_geo; - auto const v21 = box_geo.velocity_difference(p1.pos(), p2.pos(), p1.v(), p2.v()); auto const noise_vec = (ia_params.dpd.radial.pref > 0.0 || ia_params.dpd.trans.pref > 0.0) - ? dpd_noise(p1.id(), p2.id()) + ? dpd_noise(dpd, p1.id(), p2.id()) : Utils::Vector3d{}; auto const f_r = dpd_pair_force(ia_params.dpd.radial, v21, dist, noise_vec); diff --git a/src/core/dpd.hpp b/src/core/dpd.hpp index d464e3aa98b..1de9f425660 100644 --- a/src/core/dpd.hpp +++ b/src/core/dpd.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 ESPRESSO_SRC_CORE_DPD_HPP -#define ESPRESSO_SRC_CORE_DPD_HPP + +#pragma once + /** \file * Routines to use DPD as thermostat or pair force @cite soddemann03a * @@ -30,7 +31,9 @@ #ifdef DPD +#include "BoxGeometry.hpp" #include "Particle.hpp" +#include "thermostat.hpp" #include @@ -43,11 +46,10 @@ struct IA_parameters; void dpd_init(double kT, double time_step); -Utils::Vector3d dpd_pair_force(Particle const &p1, Particle const &p2, - IA_parameters const &ia_params, - Utils::Vector3d const &d, double dist, - double dist2); +Utils::Vector3d +dpd_pair_force(Particle const &p1, Particle const &p2, DPDThermostat const &dpd, + BoxGeometry const &box_geo, IA_parameters const &ia_params, + Utils::Vector3d const &d, double dist, double dist2); Utils::Vector9d dpd_stress(boost::mpi::communicator const &comm); #endif // DPD -#endif diff --git a/src/core/ek/EKNone.hpp b/src/core/ek/EKNone.hpp index 09c643bc170..33a4cd67068 100644 --- a/src/core/ek/EKNone.hpp +++ b/src/core/ek/EKNone.hpp @@ -32,6 +32,7 @@ struct EKNone { void propagate() { throw NoEKActive{}; } double get_tau() const { throw NoEKActive{}; } void veto_time_step(double) const { throw NoEKActive{}; } + void veto_kT(double) const { throw NoEKActive{}; } void sanity_checks(System::System const &) const { throw NoEKActive{}; } void on_cell_structure_change() const { throw NoEKActive{}; } void on_boxl_change() const { throw NoEKActive{}; } diff --git a/src/core/ek/EKWalberla.cpp b/src/core/ek/EKWalberla.cpp index 1e23881178b..50ecb5595dc 100644 --- a/src/core/ek/EKWalberla.cpp +++ b/src/core/ek/EKWalberla.cpp @@ -37,6 +37,8 @@ #include #include +#include +#include #include namespace EK { @@ -110,6 +112,13 @@ void EKWalberla::veto_time_step(double time_step) const { walberla_tau_sanity_checks("EK", ek_container->get_tau(), time_step); } +void EKWalberla::veto_kT(double) const { + if (not ek_container->empty()) { + // can only throw, because without agrid, we can't do the unit conversion + throw std::runtime_error("Temperature change not supported by EK"); + } +} + void EKWalberla::sanity_checks(System::System const &system) const { auto const &box_geo = *system.box_geo; auto const &lattice = ek_container->get_lattice(); diff --git a/src/core/ek/EKWalberla.hpp b/src/core/ek/EKWalberla.hpp index 603b82630ed..32243869ba0 100644 --- a/src/core/ek/EKWalberla.hpp +++ b/src/core/ek/EKWalberla.hpp @@ -55,6 +55,7 @@ struct EKWalberla { double get_tau() const; void veto_time_step(double time_step) const; + void veto_kT(double kT) const; void sanity_checks(System::System const &system) const; bool is_ready_for_propagation() const noexcept; void propagate(); @@ -67,12 +68,8 @@ struct EKWalberla { void on_node_grid_change() const { throw std::runtime_error("MPI topology change not supported by EK"); } - void on_timestep_change() const { - throw std::runtime_error("Time step change not supported by EK"); - } - void on_temperature_change() const { - throw std::runtime_error("Temperature change not supported by EK"); - } + void on_timestep_change() const {} + void on_temperature_change() const {} }; } // namespace EK diff --git a/src/core/ek/Solver.cpp b/src/core/ek/Solver.cpp index 2de532a762d..7190ce57d9d 100644 --- a/src/core/ek/Solver.cpp +++ b/src/core/ek/Solver.cpp @@ -84,6 +84,12 @@ void Solver::veto_time_step(double time_step) const { } } +void Solver::veto_kT(double kT) const { + if (impl->solver) { + std::visit([=](auto &ptr) { ptr->veto_kT(kT); }, *impl->solver); + } +} + void Solver::on_cell_structure_change() { if (impl->solver) { auto &solver = *impl->solver; diff --git a/src/core/ek/Solver.hpp b/src/core/ek/Solver.hpp index c25034fe5f1..d5cc229d9d1 100644 --- a/src/core/ek/Solver.hpp +++ b/src/core/ek/Solver.hpp @@ -35,18 +35,18 @@ struct Solver : public System::Leaf { Solver(); - /** @brief Return true if an @c EK solver is active. */ + /** @brief Return true if an EK solver is active. */ [[nodiscard]] bool is_solver_set() const; - /** @brief Return true if an @c EK solver can be propagated. */ + /** @brief Return true if an EK solver can be propagated. */ bool is_ready_for_propagation() const; - /** @brief Remove the @c EK solver. */ + /** @brief Remove the EK solver. */ void reset(); /** - * @brief Set the @c EK solver. - * For developers: a specialization must exist for every new @c EK type. + * @brief Set the EK solver. + * For developers: a specialization must exist for every new EK type. */ template void set(Args... args); @@ -60,7 +60,7 @@ struct Solver : public System::Leaf { } /** - * @brief Propagate the @c EK species. + * @brief Propagate the EK species. */ void propagate(); @@ -71,20 +71,25 @@ struct Solver : public System::Leaf { void init() const {} /** - * @brief Get the @c EK time step. + * @brief Get the EK time step. */ double get_tau() const; /** - * @brief Perform @c EK parameter checks. + * @brief Perform EK parameter checks. */ void sanity_checks() const; /** - * @brief Check if a MD time step is compatible with the @c EK tau. + * @brief Check if a MD time step is compatible with the EK tau. */ void veto_time_step(double time_step) const; + /** + * @brief Check if a thermostat is compatible with the EK temperature. + */ + void veto_kT(double kT) const; + void on_boxl_change(); void on_node_grid_change(); void on_cell_structure_change(); diff --git a/src/core/electrostatics/icc.cpp b/src/core/electrostatics/icc.cpp index 975e76d4f44..cb99d9d2aa3 100644 --- a/src/core/electrostatics/icc.cpp +++ b/src/core/electrostatics/icc.cpp @@ -50,7 +50,6 @@ #include #include -#include #include #include #include @@ -212,6 +211,8 @@ void ICCStar::iteration(CellStructure &cell_structure, } void icc_data::sanity_checks() const { + if (n_icc <= 0) + throw std::domain_error("Parameter 'n_icc' must be >= 1"); if (convergence <= 0.) throw std::domain_error("Parameter 'convergence' must be > 0"); if (relaxation < 0. or relaxation > 2.) @@ -222,12 +223,14 @@ void icc_data::sanity_checks() const { throw std::domain_error("Parameter 'first_id' must be >= 0"); if (eps_out <= 0.) throw std::domain_error("Parameter 'eps_out' must be > 0"); - - assert(n_icc >= 1); - assert(areas.size() == n_icc); - assert(epsilons.size() == n_icc); - assert(sigmas.size() == n_icc); - assert(normals.size() == n_icc); + if (areas.size() != n_icc) + throw std::invalid_argument("Parameter 'areas' has incorrect shape"); + if (epsilons.size() != n_icc) + throw std::invalid_argument("Parameter 'epsilons' has incorrect shape"); + if (sigmas.size() != n_icc) + throw std::invalid_argument("Parameter 'sigmas' has incorrect shape"); + if (normals.size() != n_icc) + throw std::invalid_argument("Parameter 'normals' has incorrect shape"); } ICCStar::ICCStar(icc_data data) { diff --git a/src/core/electrostatics/mmm1d.hpp b/src/core/electrostatics/mmm1d.hpp index 207c1544429..24cada32aaf 100644 --- a/src/core/electrostatics/mmm1d.hpp +++ b/src/core/electrostatics/mmm1d.hpp @@ -21,7 +21,7 @@ /** * @file - * MMM1D algorithm for long-range %Coulomb interactions on the CPU. + * MMM1D algorithm for long-range Coulomb interactions on the CPU. * Implementation of the MMM1D method for the calculation of the electrostatic * interaction in one-dimensionally periodic systems. For details on the * method see MMM in general. The MMM1D method works only with the N-squared diff --git a/src/core/electrostatics/mmm1d_gpu.hpp b/src/core/electrostatics/mmm1d_gpu.hpp index af79e998ab7..98e5a46e784 100644 --- a/src/core/electrostatics/mmm1d_gpu.hpp +++ b/src/core/electrostatics/mmm1d_gpu.hpp @@ -21,7 +21,7 @@ /** * @file - * MMM1D algorithm for long-range %Coulomb interactions on the GPU. + * MMM1D algorithm for long-range Coulomb interactions on the GPU. * Implementation of the MMM1D method for the calculation of the electrostatic * interaction in one-dimensionally periodic systems. For details on the * method see MMM in general. The MMM1D method works only with the N-squared diff --git a/src/core/electrostatics/p3m.hpp b/src/core/electrostatics/p3m.hpp index a53f408d92e..05d7b28d3f4 100644 --- a/src/core/electrostatics/p3m.hpp +++ b/src/core/electrostatics/p3m.hpp @@ -168,8 +168,8 @@ struct CoulombP3M : public Coulomb::Actor { /** * @brief Assign a single charge into the current charge grid. * - * @param[in] q %Particle charge - * @param[in] real_pos %Particle position in real space + * @param[in] q Particle charge + * @param[in] real_pos Particle position in real space * @param[out] inter_weights Cached interpolation weights to be used. */ void assign_charge(double q, Utils::Vector3d const &real_pos, diff --git a/src/core/energy.cpp b/src/core/energy.cpp index ae66fd23e5b..3f8e21f3271 100644 --- a/src/core/energy.cpp +++ b/src/core/energy.cpp @@ -24,7 +24,6 @@ #include "cell_system/CellStructure.hpp" #include "constraints.hpp" #include "energy_inline.hpp" -#include "global_ghost_flags.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include "short_range_loop.hpp" #include "system/System.hpp" @@ -115,7 +114,7 @@ std::shared_ptr System::calculate_energy() { double System::particle_short_range_energy_contribution(int pid) { if (cell_structure->get_resort_particles()) { - cell_structure->update_ghosts_and_resort_particle(global_ghost_flags()); + cell_structure->update_ghosts_and_resort_particle(get_global_ghost_flags()); } auto ret = 0.0; diff --git a/src/core/energy_inline.hpp b/src/core/energy_inline.hpp index 99d41334cfe..68859ac6b76 100644 --- a/src/core/energy_inline.hpp +++ b/src/core/energy_inline.hpp @@ -68,7 +68,7 @@ * @param ia_params the interaction parameters between the two particles * @param d vector between p1 and p2. * @param dist distance between p1 and p2. - * @param coulomb_kernel %Coulomb energy kernel. + * @param coulomb_kernel Coulomb energy kernel. * @return the short-range interaction energy between the two particles */ inline double calc_non_bonded_pair_energy( @@ -168,7 +168,7 @@ inline double calc_non_bonded_pair_energy( * @param dist distance between p1 and p2. * @param dist2 distance squared between p1 and p2. * @param[in] ia_params non-bonded interaction kernels. - * @param[in] coulomb_kernel %Coulomb energy kernel. + * @param[in] coulomb_kernel Coulomb energy kernel. * @param[in] dipoles_kernel Dipolar energy kernel. * @param[in,out] obs_energy energy observable. */ diff --git a/src/core/forces.cpp b/src/core/forces.cpp index 1bc21bffcbf..5ce4aee3683 100644 --- a/src/core/forces.cpp +++ b/src/core/forces.cpp @@ -27,6 +27,7 @@ #include "BoxGeometry.hpp" #include "Particle.hpp" #include "ParticleRange.hpp" +#include "PropagationMode.hpp" #include "bond_breakage/bond_breakage.hpp" #include "cell_system/CellStructure.hpp" #include "cells.hpp" @@ -48,7 +49,6 @@ #include "short_range_loop.hpp" #include "system/System.hpp" #include "thermostat.hpp" -#include "thermostats/langevin_inline.hpp" #include "virtual_sites/relative.hpp" #include @@ -117,7 +117,7 @@ static void force_capping(ParticleRange const &particles, double force_cap) { } } -void System::System::calculate_forces(double kT) { +void System::System::calculate_forces() { #ifdef CALIPER CALI_CXX_MARK_FUNCTION; #endif @@ -149,7 +149,7 @@ void System::System::calculate_forces(double kT) { npt_reset_instantaneous_virials(); #endif init_forces(particles, ghost_particles); - thermostats_force_init(kT); + thermostat_force_init(); calc_long_range_forces(particles); @@ -178,13 +178,14 @@ void System::System::calculate_forces(double kT) { }, [coulomb_kernel_ptr = get_ptr(coulomb_kernel), dipoles_kernel_ptr = get_ptr(dipoles_kernel), - elc_kernel_ptr = get_ptr(elc_kernel), &nonbonded_ias = *nonbonded_ias]( - Particle &p1, Particle &p2, Distance const &d) { + elc_kernel_ptr = get_ptr(elc_kernel), &nonbonded_ias = *nonbonded_ias, + &thermostat = *thermostat, + &box_geo = *box_geo](Particle &p1, Particle &p2, Distance const &d) { auto const &ia_params = nonbonded_ias.get_ia_param(p1.type(), p2.type()); - add_non_bonded_pair_force(p1, p2, d.vec21, sqrt(d.dist2), d.dist2, - ia_params, coulomb_kernel_ptr, - dipoles_kernel_ptr, elc_kernel_ptr); + add_non_bonded_pair_force( + p1, p2, d.vec21, sqrt(d.dist2), d.dist2, ia_params, thermostat, + box_geo, coulomb_kernel_ptr, dipoles_kernel_ptr, elc_kernel_ptr); #ifdef COLLISION_DETECTION if (collision_params.mode != CollisionModeType::OFF) detect_collision(p1, p2, d.dist2); @@ -213,8 +214,9 @@ void System::System::calculate_forces(double kT) { // Must be done here. Forces need to be ghost-communicated immersed_boundaries.volume_conservation(*cell_structure); - if (lb.is_solver_set()) { - LB::couple_particles(particles, ghost_particles, time_step); + if (thermostat->lb and (propagation->used_propagations & + PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE)) { + lb_couple_particles(time_step); } #ifdef CUDA diff --git a/src/core/forces_inline.hpp b/src/core/forces_inline.hpp index f3e54f1da13..f6d643d1e71 100644 --- a/src/core/forces_inline.hpp +++ b/src/core/forces_inline.hpp @@ -195,13 +195,16 @@ inline ParticleForce calc_opposing_force(ParticleForce const &pf, * @param[in] dist distance between @p p1 and @p p2. * @param[in] dist2 distance squared between @p p1 and @p p2. * @param[in] ia_params non-bonded interaction kernels. - * @param[in] coulomb_kernel %Coulomb force kernel. + * @param[in] thermostat thermostat. + * @param[in] box_geo box geometry. + * @param[in] coulomb_kernel Coulomb force kernel. * @param[in] dipoles_kernel Dipolar force kernel. * @param[in] elc_kernel ELC force correction kernel. */ inline void add_non_bonded_pair_force( Particle &p1, Particle &p2, Utils::Vector3d const &d, double dist, double dist2, IA_parameters const &ia_params, + Thermostat::Thermostat const &thermostat, BoxGeometry const &box_geo, Coulomb::ShortRangeForceKernel::kernel_type const *coulomb_kernel, Dipoles::ShortRangeForceKernel::kernel_type const *dipoles_kernel, Coulomb::ShortRangeForceCorrectionsKernel::kernel_type const *elc_kernel) { @@ -255,8 +258,9 @@ inline void add_non_bonded_pair_force( /* The inter dpd force should not be part of the virial */ #ifdef DPD - if (thermo_switch & THERMO_DPD) { - auto const force = dpd_pair_force(p1, p2, ia_params, d, dist, dist2); + if (thermostat.thermo_switch & THERMO_DPD) { + auto const force = dpd_pair_force(p1, p2, *thermostat.dpd, box_geo, + ia_params, d, dist, dist2); p1.force() += force; p2.force() -= force; } @@ -287,7 +291,7 @@ inline void add_non_bonded_pair_force( * @param[in] p2 Second particle. * @param[in] iaparams Bonded parameters for the interaction. * @param[in] dx Vector between @p p1 and @p p2. - * @param[in] kernel %Coulomb force kernel. + * @param[in] kernel Coulomb force kernel. */ inline boost::optional calc_bond_pair_force( Particle const &p1, Particle const &p2, diff --git a/src/core/ghosts.cpp b/src/core/ghosts.cpp index c513656ffb9..5a992d1870f 100644 --- a/src/core/ghosts.cpp +++ b/src/core/ghosts.cpp @@ -247,9 +247,9 @@ static auto calc_transmit_size(GhostCommunication const &ghost_comm, } static void prepare_send_buffer(CommBuf &send_buffer, - const GhostCommunication &ghost_comm, + GhostCommunication const &ghost_comm, + BoxGeometry const &box_geo, unsigned int data_parts) { - auto const &box_geo = *System::get_system().box_geo; /* reallocate send buffer */ send_buffer.resize(calc_transmit_size(ghost_comm, box_geo, data_parts)); @@ -295,9 +295,9 @@ static void prepare_ghost_cell(ParticleList *cell, std::size_t size) { } static void prepare_recv_buffer(CommBuf &recv_buffer, - const GhostCommunication &ghost_comm, + GhostCommunication const &ghost_comm, + BoxGeometry const &box_geo, unsigned int data_parts) { - auto const &box_geo = *System::get_system().box_geo; /* reallocate recv buffer */ recv_buffer.resize(calc_transmit_size(ghost_comm, box_geo, data_parts)); /* clear bond buffer */ @@ -305,11 +305,11 @@ static void prepare_recv_buffer(CommBuf &recv_buffer, } static void put_recv_buffer(CommBuf &recv_buffer, - const GhostCommunication &ghost_comm, + GhostCommunication const &ghost_comm, + BoxGeometry const &box_geo, unsigned int data_parts) { /* put back data */ auto archiver = Utils::MemcpyIArchive{Utils::make_span(recv_buffer)}; - auto const &box_geo = *System::get_system().box_geo; if (data_parts & GHOSTTRANS_PARTNUM) { for (auto part_list : ghost_comm.part_lists) { @@ -372,9 +372,9 @@ static void add_forces_from_recv_buffer(CommBuf &recv_buffer, } } -static void cell_cell_transfer(const GhostCommunication &ghost_comm, +static void cell_cell_transfer(GhostCommunication const &ghost_comm, + BoxGeometry const &box_geo, unsigned int data_parts) { - auto const &box_geo = *System::get_system().box_geo; CommBuf buffer; if (!(data_parts & GHOSTTRANS_PARTNUM)) { buffer.resize(calc_transmit_size(box_geo, data_parts)); @@ -437,15 +437,13 @@ static bool is_poststorable(GhostCommunication const &ghost_comm, return is_recv_op(comm_type, node, this_node) && poststore; } -void ghost_communicator(const GhostCommunicator &gcr, unsigned int data_parts) { +void ghost_communicator(GhostCommunicator const &gcr, + BoxGeometry const &box_geo, unsigned int data_parts) { if (GHOSTTRANS_NONE == data_parts) return; static CommBuf send_buffer, recv_buffer; -#ifndef NDEBUG - auto const &box_geo = *System::get_system().box_geo; -#endif auto const &comm = gcr.mpi_comm; for (auto it = gcr.communications.begin(); it != gcr.communications.end(); @@ -454,7 +452,7 @@ void ghost_communicator(const GhostCommunicator &gcr, unsigned int data_parts) { int const comm_type = ghost_comm.type & GHOST_JOBMASK; if (comm_type == GHOST_LOCL) { - cell_cell_transfer(ghost_comm, data_parts); + cell_cell_transfer(ghost_comm, box_geo, data_parts); continue; } @@ -466,7 +464,7 @@ void ghost_communicator(const GhostCommunicator &gcr, unsigned int data_parts) { if (is_send_op(comm_type, node, comm.rank())) { /* ok, we send this step, prepare send buffer if not yet done */ if (!prefetch) { - prepare_send_buffer(send_buffer, ghost_comm, data_parts); + prepare_send_buffer(send_buffer, ghost_comm, box_geo, data_parts); } // Check prefetched send buffers (must also hold for buffers allocated // in the previous lines.) @@ -481,12 +479,13 @@ void ghost_communicator(const GhostCommunicator &gcr, unsigned int data_parts) { }); if (prefetch_ghost_comm != gcr.communications.end()) - prepare_send_buffer(send_buffer, *prefetch_ghost_comm, data_parts); + prepare_send_buffer(send_buffer, *prefetch_ghost_comm, box_geo, + data_parts); } /* recv buffer for recv and multinode operations to this node */ if (is_recv_op(comm_type, node, comm.rank())) - prepare_recv_buffer(recv_buffer, ghost_comm, data_parts); + prepare_recv_buffer(recv_buffer, ghost_comm, box_geo, data_parts); /* transfer data */ // Use two send/recvs in order to avoid having to serialize CommBuf @@ -540,7 +539,7 @@ void ghost_communicator(const GhostCommunicator &gcr, unsigned int data_parts) { add_rattle_correction_from_recv_buffer(recv_buffer, ghost_comm); #endif else - put_recv_buffer(recv_buffer, ghost_comm, data_parts); + put_recv_buffer(recv_buffer, ghost_comm, box_geo, data_parts); } } else if (poststore) { /* send op; write back delayed data from last recv, when this was a @@ -564,7 +563,8 @@ void ghost_communicator(const GhostCommunicator &gcr, unsigned int data_parts) { *poststore_ghost_comm); #endif else - put_recv_buffer(recv_buffer, *poststore_ghost_comm, data_parts); + put_recv_buffer(recv_buffer, *poststore_ghost_comm, box_geo, + data_parts); } } } diff --git a/src/core/ghosts.hpp b/src/core/ghosts.hpp index 4d52b49b53b..22fdb3236f5 100644 --- a/src/core/ghosts.hpp +++ b/src/core/ghosts.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_GHOSTS_HPP -#define CORE_GHOSTS_HPP + +#pragma once + /** \file * Ghost particles and particle exchange. * @@ -83,6 +84,8 @@ * * The ghost communicators are created by the cell systems. */ + +#include "BoxGeometry.hpp" #include "ParticleList.hpp" #include @@ -164,8 +167,7 @@ struct GhostCommunicator { }; /** - * @brief Do a ghost communication with caller specified data parts. + * @brief Do a ghost communication with the specified data parts. */ -void ghost_communicator(const GhostCommunicator &gcr, unsigned int data_parts); - -#endif +void ghost_communicator(GhostCommunicator const &gcr, + BoxGeometry const &box_geo, unsigned int data_parts); diff --git a/src/core/global_ghost_flags.cpp b/src/core/global_ghost_flags.cpp deleted file mode 100644 index ff38d61b402..00000000000 --- a/src/core/global_ghost_flags.cpp +++ /dev/null @@ -1,57 +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 . - */ - -#include "global_ghost_flags.hpp" - -#include "bonded_interactions/thermalized_bond.hpp" -#include "collision.hpp" -#include "config/config.hpp" -#include "system/System.hpp" -#include "thermostat.hpp" - -/** - * @brief Returns the ghost flags required for running pair - * kernels for the global state, e.g. the force calculation. - * @return Required data parts; - */ -unsigned global_ghost_flags() { - /* Position and Properties are always requested. */ - unsigned data_parts = Cells::DATA_PART_POSITION | Cells::DATA_PART_PROPERTIES; - - if (::System::get_system().lb.is_solver_set()) - data_parts |= Cells::DATA_PART_MOMENTUM; - - if (::thermo_switch & THERMO_DPD) - data_parts |= Cells::DATA_PART_MOMENTUM; - - if (::n_thermalized_bonds) { - data_parts |= Cells::DATA_PART_MOMENTUM; - data_parts |= Cells::DATA_PART_BONDS; - } - -#ifdef COLLISION_DETECTION - if (::collision_params.mode != CollisionModeType::OFF) { - data_parts |= Cells::DATA_PART_BONDS; - } -#endif - - return data_parts; -} diff --git a/src/core/immersed_boundary/ibm_common.hpp b/src/core/immersed_boundary/ibm_common.hpp index 5fb5606ad6f..62262130dc8 100644 --- a/src/core/immersed_boundary/ibm_common.hpp +++ b/src/core/immersed_boundary/ibm_common.hpp @@ -25,7 +25,7 @@ /** * @brief Returns the position of a given particle. * - * @param pid %Particle id. + * @param pid Particle id. * @return position of the particle. */ Utils::Vector3d get_ibm_particle_position(int pid); diff --git a/src/core/integrate.cpp b/src/core/integrate.cpp index 9b2bcd50bb2..61b925b2669 100644 --- a/src/core/integrate.cpp +++ b/src/core/integrate.cpp @@ -39,14 +39,13 @@ #include "PropagationMode.hpp" #include "accumulators.hpp" #include "bond_breakage/bond_breakage.hpp" -#include "bonded_interactions/rigid_bond.hpp" +#include "bonded_interactions/bonded_interaction_data.hpp" #include "cell_system/CellStructure.hpp" #include "cells.hpp" #include "collision.hpp" #include "communication.hpp" #include "errorhandling.hpp" #include "forces.hpp" -#include "global_ghost_flags.hpp" #include "lb/particle_coupling.hpp" #include "lb/utils.hpp" #include "lees_edwards/lees_edwards.hpp" @@ -128,14 +127,14 @@ void LeesEdwards::unset_protocol() { } // namespace LeesEdwards -void Propagation::update_default_propagation() { +void Propagation::update_default_propagation(int thermo_switch) { 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) { + if ((thermo_switch & THERMO_LB) and (thermo_switch & THERMO_LANGEVIN)) { default_propagation = PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE; #ifdef ROTATION default_propagation |= PropagationMode::ROT_LANGEVIN; @@ -193,6 +192,7 @@ void System::System::update_used_propagations() { } void System::System::integrator_sanity_checks() const { + auto const thermo_switch = thermostat->thermo_switch; if (time_step <= 0.) { runtimeErrorMsg() << "time_step not set"; } @@ -210,7 +210,7 @@ void System::System::integrator_sanity_checks() const { } #ifdef NPT if (propagation->used_propagations & PropagationMode::TRANS_LANGEVIN_NPT) { - if (thermo_switch != THERMO_OFF and thermo_switch != THERMO_NPT_ISO) { + if (thermo_switch != THERMO_NPT_ISO) { runtimeErrorMsg() << "The NpT integrator requires the NpT thermostat"; } if (box_geo->type() == BoxType::LEES_EDWARDS) { @@ -225,11 +225,25 @@ void System::System::integrator_sanity_checks() const { } if (propagation->used_propagations & PropagationMode::TRANS_STOKESIAN) { #ifdef STOKESIAN_DYNAMICS - if (thermo_switch != THERMO_OFF && thermo_switch != THERMO_SD) { + if (thermo_switch != THERMO_SD) { runtimeErrorMsg() << "The SD integrator requires the SD thermostat"; } #endif } + if (lb.is_solver_set() and (propagation->used_propagations & + (PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE | + PropagationMode::TRANS_LB_TRACER))) { + if (thermostat->lb == nullptr) { + runtimeErrorMsg() << "The LB integrator requires the LB thermostat"; + } + } + if (::bonded_ia_params.get_n_thermalized_bonds() >= 1 and + (thermostat->thermalized_bond == nullptr or + (thermo_switch & THERMO_BOND) == 0)) { + runtimeErrorMsg() + << "Thermalized bonds require the thermalized_bond thermostat"; + } + #ifdef ROTATION for (auto const &p : cell_structure->local_particles()) { using namespace PropagationMode; @@ -285,7 +299,7 @@ void walberla_agrid_sanity_checks(std::string method, "waLBerla and ESPResSo disagree about domain decomposition."); } } -#endif +#endif // WALBERLA static void resort_particles_if_needed(System::System &system) { auto &cell_structure = *system.cell_structure; @@ -296,8 +310,15 @@ static void resort_particles_if_needed(System::System &system) { } } -void System::System::thermostats_force_init(double kT) { +void System::System::thermostat_force_init() { auto const &propagation = *this->propagation; + if ((not thermostat->langevin) or ((propagation.used_propagations & + (PropagationMode::TRANS_LANGEVIN | + PropagationMode::ROT_LANGEVIN)) == 0)) { + return; + } + auto const &langevin = *thermostat->langevin; + auto const kT = thermostat->kT; 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); @@ -313,12 +334,14 @@ void System::System::thermostats_force_init(double kT) { * @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) { + Propagation const &propagation, + System::System &system, double time_step) { // steepest decent if (propagation.integ_switch == INTEG_METHOD_STEEPEST_DESCENT) return steepest_descent_step(particles); + auto const &thermostat = *system.thermostat; + auto const kT = thermostat.kT; for (auto &p : particles) { #ifdef VIRTUAL_SITES // virtual sites are updated later in the integration loop @@ -341,10 +364,10 @@ static bool integrator_step_1(ParticleRange const &particles, 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); + brownian_dynamics_propagator(*thermostat.brownian, p, time_step, kT); #ifdef ROTATION if (propagation.should_propagate_with(p, PropagationMode::ROT_BROWNIAN)) - brownian_dynamics_rotator(brownian, p, time_step, kT); + brownian_dynamics_rotator(*thermostat.brownian, p, time_step, kT); #endif } @@ -352,7 +375,8 @@ static bool integrator_step_1(ParticleRange const &particles, 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); + velocity_verlet_npt_step_1(particles.filter(pred), *thermostat.npt_iso, + time_step, system); } #endif @@ -360,7 +384,8 @@ static bool integrator_step_1(ParticleRange const &particles, 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); + stokesian_dynamics_step_1(particles.filter(pred), *thermostat.stokesian, + time_step, kT); } #endif // STOKESIAN_DYNAMICS @@ -368,7 +393,8 @@ static bool integrator_step_1(ParticleRange const &particles, } static void integrator_step_2(ParticleRange const &particles, - Propagation const &propagation, double kT, + Propagation const &propagation, + Thermostat::Thermostat const &thermostat, double time_step) { if (propagation.integ_switch == INTEG_METHOD_STEEPEST_DESCENT) return; @@ -400,7 +426,8 @@ static void integrator_step_2(ParticleRange const &particles, 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); + velocity_verlet_npt_step_2(particles.filter(pred), *thermostat.npt_iso, + time_step); } #endif } @@ -416,9 +443,12 @@ int System::System::integrate(int n_steps, int reuse_forces) { PropagationMode::TRANS_VS_RELATIVE); }; #endif +#ifdef BOND_CONSTRAINT + auto const n_rigid_bonds = ::bonded_ia_params.get_n_rigid_bonds(); +#endif // Prepare particle structure and run sanity checks of all active algorithms - propagation.update_default_propagation(); + propagation.update_default_propagation(thermostat->thermo_switch); update_used_propagations(); on_integration_start(); @@ -433,7 +463,7 @@ int System::System::integrate(int n_steps, int reuse_forces) { #ifdef CALIPER CALI_MARK_BEGIN("Initial Force Calculation"); #endif - lb_lbcoupling_deactivate(); + thermostat->lb_coupling_deactivate(); #ifdef VIRTUAL_SITES_RELATIVE if (has_vs_rel()) { @@ -442,9 +472,9 @@ int System::System::integrate(int n_steps, int reuse_forces) { #endif // Communication step: distribute ghost positions - cell_structure->update_ghosts_and_resort_particle(global_ghost_flags()); + cell_structure->update_ghosts_and_resort_particle(get_global_ghost_flags()); - calculate_forces(::temperature); + calculate_forces(); if (propagation.integ_switch != INTEG_METHOD_STEEPEST_DESCENT) { #ifdef ROTATION @@ -457,7 +487,7 @@ int System::System::integrate(int n_steps, int reuse_forces) { #endif } - lb_lbcoupling_activate(); + thermostat->lb_coupling_activate(); if (check_runtime_errors(comm_cart)) return INTEG_ERROR_RUNTIME; @@ -497,13 +527,13 @@ int System::System::integrate(int n_steps, int reuse_forces) { auto particles = cell_structure->local_particles(); #ifdef BOND_CONSTRAINT - if (n_rigidbonds) + if (n_rigid_bonds) save_old_position(particles, cell_structure->ghost_particles()); #endif lees_edwards->update_box_params(*box_geo, sim_time); bool early_exit = - integrator_step_1(particles, propagation, ::temperature, time_step); + integrator_step_1(particles, propagation, *this, time_step); if (early_exit) break; @@ -523,11 +553,11 @@ int System::System::integrate(int n_steps, int reuse_forces) { } // Propagate philox RNG counters - philox_counter_increment(); + thermostat->philox_counter_increment(); #ifdef BOND_CONSTRAINT // Correct particle positions that participate in a rigid/constrained bond - if (n_rigidbonds) { + if (n_rigid_bonds) { correct_position_shake(*cell_structure, *box_geo); } #endif @@ -548,18 +578,20 @@ int System::System::integrate(int n_steps, int reuse_forces) { n_verlet_updates++; // Communication step: distribute ghost positions - cell_structure->update_ghosts_and_resort_particle(global_ghost_flags()); + cell_structure->update_ghosts_and_resort_particle(get_global_ghost_flags()); particles = cell_structure->local_particles(); - calculate_forces(::temperature); + calculate_forces(); #ifdef VIRTUAL_SITES_INERTIALESS_TRACERS - if (propagation.used_propagations & PropagationMode::TRANS_LB_TRACER) { - lb_tracers_add_particle_force_to_fluid(*cell_structure, time_step); + if (thermostat->lb and + (propagation.used_propagations & PropagationMode::TRANS_LB_TRACER)) { + lb_tracers_add_particle_force_to_fluid(*cell_structure, *box_geo, + *local_geo, lb, time_step); } #endif - integrator_step_2(particles, propagation, ::temperature, time_step); + integrator_step_2(particles, propagation, *thermostat, time_step); if (propagation.integ_switch == INTEG_METHOD_BD) { resort_particles_if_needed(*this); } @@ -571,7 +603,7 @@ int System::System::integrate(int n_steps, int reuse_forces) { } #ifdef BOND_CONSTRAINT // SHAKE velocity updates - if (n_rigidbonds) { + if (n_rigid_bonds) { correct_velocity_shake(*cell_structure, *box_geo); } #endif @@ -599,7 +631,6 @@ int System::System::integrate(int n_steps, int reuse_forces) { 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()); propagation.lb_skipped_md_steps += 1; @@ -607,7 +638,6 @@ int System::System::integrate(int n_steps, int reuse_forces) { 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()); propagation.ek_skipped_md_steps += 1; @@ -616,10 +646,15 @@ int System::System::integrate(int n_steps, int reuse_forces) { ek.propagate(); } } + if (lb_active and (propagation.used_propagations & + PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE)) { + thermostat->lb->rng_increment(); + } #ifdef VIRTUAL_SITES_INERTIALESS_TRACERS - if (propagation.used_propagations & PropagationMode::TRANS_LB_TRACER) { - lb_tracers_propagate(*cell_structure, time_step); + if (thermostat->lb and + (propagation.used_propagations & PropagationMode::TRANS_LB_TRACER)) { + lb_tracers_propagate(*cell_structure, lb, time_step); } #endif diff --git a/src/core/integrators/Propagation.hpp b/src/core/integrators/Propagation.hpp index f810c5f1ba9..c0949246ebe 100644 --- a/src/core/integrators/Propagation.hpp +++ b/src/core/integrators/Propagation.hpp @@ -33,7 +33,7 @@ class Propagation { /** If true, forces will be recalculated before the next integration. */ bool recalc_forces = true; - void update_default_propagation(); + void update_default_propagation(int thermo_switch); template bool should_propagate_with(Particle const &p, int mode) const { diff --git a/src/core/integrators/stokesian_dynamics_inline.hpp b/src/core/integrators/stokesian_dynamics_inline.hpp index 4150e8a819c..ee0a8eedc2f 100644 --- a/src/core/integrators/stokesian_dynamics_inline.hpp +++ b/src/core/integrators/stokesian_dynamics_inline.hpp @@ -25,10 +25,12 @@ #include "rotation.hpp" #include "stokesian_dynamics/sd_interface.hpp" +#include "thermostat.hpp" inline void stokesian_dynamics_step_1(ParticleRangeStokesian const &particles, - double time_step) { - propagate_vel_pos_sd(particles, time_step); + StokesianThermostat const &stokesian, + double time_step, double kT) { + propagate_vel_pos_sd(particles, stokesian, time_step, kT); for (auto &p : particles) { // translate diff --git a/src/core/integrators/velocity_verlet_npt.cpp b/src/core/integrators/velocity_verlet_npt.cpp index 328bf31859d..ad6f350564a 100644 --- a/src/core/integrators/velocity_verlet_npt.cpp +++ b/src/core/integrators/velocity_verlet_npt.cpp @@ -45,6 +45,7 @@ static constexpr Utils::Vector3i nptgeom_dir{{1, 2, 4}}; static void velocity_verlet_npt_propagate_vel_final(ParticleRangeNPT const &particles, + IsotropicNptThermostat const &npt_iso, double time_step) { nptiso.p_vel = {}; @@ -65,7 +66,9 @@ velocity_verlet_npt_propagate_vel_final(ParticleRangeNPT const &particles, } /** Scale and communicate instantaneous NpT pressure */ -static void velocity_verlet_npt_finalize_p_inst(double time_step) { +static void +velocity_verlet_npt_finalize_p_inst(IsotropicNptThermostat const &npt_iso, + double time_step) { /* finalize derivation of p_inst */ nptiso.p_inst = 0.0; for (unsigned int i = 0; i < 3; i++) { @@ -84,17 +87,18 @@ static void velocity_verlet_npt_finalize_p_inst(double time_step) { } } -static void velocity_verlet_npt_propagate_pos(ParticleRangeNPT const &particles, - double time_step) { +static void +velocity_verlet_npt_propagate_pos(ParticleRangeNPT const &particles, + IsotropicNptThermostat const &npt_iso, + double time_step, System::System &system) { - auto &system = System::get_system(); auto &box_geo = *system.box_geo; auto &cell_structure = *system.cell_structure; Utils::Vector3d scal{}; double L_new = 0.0; /* finalize derivation of p_inst */ - velocity_verlet_npt_finalize_p_inst(time_step); + velocity_verlet_npt_finalize_p_inst(npt_iso, time_step); /* adjust \ref NptIsoParameters::nptiso.volume; prepare pos- and * vel-rescaling @@ -158,8 +162,10 @@ static void velocity_verlet_npt_propagate_pos(ParticleRangeNPT const &particles, system.on_boxl_change(true); } -static void velocity_verlet_npt_propagate_vel(ParticleRangeNPT const &particles, - double time_step) { +static void +velocity_verlet_npt_propagate_vel(ParticleRangeNPT const &particles, + IsotropicNptThermostat const &npt_iso, + double time_step) { nptiso.p_vel = {}; for (auto &p : particles) { @@ -179,15 +185,17 @@ static void velocity_verlet_npt_propagate_vel(ParticleRangeNPT const &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); + IsotropicNptThermostat const &npt_iso, + double time_step, System::System &system) { + velocity_verlet_npt_propagate_vel(particles, npt_iso, time_step); + velocity_verlet_npt_propagate_pos(particles, npt_iso, time_step, system); } void velocity_verlet_npt_step_2(ParticleRangeNPT const &particles, + IsotropicNptThermostat const &npt_iso, double time_step) { - velocity_verlet_npt_propagate_vel_final(particles, time_step); - velocity_verlet_npt_finalize_p_inst(time_step); + velocity_verlet_npt_propagate_vel_final(particles, npt_iso, time_step); + velocity_verlet_npt_finalize_p_inst(npt_iso, time_step); } #endif // NPT diff --git a/src/core/integrators/velocity_verlet_npt.hpp b/src/core/integrators/velocity_verlet_npt.hpp index 127296dfee1..e0992f24484 100644 --- a/src/core/integrators/velocity_verlet_npt.hpp +++ b/src/core/integrators/velocity_verlet_npt.hpp @@ -26,6 +26,7 @@ #include "ParticleRange.hpp" #include "PropagationMode.hpp" #include "PropagationPredicate.hpp" +#include "thermostat.hpp" struct PropagationPredicateNPT { int modes; @@ -41,6 +42,10 @@ struct PropagationPredicateNPT { using ParticleRangeNPT = ParticleRangeFiltered; +namespace System { +class System; +} + /** Special propagator for NpT isotropic. * Propagate the velocities and positions. Integration steps before force * calculation of the Velocity Verlet integrator: @@ -51,7 +56,8 @@ using ParticleRangeNPT = ParticleRangeFiltered; * positions and velocities and check Verlet list criterion (only NpT). */ void velocity_verlet_npt_step_1(ParticleRangeNPT const &particles, - double time_step); + IsotropicNptThermostat const &npt_iso, + double time_step, System::System &system); /** Final integration step of the Velocity Verlet+NpT integrator. * Finalize instantaneous pressure calculation: @@ -59,6 +65,7 @@ void velocity_verlet_npt_step_1(ParticleRangeNPT const &particles, * + 0.5 \Delta t \cdot F(t+\Delta t)/m \f] */ void velocity_verlet_npt_step_2(ParticleRangeNPT const &particles, + IsotropicNptThermostat const &npt_iso, double time_step); #endif // NPT diff --git a/src/core/io/writer/h5md_core.hpp b/src/core/io/writer/h5md_core.hpp index ff133821cbc..ab4861b9ea7 100644 --- a/src/core/io/writer/h5md_core.hpp +++ b/src/core/io/writer/h5md_core.hpp @@ -103,7 +103,7 @@ inline auto fields_list_to_bitfield(std::vector const &fields) { class File { public: /** - * @brief Constructor of the File class. + * @brief Constructor. * @param file_path Name for the hdf5 file on disk. * @param script_path Path to the simulation script. * @param output_fields Properties to write to disk. diff --git a/src/core/lb/LBNone.hpp b/src/core/lb/LBNone.hpp index 690aececd68..bf913c53818 100644 --- a/src/core/lb/LBNone.hpp +++ b/src/core/lb/LBNone.hpp @@ -47,6 +47,7 @@ struct LBNone { } Utils::Vector3d get_momentum() const { throw NoLBActive{}; } void veto_time_step(double) const { throw NoLBActive{}; } + void veto_kT(double) const { throw NoLBActive{}; } void sanity_checks(System::System const &) const { throw NoLBActive{}; } void lebc_sanity_checks(unsigned int, unsigned int) const { throw NoLBActive{}; diff --git a/src/core/lb/LBWalberla.cpp b/src/core/lb/LBWalberla.cpp index 3bd7a421f42..8d8995a9274 100644 --- a/src/core/lb/LBWalberla.cpp +++ b/src/core/lb/LBWalberla.cpp @@ -28,10 +28,12 @@ #include "errorhandling.hpp" #include "integrate.hpp" #include "system/System.hpp" +#include "thermostat.hpp" #include #include +#include #include @@ -75,6 +77,15 @@ void LBWalberla::veto_time_step(double time_step) const { walberla_tau_sanity_checks("LB", lb_params->get_tau(), time_step); } +void LBWalberla::veto_kT(double kT) const { + auto const energy_conversion = + Utils::int_pow<2>(lb_params->get_agrid() / lb_params->get_tau()); + auto const lb_kT = lb_fluid->get_kT() * energy_conversion; + if (not ::Thermostat::are_kT_equal(lb_kT, kT)) { + throw std::runtime_error("Temperature change not supported by LB"); + } +} + void LBWalberla::sanity_checks(System::System const &system) const { auto const agrid = lb_params->get_agrid(); auto [lb_left, lb_right] = lb_fluid->get_lattice().get_local_domain(); diff --git a/src/core/lb/LBWalberla.hpp b/src/core/lb/LBWalberla.hpp index 64d54e9e933..16e35eb2b9f 100644 --- a/src/core/lb/LBWalberla.hpp +++ b/src/core/lb/LBWalberla.hpp @@ -68,6 +68,7 @@ struct LBWalberla { Utils::Vector3d const &force); void propagate(); void veto_time_step(double time_step) const; + void veto_kT(double kT) const; void sanity_checks(System::System const &system) const; void lebc_sanity_checks(unsigned int shear_direction, unsigned int shear_plane_normal) const; @@ -79,12 +80,8 @@ struct LBWalberla { void on_node_grid_change() const { throw std::runtime_error("MPI topology change not supported by LB"); } - void on_timestep_change() const { - throw std::runtime_error("Time step change not supported by LB"); - } - void on_temperature_change() const { - throw std::runtime_error("Temperature change not supported by LB"); - } + void on_timestep_change() const {} + void on_temperature_change() const {} }; } // namespace LB diff --git a/src/core/lb/Solver.cpp b/src/core/lb/Solver.cpp index 6dea3c09a19..8951595d829 100644 --- a/src/core/lb/Solver.cpp +++ b/src/core/lb/Solver.cpp @@ -28,6 +28,7 @@ #include "BoxGeometry.hpp" #include "system/System.hpp" +#include "thermostat.hpp" #ifdef WALBERLA #include @@ -83,6 +84,12 @@ void Solver::veto_time_step(double time_step) const { } } +void Solver::veto_kT(double kT) const { + if (impl->solver) { + std::visit([=](auto &ptr) { ptr->veto_kT(kT); }, *impl->solver); + } +} + void Solver::lebc_sanity_checks(unsigned int shear_direction, unsigned int shear_plane_normal) const { if (impl->solver) { diff --git a/src/core/lb/Solver.hpp b/src/core/lb/Solver.hpp index 78a707b1f35..e6012696d0c 100644 --- a/src/core/lb/Solver.hpp +++ b/src/core/lb/Solver.hpp @@ -37,15 +37,15 @@ struct Solver : public System::Leaf { Solver(); - /** @brief Return true if a @c LB solver is active. */ + /** @brief Return true if a LB solver is active. */ [[nodiscard]] bool is_solver_set() const; - /** @brief Remove the @c LB solver. */ + /** @brief Remove the LB solver. */ void reset(); /** - * @brief Set the @c LB solver. - * For developers: a specialization must exist for every new @c LB type. + * @brief Set the LB solver. + * For developers: a specialization must exist for every new LB type. */ template void set(Args... args); @@ -59,7 +59,7 @@ struct Solver : public System::Leaf { } /** - * @brief Propagate the @c LB fluid. + * @brief Propagate the LB fluid. */ void propagate(); @@ -70,33 +70,38 @@ struct Solver : public System::Leaf { void init() const {} /** - * @brief Perform @c LB parameter and boundary velocity checks. + * @brief Perform LB parameter and boundary velocity checks. */ void sanity_checks() const; /** - * @brief Check if a MD time step is compatible with the @c LB tau. + * @brief Check if a MD time step is compatible with the LB tau. */ void veto_time_step(double time_step) const; /** - * @brief Perform @c LB LEbc parameter checks. + * @brief Check if a thermostat is compatible with the LB temperature. + */ + void veto_kT(double kT) const; + + /** + * @brief Perform LB LEbc parameter checks. */ void lebc_sanity_checks(unsigned int shear_direction, unsigned int shear_plane_normal) const; /** - * @brief Get the @c LB time step. + * @brief Get the LB time step. */ double get_tau() const; /** - * @brief Get the @c LB grid spacing. + * @brief Get the LB grid spacing. */ double get_agrid() const; /** - * @brief Get the thermal energy parameter of the @c LB fluid. + * @brief Get the thermal energy parameter of the LB fluid. */ double get_kT() const; @@ -110,8 +115,8 @@ struct Solver : public System::Leaf { Utils::Vector3d get_momentum() const; /** - * @brief Calculate the interpolated fluid velocity in @c LB units. - * Use this function in MPI-parallel code. The @c LB ghost layer is ignored. + * @brief Calculate the interpolated fluid velocity in LB units. + * Use this function in MPI-parallel code. The LB ghost layer is ignored. * @param pos Position in MD units at which the velocity is to be calculated. * @retval interpolated fluid velocity. */ @@ -119,8 +124,8 @@ struct Solver : public System::Leaf { get_interpolated_velocity(Utils::Vector3d const &pos) const; /** - * @brief Calculate the interpolated fluid density in @c LB units. - * Use this function in MPI-parallel code. The @c LB ghost layer is ignored. + * @brief Calculate the interpolated fluid density in LB units. + * Use this function in MPI-parallel code. The LB ghost layer is ignored. * @param pos Position in MD units at which the density is to be calculated. * @retval interpolated fluid density. */ @@ -129,7 +134,7 @@ struct Solver : public System::Leaf { /** * @brief Calculate the interpolated fluid velocity in MD units. - * Special method used only for particle coupling. Uses the @c LB ghost layer. + * Special method used only for particle coupling. Uses the LB ghost layer. * @param pos Position in MD units at which the velocity is to be calculated. * @retval interpolated fluid velocity. */ diff --git a/src/core/lb/particle_coupling.cpp b/src/core/lb/particle_coupling.cpp index 10dcc8be663..cf10874730f 100644 --- a/src/core/lb/particle_coupling.cpp +++ b/src/core/lb/particle_coupling.cpp @@ -46,65 +46,6 @@ #include #include -LB::ParticleCouplingConfig lb_particle_coupling; - -static auto is_lb_active() { return System::get_system().lb.is_solver_set(); } - -void mpi_bcast_lb_particle_coupling_local() { - boost::mpi::broadcast(comm_cart, lb_particle_coupling, 0); -} - -REGISTER_CALLBACK(mpi_bcast_lb_particle_coupling_local) - -void mpi_bcast_lb_particle_coupling() { - mpi_call_all(mpi_bcast_lb_particle_coupling_local); -} - -void lb_lbcoupling_activate() { lb_particle_coupling.couple_to_md = true; } - -void lb_lbcoupling_deactivate() { - if (is_lb_active() and this_node == 0 and lb_particle_coupling.gamma > 0.) { - runtimeWarningMsg() - << "Recalculating forces, so the LB coupling forces are not " - "included in the particle force the first time step. This " - "only matters if it happens frequently during sampling."; - } - - lb_particle_coupling.couple_to_md = false; -} - -void lb_lbcoupling_set_gamma(double gamma) { - lb_particle_coupling.gamma = gamma; -} - -double lb_lbcoupling_get_gamma() { return lb_particle_coupling.gamma; } - -bool lb_lbcoupling_is_seed_required() { - if (is_lb_active()) { - return not lb_particle_coupling.rng_counter_coupling.is_initialized(); - } - return false; -} - -uint64_t lb_coupling_get_rng_state_cpu() { - return lb_particle_coupling.rng_counter_coupling->value(); -} - -uint64_t lb_lbcoupling_get_rng_state() { - if (is_lb_active()) { - return lb_coupling_get_rng_state_cpu(); - } - throw std::runtime_error("No LB active"); -} - -void lb_lbcoupling_set_rng_state(uint64_t counter) { - if (is_lb_active()) { - lb_particle_coupling.rng_counter_coupling = - Utils::Counter(counter); - } else - throw std::runtime_error("No LB active"); -} - void add_md_force(LB::Solver &lb, Utils::Vector3d const &pos, Utils::Vector3d const &force, double time_step) { /* transform momentum transfer to lattice units @@ -113,8 +54,8 @@ void add_md_force(LB::Solver &lb, Utils::Vector3d const &pos, lb.add_force_density(pos, delta_j); } -static Thermostat::GammaType lb_handle_particle_anisotropy(Particle const &p) { - auto const lb_gamma = lb_lbcoupling_get_gamma(); +static Thermostat::GammaType lb_handle_particle_anisotropy(Particle const &p, + double lb_gamma) { #ifdef THERMOSTAT_PER_PARTICLE auto const &partcl_gamma = p.gamma(); #ifdef PARTICLE_ANISOTROPY @@ -122,20 +63,21 @@ static Thermostat::GammaType lb_handle_particle_anisotropy(Particle const &p) { #else auto const default_gamma = lb_gamma; #endif // PARTICLE_ANISOTROPY - return handle_particle_gamma(partcl_gamma, default_gamma); + return Thermostat::handle_particle_gamma(partcl_gamma, default_gamma); #else return lb_gamma; #endif // THERMOSTAT_PER_PARTICLE } -Utils::Vector3d lb_drag_force(LB::Solver const &lb, Particle const &p, +Utils::Vector3d lb_drag_force(LB::Solver const &lb, double lb_gamma, + Particle const &p, Utils::Vector3d const &shifted_pos, Utils::Vector3d const &vel_offset) { /* calculate fluid velocity at particle's position this is done by linear interpolation (eq. (11) @cite ahlrichs99a) */ auto const v_fluid = lb.get_coupling_interpolated_velocity(shifted_pos); auto const v_drift = v_fluid + vel_offset; - auto const gamma = lb_handle_particle_anisotropy(p); + auto const gamma = lb_handle_particle_anisotropy(p, lb_gamma); /* calculate viscous force (eq. (9) @cite ahlrichs99a) */ return Utils::hadamard_product(gamma, v_drift - p.v()); @@ -149,11 +91,11 @@ Utils::Vector3d lb_drag_force(LB::Solver const &lb, Particle const &p, * * @return True iff the point is inside of the box up to halo. */ -static bool in_local_domain(Utils::Vector3d const &pos, double halo = 0.) { +static bool in_local_domain(LocalBox const &local_box, + Utils::Vector3d const &pos, double halo = 0.) { auto const halo_vec = Utils::Vector3d::broadcast(halo); - auto const &local_geo = *System::get_system().local_geo; - auto const lower_corner = local_geo.my_left() - halo_vec; - auto const upper_corner = local_geo.my_right() + halo_vec; + auto const lower_corner = local_box.my_left() - halo_vec; + auto const upper_corner = local_box.my_right() + halo_vec; return pos >= lower_corner and pos < upper_corner; } @@ -164,13 +106,10 @@ static bool in_box(Utils::Vector3d const &pos, return pos >= lower_corner and pos < upper_corner; } -static bool in_local_halo(Utils::Vector3d const &pos, double agrid) { +bool in_local_halo(LocalBox const &local_box, Utils::Vector3d const &pos, + double agrid) { auto const halo = 0.5 * agrid; - return in_local_domain(pos, halo); -} - -bool in_local_halo(Utils::Vector3d const &pos) { - return in_local_domain(pos, System::get_system().lb.get_agrid()); + return in_local_domain(local_box, pos, halo); } /** @@ -178,20 +117,18 @@ bool in_local_halo(Utils::Vector3d const &pos) { * coordinate */ std::vector positions_in_halo(Utils::Vector3d const &pos, - BoxGeometry const &box, + BoxGeometry const &box_geo, + LocalBox const &local_box, double agrid) { - auto const &system = System::get_system(); - auto const &box_geo = *system.box_geo; - auto const &local_geo = *system.local_geo; auto const halo = 0.5 * agrid; auto const halo_vec = Utils::Vector3d::broadcast(halo); - auto const fully_inside_lower = local_geo.my_left() + 2. * halo_vec; - auto const fully_inside_upper = local_geo.my_right() - 2. * halo_vec; + auto const fully_inside_lower = local_box.my_left() + 2. * halo_vec; + auto const fully_inside_upper = local_box.my_right() - 2. * halo_vec; if (in_box(pos, fully_inside_lower, fully_inside_upper)) { return {pos}; } - auto const halo_lower_corner = local_geo.my_left() - halo_vec; - auto const halo_upper_corner = local_geo.my_right() + halo_vec; + auto const halo_lower_corner = local_box.my_left() - halo_vec; + auto const halo_upper_corner = local_box.my_right() + halo_vec; std::vector res; for (int i : {-1, 0, 1}) { @@ -199,7 +136,7 @@ std::vector positions_in_halo(Utils::Vector3d const &pos, for (int k : {-1, 0, 1}) { Utils::Vector3d shift{{double(i), double(j), double(k)}}; Utils::Vector3d pos_shifted = - pos + Utils::hadamard_product(box.length(), shift); + pos + Utils::hadamard_product(box_geo.length(), shift); if (box_geo.type() == BoxType::LEES_EDWARDS) { auto le = box_geo.lees_edwards_bc(); @@ -225,36 +162,30 @@ Utils::Vector3d ParticleCoupling::get_noise_term(Particle const &p) const { if (not m_thermalized) { return Utils::Vector3d{}; } - auto const &rng_counter = lb_particle_coupling.rng_counter_coupling; - if (not rng_counter) { - throw std::runtime_error( - "Access to uninitialized LB particle coupling RNG counter"); - } using std::sqrt; using Utils::sqrt; - auto const counter = rng_counter->value(); - auto const gamma = lb_handle_particle_anisotropy(p); - return m_noise_pref_wo_gamma * - Utils::hadamard_product( - sqrt(gamma), - Random::noise_uniform(counter, 0, p.id())); + auto const gamma = lb_handle_particle_anisotropy(p, m_thermostat.gamma); + auto const noise = Random::noise_uniform( + m_thermostat.rng_counter(), m_thermostat.rng_seed(), p.id()); + return m_noise_pref_wo_gamma * Utils::hadamard_product(sqrt(gamma), noise); } void ParticleCoupling::kernel(Particle &p) { auto const agrid = m_lb.get_agrid(); - auto const &box_geo = *System::get_system().box_geo; // Calculate coupling force Utils::Vector3d force_on_particle = {}; - auto folded_pos = box_geo.folded_position(p.pos()); + auto const halo_pos = positions_in_halo(m_box_geo.folded_position(p.pos()), + m_box_geo, m_local_box, agrid); #ifdef ENGINE if (not p.swimming().is_engine_force_on_fluid) #endif - for (auto pos : positions_in_halo(folded_pos, box_geo, agrid)) { - if (in_local_halo(pos, agrid)) { + for (auto const &pos : halo_pos) { + if (in_local_halo(m_local_box, pos, agrid)) { auto const vel_offset = lb_drift_velocity_offset(p); - auto const drag_force = lb_drag_force(m_lb, p, pos, vel_offset); + auto const drag_force = + lb_drag_force(m_lb, m_thermostat.gamma, p, pos, vel_offset); auto const random_force = get_noise_term(p); force_on_particle = drag_force + random_force; break; @@ -270,8 +201,8 @@ void ParticleCoupling::kernel(Particle &p) { // couple positions including shifts by one box length to add // forces to ghost layers - for (auto pos : positions_in_halo(folded_pos, box_geo, agrid)) { - if (in_local_domain(pos)) { + for (auto const &pos : halo_pos) { + if (in_local_domain(m_local_box, pos)) { /* Particle is in our LB volume, so this node * is responsible to adding its force */ p.force() += force_on_particle; @@ -280,12 +211,6 @@ void ParticleCoupling::kernel(Particle &p) { } } -bool CouplingBookkeeping::is_ghost_for_local_particle(Particle const &p) const { - auto const &system = System::get_system(); - auto const &cell_structure = *system.cell_structure; - return not cell_structure.get_local_particle(p.id())->is_ghost(); -} - #if defined(THERMOSTAT_PER_PARTICLE) and defined(PARTICLE_ANISOTROPY) static void lb_coupling_sanity_checks(Particle const &p) { /* @@ -300,35 +225,32 @@ static void lb_coupling_sanity_checks(Particle const &p) { } #endif -void couple_particles(ParticleRange const &real_particles, - ParticleRange const &ghost_particles, double time_step) { +} // namespace LB + +void System::System::lb_couple_particles(double time_step) { #ifdef CALIPER CALI_CXX_MARK_FUNCTION; #endif - if (lb_particle_coupling.couple_to_md) { - auto &lb = System::get_system().lb; - if (lb.is_solver_set()) { - ParticleCoupling coupling{lb, time_step}; - CouplingBookkeeping bookkeeping{}; - for (auto const &particle_range : {real_particles, ghost_particles}) { - for (auto &p : particle_range) { - if (not LB::is_tracer(p) and bookkeeping.should_be_coupled(p)) { + assert(thermostat->lb != nullptr); + if (thermostat->lb->couple_to_md) { + if (not lb.is_solver_set()) { + runtimeErrorMsg() << "The LB thermostat requires a LB fluid"; + return; + } + auto const real_particles = cell_structure->local_particles(); + auto const ghost_particles = cell_structure->ghost_particles(); + LB::ParticleCoupling coupling{*thermostat->lb, lb, *box_geo, *local_geo, + time_step}; + LB::CouplingBookkeeping bookkeeping{*cell_structure}; + for (auto const *particle_range : {&real_particles, &ghost_particles}) { + for (auto &p : *particle_range) { + 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); + LB::lb_coupling_sanity_checks(p); #endif - coupling.kernel(p); - } + coupling.kernel(p); } } } } } - -} // namespace LB - -void lb_lbcoupling_propagate() { - auto const &lb = System::get_system().lb; - if (lb.is_solver_set() and lb.get_kT() > 0.0) { - lb_particle_coupling.rng_counter_coupling->increment(); - } -} diff --git a/src/core/lb/particle_coupling.hpp b/src/core/lb/particle_coupling.hpp index 2213c8ee3f1..d5e7cdf3a5b 100644 --- a/src/core/lb/particle_coupling.hpp +++ b/src/core/lb/particle_coupling.hpp @@ -20,55 +20,29 @@ #pragma once #include "BoxGeometry.hpp" +#include "LocalBox.hpp" #include "Particle.hpp" #include "ParticleRange.hpp" #include "PropagationMode.hpp" +#include "cell_system/CellStructure.hpp" #include "lb/Solver.hpp" +#include "system/System.hpp" +#include "thermostat.hpp" -#include #include #include -#include -#include - -#include #include -#include #include #include -using OptionalCounter = boost::optional>; - -void lb_lbcoupling_propagate(); -uint64_t lb_lbcoupling_get_rng_state(); -void lb_lbcoupling_set_rng_state(uint64_t counter); -void lb_lbcoupling_set_gamma(double friction); -double lb_lbcoupling_get_gamma(); -bool lb_lbcoupling_is_seed_required(); - -/** - * @brief Activate the coupling between LB and MD particles. - * @note This is a collective function and needs to be called from all - * processes. - */ -void lb_lbcoupling_activate(); - -/** - * @brief Deactivate the coupling between LB and MD particles. - * @note This is a collective function and needs to be called from all - * processes. - */ -void lb_lbcoupling_deactivate(); - /** * @brief Check if a position is within the local LB domain plus halo. * - * @param pos Position to check - * * @return True iff the point is inside of the domain. */ -bool in_local_halo(Utils::Vector3d const &pos); +bool in_local_halo(LocalBox const &local_box, Utils::Vector3d const &pos, + double agrid); /** * @brief Add a force to the lattice force density. @@ -83,18 +57,15 @@ void add_md_force(LB::Solver &lb, Utils::Vector3d const &pos, // internal function exposed for unit testing std::vector positions_in_halo(Utils::Vector3d const &pos, BoxGeometry const &box, + LocalBox const &local_geo, double agrid); -// internal function exposed for unit testing -void add_swimmer_force(Particle const &p, double time_step); - -void mpi_bcast_lb_particle_coupling(); - /** @brief Calculate drag force on a single particle. * * See section II.C. @cite ahlrichs99a * * @param[in] lb The coupled fluid + * @param[in] lb_gamma The friction coefficient * @param[in] p The coupled particle * @param[in] shifted_pos The particle position in LB units with optional shift * @param[in] vel_offset Velocity offset in MD units to be added to @@ -102,60 +73,39 @@ void mpi_bcast_lb_particle_coupling(); * * @return The viscous coupling force */ -Utils::Vector3d lb_drag_force(LB::Solver const &lb, Particle const &p, +Utils::Vector3d lb_drag_force(LB::Solver const &lb, double lb_gamma, + Particle const &p, Utils::Vector3d const &shifted_pos, Utils::Vector3d const &vel_offset); -namespace LB { -struct ParticleCouplingConfig { - OptionalCounter rng_counter_coupling = {}; - /** @brief Friction coefficient for the particle coupling. */ - double gamma = 0.0; - bool couple_to_md = false; - -private: - friend class boost::serialization::access; - - template void serialize(Archive &ar, const unsigned int) { - ar &rng_counter_coupling; - ar γ - ar &couple_to_md; - } -}; -} // namespace LB - -// internal global exposed for unit testing -extern LB::ParticleCouplingConfig lb_particle_coupling; - namespace LB { -/** @brief Calculate particle-lattice interactions. */ -void couple_particles(ParticleRange const &real_particles, - ParticleRange const &ghost_particles, double time_step); - class ParticleCoupling { + LBThermostat const &m_thermostat; LB::Solver &m_lb; - bool m_thermalized; + BoxGeometry const &m_box_geo; + LocalBox const &m_local_box; double m_time_step; double m_noise_pref_wo_gamma; + bool m_thermalized; public: - ParticleCoupling(LB::Solver &lb, double time_step, double kT) - : m_lb{lb}, m_thermalized{kT != 0.}, m_time_step{time_step} { - assert(kT >= 0.); + ParticleCoupling(LBThermostat const &thermostat, LB::Solver &lb, + BoxGeometry const &box_geo, LocalBox const &local_box, + double time_step) + : m_thermostat{thermostat}, m_lb{lb}, m_box_geo{box_geo}, + m_local_box{local_box}, m_time_step{time_step} { /* Eq. (16) @cite ahlrichs99a, without the gamma term. * The factor 12 comes from the fact that we use random numbers * from -0.5 to 0.5 (equally distributed) which have variance 1/12. * The time step comes from the discretization. */ auto constexpr variance_inv = 12.; + auto const kT = lb.get_kT() * Utils::sqr(lb.get_lattice_speed()); + m_thermalized = (kT != 0.); m_noise_pref_wo_gamma = std::sqrt(variance_inv * 2. * kT / 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; void kernel(Particle &p); @@ -179,11 +129,17 @@ class ParticleCoupling { */ class CouplingBookkeeping { std::unordered_set m_coupled_ghosts; + CellStructure const &m_cell_structure; /** @brief Check if there is locally a real particle for the given ghost. */ - bool is_ghost_for_local_particle(Particle const &p) const; + bool is_ghost_for_local_particle(Particle const &p) const { + return not m_cell_structure.get_local_particle(p.id())->is_ghost(); + } public: + explicit CouplingBookkeeping(CellStructure const &cell_structure) + : m_cell_structure{cell_structure} {} + /** @brief Determine if a given particle should be coupled. */ bool should_be_coupled(Particle const &p) { auto const propagation = p.propagation(); diff --git a/src/core/magnetostatics/dlc.cpp b/src/core/magnetostatics/dlc.cpp index 34336273ac1..51e7662a9a6 100644 --- a/src/core/magnetostatics/dlc.cpp +++ b/src/core/magnetostatics/dlc.cpp @@ -106,7 +106,7 @@ static Utils::Vector3d calc_slab_dipole(ParticleRange const &particles) { /** * @brief Compute the dipolar force and torque corrections. - * %Algorithm implemented accordingly to @cite brodka04a. + * Algorithm implemented accordingly to @cite brodka04a. */ static void dipolar_force_corrections(int kcut, std::vector &fs, @@ -242,7 +242,7 @@ static void dipolar_force_corrections(int kcut, /** * @brief Compute the dipolar DLC energy correction. - * %Algorithm implemented accordingly to @cite brodka04a. + * Algorithm implemented accordingly to @cite brodka04a. */ static double dipolar_energy_correction(int kcut, ParticleRange const &particles, diff --git a/src/core/object-in-fluid/oif_local_forces.hpp b/src/core/object-in-fluid/oif_local_forces.hpp index cc3d63371bd..b4d622cbd6f 100644 --- a/src/core/object-in-fluid/oif_local_forces.hpp +++ b/src/core/object-in-fluid/oif_local_forces.hpp @@ -100,9 +100,9 @@ struct OifLocalForcesBond { /** Compute the OIF local forces. * See @cite dupin07a, @cite jancigova16a. * @param box_geo Box geometry. - * @param p2 %Particle of triangle 1. + * @param p2 Particle of triangle 1. * @param p1 , p3 Particles common to triangle 1 and triangle 2. - * @param p4 %Particle of triangle 2. + * @param p4 Particle of triangle 2. * @return forces on @p p1, @p p2, @p p3, @p p4 */ inline std::tuple>; -/** %Particle-based observable. +/** Particle-based observable. * * Base class for observables extracting raw data from particle subsets and * returning either the data or a statistic derived from it. @@ -188,8 +188,8 @@ get_all_particle_positions(boost::mpi::communicator const &comm, * implements necessary algorithms needed for observables that are based on * single particle properties. * @tparam ObsType An observables composed of an algorithm from - * src/particle_observables/include/particle_observables/algorithms.hpp and two - * particle properties. + * @ref src/particle_observables/include/particle_observables/algorithms.hpp + * and two particle properties. * * Example usage: * @code{.cpp} diff --git a/src/core/pressure_inline.hpp b/src/core/pressure_inline.hpp index 5e35da4ce20..924440b3463 100644 --- a/src/core/pressure_inline.hpp +++ b/src/core/pressure_inline.hpp @@ -50,8 +50,8 @@ * @param d vector between p1 and p2. * @param dist distance between p1 and p2. * @param ia_params non-bonded interaction kernels. - * @param kernel_forces %Coulomb force kernel. - * @param kernel_pressure %Coulomb pressure kernel. + * @param kernel_forces Coulomb force kernel. + * @param kernel_pressure Coulomb pressure kernel. * @param[in,out] obs_pressure pressure observable. */ inline void add_non_bonded_pair_virials( diff --git a/src/core/rotation.cpp b/src/core/rotation.cpp index d248e483389..999359c0b41 100644 --- a/src/core/rotation.cpp +++ b/src/core/rotation.cpp @@ -46,7 +46,7 @@ * See @cite sonnenschein85a. Please note that ESPResSo uses scalar-first * notation for quaternions, while @cite sonnenschein85a uses scalar-last * notation. - * @param[in] p %Particle + * @param[in] p Particle * @param[out] Qd First derivative of the particle quaternion * @param[out] Qdd Second derivative of the particle quaternion * @param[out] S Function of @p Qd and @p Qdd, used to evaluate the diff --git a/src/core/stokesian_dynamics/sd_interface.cpp b/src/core/stokesian_dynamics/sd_interface.cpp index e9802142dd9..14685359462 100644 --- a/src/core/stokesian_dynamics/sd_interface.cpp +++ b/src/core/stokesian_dynamics/sd_interface.cpp @@ -71,8 +71,6 @@ BOOST_IS_BITWISE_SERIALIZABLE(SD_particle_data) static StokesianDynamicsParameters params{0., {}, 0}; -static double sd_kT = 0.0; - /** Buffer that holds the (translational and angular) velocities of the local * particles on each node, used for returning results. */ static std::vector v_sd{}; @@ -127,18 +125,9 @@ StokesianDynamicsParameters::StokesianDynamicsParameters( } } -void set_sd_kT(double kT) { - if (kT < 0.0) { - throw std::domain_error("kT has an invalid value: " + std::to_string(kT)); - } - - sd_kT = kT; -} - -double get_sd_kT() { return sd_kT; } - void propagate_vel_pos_sd(ParticleRangeStokesian const &particles, - double const time_step) { + StokesianThermostat const &stokesian, + double const time_step, double const kT) { static std::vector parts_buffer{}; @@ -184,7 +173,7 @@ void propagate_vel_pos_sd(ParticleRangeStokesian const &particles, } v_sd = sd_cpu(x_host, f_host, a_host, n_part, params.viscosity, - std::sqrt(sd_kT / time_step), + std::sqrt(kT / time_step), static_cast(stokesian.rng_counter()), static_cast(stokesian.rng_seed()), params.flags); } else { // if (this_node == 0) diff --git a/src/core/stokesian_dynamics/sd_interface.hpp b/src/core/stokesian_dynamics/sd_interface.hpp index 7977c31e066..695dc576dfa 100644 --- a/src/core/stokesian_dynamics/sd_interface.hpp +++ b/src/core/stokesian_dynamics/sd_interface.hpp @@ -31,6 +31,7 @@ #include "ParticleRange.hpp" #include "PropagationMode.hpp" #include "PropagationPredicate.hpp" +#include "thermostat.hpp" #include @@ -67,15 +68,13 @@ enum class sd_flags : int { void register_integrator(StokesianDynamicsParameters const &obj); -void set_sd_kT(double kT); -double get_sd_kT(); - /** Takes the forces and torques on all particles and computes their * velocities. Acts globally on particles on all nodes; i.e. particle data * is gathered from all nodes and their velocities and angular velocities are * set according to the Stokesian Dynamics method. */ void propagate_vel_pos_sd(ParticleRangeStokesian const &particles, - double time_step); + StokesianThermostat const &stokesian, + double time_step, double kT); #endif // STOKESIAN_DYNAMICS diff --git a/src/core/system/System.cpp b/src/core/system/System.cpp index 075bbb30be0..66c0e35d78d 100644 --- a/src/core/system/System.cpp +++ b/src/core/system/System.cpp @@ -26,6 +26,7 @@ #include "LocalBox.hpp" #include "PropagationMode.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" +#include "bonded_interactions/thermalized_bond.hpp" #include "cell_system/CellStructure.hpp" #include "cell_system/CellStructureType.hpp" #include "cell_system/HybridDecomposition.hpp" @@ -34,7 +35,6 @@ #include "constraints.hpp" #include "electrostatics/icc.hpp" #include "errorhandling.hpp" -#include "global_ghost_flags.hpp" #include "immersed_boundaries.hpp" #include "npt.hpp" #include "particle_node.hpp" @@ -61,6 +61,7 @@ System::System(Private) { local_geo = std::make_shared(); cell_structure = std::make_shared(*box_geo); propagation = std::make_shared(); + thermostat = std::make_shared(); nonbonded_ias = std::make_shared(); comfixed = std::make_shared(); galilei = std::make_shared(); @@ -77,6 +78,7 @@ void System::initialize() { auto handle = shared_from_this(); cell_structure->bind_system(handle); lees_edwards->bind_system(handle); + thermostat->bind_system(handle); #ifdef CUDA gpu.bind_system(handle); gpu.initialize(); @@ -108,6 +110,15 @@ void System::set_time_step(double value) { on_timestep_change(); } +void System::check_kT(double value) const { + if (lb.is_solver_set()) { + lb.veto_kT(value); + } + if (ek.is_solver_set()) { + ek.veto_kT(value); + } +} + void System::set_force_cap(double value) { force_cap = value; propagation->recalc_forces = true; @@ -256,7 +267,7 @@ void System::on_lb_boundary_conditions_change() { } void System::on_particle_local_change() { - cell_structure->update_ghosts_and_resort_particle(global_ghost_flags()); + cell_structure->update_ghosts_and_resort_particle(get_global_ghost_flags()); propagation->recalc_forces = true; } @@ -289,7 +300,7 @@ void System::update_dependent_particles() { #ifdef VIRTUAL_SITES_RELATIVE vs_relative_update_particles(*cell_structure, *box_geo); #endif - cell_structure->update_ghosts_and_resort_particle(global_ghost_flags()); + cell_structure->update_ghosts_and_resort_particle(get_global_ghost_flags()); #endif #ifdef ELECTROSTATICS @@ -305,7 +316,7 @@ void System::update_dependent_particles() { void System::on_observable_calc() { /* Prepare particle structure: Communication step: number of ghosts and ghost * information */ - cell_structure->update_ghosts_and_resort_particle(global_ghost_flags()); + cell_structure->update_ghosts_and_resort_particle(get_global_ghost_flags()); update_dependent_particles(); #ifdef ELECTROSTATICS @@ -384,7 +395,7 @@ void System::on_integration_start() { /* Prepare the thermostat */ if (reinit_thermo) { - thermo_init(time_step); + thermostat->recalc_prefactors(time_step); reinit_thermo = false; propagation->recalc_forces = true; } @@ -422,6 +433,35 @@ void System::on_integration_start() { on_observable_calc(); } +/** + * @brief Returns the ghost flags required for running pair + * kernels for the global state, e.g. the force calculation. + * @return Required data parts; + */ +unsigned System::get_global_ghost_flags() const { + /* Position and Properties are always requested. */ + unsigned data_parts = Cells::DATA_PART_POSITION | Cells::DATA_PART_PROPERTIES; + + if (lb.is_solver_set()) + data_parts |= Cells::DATA_PART_MOMENTUM; + + if (thermostat->thermo_switch & THERMO_DPD) + data_parts |= Cells::DATA_PART_MOMENTUM; + + if (thermostat->thermo_switch & THERMO_BOND) { + data_parts |= Cells::DATA_PART_MOMENTUM; + data_parts |= Cells::DATA_PART_BONDS; + } + +#ifdef COLLISION_DETECTION + if (::collision_params.mode != CollisionModeType::OFF) { + data_parts |= Cells::DATA_PART_BONDS; + } +#endif + + return data_parts; +} + } // namespace System void mpi_init_stand_alone(int argc, char **argv) { diff --git a/src/core/system/System.dox b/src/core/system/System.dox index f4b816c5851..efdfbbe6f22 100644 --- a/src/core/system/System.dox +++ b/src/core/system/System.dox @@ -18,7 +18,7 @@ */ /** - * @page SystemClassDesign %System class design + * @page SystemClassDesign System class design * * @tableofcontents * @@ -58,7 +58,7 @@ * - Include the relevant header in @ref src/core/system/System.impl.hpp. * - Adapt the @ref System::System::System constructor accordingly. * - script interface: - * - Define the %ScriptInterface class of the new feature, which serves as + * - Define the @c ScriptInterface class of the new feature, which serves as * the connection between the C++ core instance and the Python instance. * - If relevant, override * @ref ScriptInterface::System::Leaf::on_bind_system and diff --git a/src/core/system/System.hpp b/src/core/system/System.hpp index 0e6174457ce..4a5b1e853a7 100644 --- a/src/core/system/System.hpp +++ b/src/core/system/System.hpp @@ -41,6 +41,9 @@ class LocalBox; struct CellStructure; class Propagation; class InteractionsNonBonded; +namespace Thermostat { +class Thermostat; +} class ComFixed; class Galilei; class Observable_stat; @@ -121,6 +124,8 @@ class System : public std::enable_shared_from_this { /** @brief Get the interaction range. */ double get_interaction_range() const; + unsigned get_global_ghost_flags() const; + /** Check electrostatic and magnetostatic methods are properly initialized. * @return true if sanity checks failed. */ @@ -133,7 +138,7 @@ class System : public std::enable_shared_from_this { std::shared_ptr calculate_pressure(); /** @brief Calculate all forces. */ - void calculate_forces(double kT); + void calculate_forces(); #ifdef DIPOLE_FIELD_TRACKING /** @brief Calculate dipole fields. */ @@ -188,7 +193,10 @@ 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); + /** @brief Calculate initial particle forces from active thermostats. */ + void thermostat_force_init(); + /** @brief Calculate particle-lattice interactions. */ + void lb_couple_particles(double time_step); /** \name Hook procedures * These procedures are called if several significant changes to @@ -244,6 +252,10 @@ class System : public std::enable_shared_from_this { * @brief Update the global propagation bitmask. */ void update_used_propagations(); + /** + * @brief Veto temperature change. + */ + void check_kT(double value) const; Coulomb::Solver coulomb; Dipoles::Solver dipoles; @@ -254,6 +266,7 @@ class System : public std::enable_shared_from_this { std::shared_ptr cell_structure; std::shared_ptr propagation; std::shared_ptr nonbonded_ias; + std::shared_ptr thermostat; std::shared_ptr comfixed; std::shared_ptr galilei; std::shared_ptr bond_breakage; diff --git a/src/core/system/System.impl.hpp b/src/core/system/System.impl.hpp index 3a2c1e9f662..45dc104b29e 100644 --- a/src/core/system/System.impl.hpp +++ b/src/core/system/System.impl.hpp @@ -32,6 +32,7 @@ #include "integrators/Propagation.hpp" #include "lees_edwards/lees_edwards.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" +#include "thermostat.hpp" #include "BoxGeometry.hpp" #include "LocalBox.hpp" diff --git a/src/core/thermostat.cpp b/src/core/thermostat.cpp index cc44101ac32..ed55999f4ff 100644 --- a/src/core/thermostat.cpp +++ b/src/core/thermostat.cpp @@ -24,8 +24,8 @@ #include "config/config.hpp" +#include "bonded_interactions/bonded_interaction_data.hpp" #include "bonded_interactions/thermalized_bond.hpp" -#include "bonded_interactions/thermalized_bond_utils.hpp" #include "communication.hpp" #include "dpd.hpp" #include "errorhandling.hpp" @@ -33,192 +33,74 @@ #include "system/System.hpp" #include "thermostat.hpp" -#include +#include -#include -#include - -int thermo_switch = THERMO_OFF; -double temperature = 0.0; - -using Thermostat::GammaType; - -/** - * @brief Create MPI callbacks of thermostat objects - * - * @param thermostat The thermostat object name - */ -#define REGISTER_THERMOSTAT_CALLBACKS(thermostat) \ - void mpi_##thermostat##_set_rng_seed(uint32_t const seed) { \ - (thermostat).rng_initialize(seed); \ - } \ - \ - REGISTER_CALLBACK(mpi_##thermostat##_set_rng_seed) \ - \ - void thermostat##_set_rng_seed(uint32_t const seed) { \ - mpi_call_all(mpi_##thermostat##_set_rng_seed, seed); \ - } \ - \ - void mpi_##thermostat##_set_rng_counter(uint64_t const value) { \ - (thermostat).set_rng_counter(value); \ - } \ - \ - REGISTER_CALLBACK(mpi_##thermostat##_set_rng_counter) \ - \ - void thermostat##_set_rng_counter(uint64_t const value) { \ - mpi_call_all(mpi_##thermostat##_set_rng_counter, value); \ +void Thermostat::Thermostat::recalc_prefactors(double time_step) { + if (thermalized_bond) { + thermalized_bond->recalc_prefactors(time_step); } - -LangevinThermostat langevin = {}; -BrownianThermostat brownian = {}; -#ifdef NPT -IsotropicNptThermostat npt_iso = {}; -#endif -ThermalizedBondThermostat thermalized_bond = {}; -#ifdef DPD -DPDThermostat dpd = {}; -#endif -#ifdef STOKESIAN_DYNAMICS -StokesianThermostat stokesian = {}; -#endif - -REGISTER_THERMOSTAT_CALLBACKS(langevin) -REGISTER_THERMOSTAT_CALLBACKS(brownian) -#ifdef NPT -REGISTER_THERMOSTAT_CALLBACKS(npt_iso) -#endif -REGISTER_THERMOSTAT_CALLBACKS(thermalized_bond) -#ifdef DPD -REGISTER_THERMOSTAT_CALLBACKS(dpd) -#endif -#ifdef STOKESIAN_DYNAMICS -REGISTER_THERMOSTAT_CALLBACKS(stokesian) -#endif - -void thermo_init(double time_step) { - // initialize thermalized bond regardless of the current thermostat - if (n_thermalized_bonds) { - thermalized_bond_init(time_step); + if (langevin) { + langevin->recalc_prefactors(kT, time_step); } - if (thermo_switch == THERMO_OFF) { - return; + if (brownian) { + brownian->recalc_prefactors(kT); } - if (thermo_switch & THERMO_LANGEVIN) - langevin.recalc_prefactors(temperature, time_step); #ifdef DPD - if (thermo_switch & THERMO_DPD) - dpd_init(temperature, time_step); + if (dpd) { + dpd_init(kT, time_step); + } #endif #ifdef NPT - if (thermo_switch & THERMO_NPT_ISO) { - npt_iso.recalc_prefactors(temperature, nptiso.piston, time_step); + if (npt_iso) { + npt_iso->recalc_prefactors(kT, nptiso.piston, time_step); } #endif - if (thermo_switch & THERMO_BROWNIAN) - brownian.recalc_prefactors(temperature); } -void philox_counter_increment() { +void Thermostat::Thermostat::philox_counter_increment() { if (thermo_switch & THERMO_LANGEVIN) { - langevin.rng_increment(); + langevin->rng_increment(); } if (thermo_switch & THERMO_BROWNIAN) { - brownian.rng_increment(); + brownian->rng_increment(); } #ifdef NPT if (thermo_switch & THERMO_NPT_ISO) { - npt_iso.rng_increment(); + npt_iso->rng_increment(); } #endif #ifdef DPD if (thermo_switch & THERMO_DPD) { - dpd.rng_increment(); + dpd->rng_increment(); } #endif #ifdef STOKESIAN_DYNAMICS if (thermo_switch & THERMO_SD) { - stokesian.rng_increment(); + stokesian->rng_increment(); } #endif - if (n_thermalized_bonds) { - thermalized_bond.rng_increment(); + if (thermo_switch & THERMO_BOND) { + thermalized_bond->rng_increment(); } } -void mpi_set_brownian_gamma_local(GammaType const &gamma) { - brownian.gamma = gamma; -} - -void mpi_set_brownian_gamma_rot_local(GammaType const &gamma) { - brownian.gamma_rotation = gamma; -} - -void mpi_set_langevin_gamma_local(GammaType const &gamma) { - langevin.gamma = gamma; - System::get_system().on_thermostat_param_change(); -} - -void mpi_set_langevin_gamma_rot_local(GammaType const &gamma) { - langevin.gamma_rotation = gamma; - System::get_system().on_thermostat_param_change(); -} - -REGISTER_CALLBACK(mpi_set_brownian_gamma_local) -REGISTER_CALLBACK(mpi_set_brownian_gamma_rot_local) -REGISTER_CALLBACK(mpi_set_langevin_gamma_local) -REGISTER_CALLBACK(mpi_set_langevin_gamma_rot_local) - -void mpi_set_brownian_gamma(GammaType const &gamma) { - mpi_call_all(mpi_set_brownian_gamma_local, gamma); -} - -void mpi_set_brownian_gamma_rot(GammaType const &gamma) { - mpi_call_all(mpi_set_brownian_gamma_rot_local, gamma); -} - -void mpi_set_langevin_gamma(GammaType const &gamma) { - mpi_call_all(mpi_set_langevin_gamma_local, gamma); -} -void mpi_set_langevin_gamma_rot(GammaType const &gamma) { - mpi_call_all(mpi_set_langevin_gamma_rot_local, gamma); -} - -void mpi_set_temperature_local(double temperature) { - ::temperature = temperature; - try { - System::get_system().on_temperature_change(); - } catch (std::exception const &err) { - runtimeErrorMsg() << err.what(); +void Thermostat::Thermostat::lb_coupling_deactivate() { + if (lb) { + if (get_system().lb.is_solver_set() and ::comm_cart.rank() == 0 and + lb->gamma > 0.) { + runtimeWarningMsg() + << "Recalculating forces, so the LB coupling forces are not " + "included in the particle force the first time step. This " + "only matters if it happens frequently during sampling."; + } + lb->couple_to_md = false; } - System::get_system().on_thermostat_param_change(); -} - -REGISTER_CALLBACK(mpi_set_temperature_local) - -void mpi_set_temperature(double temperature) { - mpi_call_all(mpi_set_temperature_local, temperature); -} - -void mpi_set_thermo_switch_local(int thermo_switch) { - ::thermo_switch = thermo_switch; -} - -REGISTER_CALLBACK(mpi_set_thermo_switch_local) - -void mpi_set_thermo_switch(int thermo_switch) { - mpi_call_all(mpi_set_thermo_switch_local, thermo_switch); } -#ifdef NPT -void mpi_set_nptiso_gammas_local(double gamma0, double gammav) { - npt_iso.gamma0 = gamma0; - npt_iso.gammav = gammav; - System::get_system().on_thermostat_param_change(); -} - -REGISTER_CALLBACK(mpi_set_nptiso_gammas_local) - -void mpi_set_nptiso_gammas(double gamma0, double gammav) { - mpi_call_all(mpi_set_nptiso_gammas_local, gamma0, gammav); +void ThermalizedBondThermostat::recalc_prefactors(double time_step) { + for (auto &kv : ::bonded_ia_params) { + if (auto *bond = boost::get(&(*kv.second))) { + bond->recalc_prefactors(time_step); + } + } } -#endif diff --git a/src/core/thermostat.hpp b/src/core/thermostat.hpp index 88878664599..88ed175641a 100644 --- a/src/core/thermostat.hpp +++ b/src/core/thermostat.hpp @@ -26,85 +26,53 @@ */ #include "Particle.hpp" +#include "PropagationMode.hpp" #include "rotation.hpp" +#include "system/Leaf.hpp" #include "config/config.hpp" #include #include -#include - #include #include #include -/** \name Thermostat switches */ -/**@{*/ -#define THERMO_OFF 0 -#define THERMO_LANGEVIN 1 -#define THERMO_DPD 2 -#define THERMO_NPT_ISO 4 -#define THERMO_LB 8 -#define THERMO_BROWNIAN 16 -#define THERMO_SD 32 -/**@}*/ - namespace Thermostat { #ifdef PARTICLE_ANISOTROPY using GammaType = Utils::Vector3d; #else using GammaType = double; #endif -} // namespace Thermostat - -namespace { /** * @brief Value for unset friction coefficient. * Sentinel value for the Langevin/Brownian parameters, * indicating that they have not been set yet. */ #ifdef PARTICLE_ANISOTROPY -constexpr Thermostat::GammaType gamma_sentinel{{-1.0, -1.0, -1.0}}; +constexpr GammaType gamma_sentinel{{-1.0, -1.0, -1.0}}; #else -constexpr Thermostat::GammaType gamma_sentinel{-1.0}; +constexpr GammaType gamma_sentinel{-1.0}; #endif /** * @brief Value for a null friction coefficient. */ #ifdef PARTICLE_ANISOTROPY -constexpr Thermostat::GammaType gamma_null{{0.0, 0.0, 0.0}}; +constexpr GammaType gamma_null{{0.0, 0.0, 0.0}}; #else -constexpr Thermostat::GammaType gamma_null{0.0}; +constexpr GammaType gamma_null{0.0}; #endif -} // namespace - -/************************************************ - * exported variables - ************************************************/ - -/** Switch determining which thermostat(s) to use. This is a or'd value - * of the different possible thermostats (defines: \ref THERMO_OFF, - * \ref THERMO_LANGEVIN, \ref THERMO_DPD \ref THERMO_NPT_ISO). If it - * is zero all thermostats are switched off and the temperature is - * set to zero. - */ -extern int thermo_switch; - -/** Temperature of the thermostat. */ -extern double temperature; #ifdef THERMOSTAT_PER_PARTICLE -inline auto const & -handle_particle_gamma(Thermostat::GammaType const &particle_gamma, - Thermostat::GammaType const &default_gamma) { +inline auto const &handle_particle_gamma(GammaType const &particle_gamma, + GammaType const &default_gamma) { return particle_gamma >= gamma_null ? particle_gamma : default_gamma; } #endif -inline auto -handle_particle_anisotropy(Particle const &p, - Thermostat::GammaType const &gamma_body) { +inline auto handle_particle_anisotropy(Particle const &p, + GammaType const &gamma_body) { #ifdef PARTICLE_ANISOTROPY auto const aniso_flag = (gamma_body[0] != gamma_body[1]) || (gamma_body[1] != gamma_body[2]); @@ -118,33 +86,48 @@ handle_particle_anisotropy(Particle const &p, #endif } -/************************************************ - * parameter structs - ************************************************/ +/** @brief Check that two kT values are close up to a small tolerance. */ +inline bool are_kT_equal(double old_kT, double new_kT) { + constexpr auto relative_tolerance = 1e-6; + if (old_kT == 0. and new_kT == 0.) { + return true; + } + if ((old_kT < 0. and new_kT >= 0.) or (old_kT >= 0. and new_kT < 0.)) { + return false; + } + auto const large_kT = (old_kT > new_kT) ? old_kT : new_kT; + auto const small_kT = (old_kT > new_kT) ? new_kT : old_kT; + return (large_kT / small_kT - 1. < relative_tolerance); +} +} // namespace Thermostat struct BaseThermostat { public: /** Initialize or re-initialize the RNG counter with a seed. */ - void rng_initialize(uint32_t const seed) { m_rng_seed = seed; } + void rng_initialize(uint32_t const seed) { + m_rng_seed = seed; + m_initialized = true; + } /** Increment the RNG counter */ void rng_increment() { m_rng_counter.increment(); } /** Get current value of the RNG */ uint64_t rng_counter() const { return m_rng_counter.value(); } void set_rng_counter(uint64_t value) { - m_rng_counter = Utils::Counter(0u, value); + m_rng_counter = Utils::Counter(uint64_t{0u}, value); } /** Is the RNG seed required */ - bool is_seed_required() const { return !m_rng_seed; } - uint32_t rng_seed() const { return m_rng_seed.value(); } + bool is_seed_required() const { return not m_initialized; } + uint32_t rng_seed() const { return m_rng_seed; } private: - /** RNG counter. */ - Utils::Counter m_rng_counter; - /** RNG seed */ - boost::optional m_rng_seed; + /** @brief RNG counter. */ + Utils::Counter m_rng_counter{uint64_t{0u}, uint64_t{0u}}; + /** @brief RNG seed. */ + uint32_t m_rng_seed{0u}; + bool m_initialized{false}; }; -/** %Thermostat for Langevin dynamics. */ +/** Thermostat for Langevin dynamics. */ struct LangevinThermostat : public BaseThermostat { private: using GammaType = Thermostat::GammaType; @@ -154,13 +137,12 @@ struct LangevinThermostat : public BaseThermostat { * Needs to be called every time the parameters are changed. */ void recalc_prefactors(double kT, double time_step) { + // get_system().get_time_step() pref_friction = -gamma; pref_noise = sigma(kT, time_step, gamma); - // If gamma_rotation is not set explicitly, use the translational one. - if (gamma_rotation < GammaType{}) { - gamma_rotation = gamma; - } +#ifdef ROTATION pref_noise_rotation = sigma(kT, time_step, gamma_rotation); +#endif // ROTATION } /** Calculate the noise prefactor. * Evaluates the quantity @f$ \sqrt{2 k_B T \gamma / dt} / \sigma_\eta @f$ @@ -175,28 +157,32 @@ struct LangevinThermostat : public BaseThermostat { /** @name Parameters */ /**@{*/ /** Translational friction coefficient @f$ \gamma_{\text{trans}} @f$. */ - GammaType gamma = gamma_sentinel; + GammaType gamma = Thermostat::gamma_sentinel; +#ifdef ROTATION /** Rotational friction coefficient @f$ \gamma_{\text{rot}} @f$. */ - GammaType gamma_rotation = gamma_sentinel; + GammaType gamma_rotation = Thermostat::gamma_sentinel; +#endif // ROTATION /**@}*/ /** @name Prefactors */ /**@{*/ /** Prefactor for the friction. * Stores @f$ \gamma_{\text{trans}} @f$. */ - GammaType pref_friction; + GammaType pref_friction = Thermostat::gamma_sentinel; /** Prefactor for the translational velocity noise. * Stores @f$ \sqrt{2 k_B T \gamma_{\text{trans}} / dt} / \sigma_\eta @f$. */ - GammaType pref_noise; + GammaType pref_noise = Thermostat::gamma_sentinel; +#ifdef ROTATION /** Prefactor for the angular velocity noise. * Stores @f$ \sqrt{2 k_B T \gamma_{\text{rot}} / dt} / \sigma_\eta @f$. */ - GammaType pref_noise_rotation; + GammaType pref_noise_rotation = Thermostat::gamma_sentinel; +#endif // ROTATION /**@}*/ }; -/** %Thermostat for Brownian dynamics. +/** Thermostat for Brownian dynamics. * Default particle mass is assumed to be unitary in these global parameters. */ struct BrownianThermostat : public BaseThermostat { @@ -223,10 +209,6 @@ struct BrownianThermostat : public BaseThermostat { * They correspond to the friction tensor Z from the eq. (14.31) of * @cite schlick10a. */ - // If gamma_rotation is not set explicitly, use the translational one. - if (gamma_rotation < GammaType{}) { - gamma_rotation = gamma; - } sigma_vel_rotation = sigma(kT); sigma_pos_rotation = sigma(kT, gamma_rotation); #endif // ROTATION @@ -252,9 +234,9 @@ struct BrownianThermostat : public BaseThermostat { /** @name Parameters */ /**@{*/ /** Translational friction coefficient @f$ \gamma_{\text{trans}} @f$. */ - GammaType gamma = gamma_sentinel; + GammaType gamma = Thermostat::gamma_sentinel; /** Rotational friction coefficient @f$ \gamma_{\text{rot}} @f$. */ - GammaType gamma_rotation = gamma_sentinel; + GammaType gamma_rotation = Thermostat::gamma_sentinel; /**@}*/ /** @name Prefactors */ /**@{*/ @@ -263,26 +245,30 @@ struct BrownianThermostat : public BaseThermostat { * @f$ D_{\text{trans}} = k_B T/\gamma_{\text{trans}} @f$ * the translational diffusion coefficient. */ - GammaType sigma_pos = gamma_sentinel; + GammaType sigma_pos = Thermostat::gamma_sentinel; +#ifdef ROTATION /** Rotational noise standard deviation. * Stores @f$ \sqrt{2D_{\text{rot}}} @f$ with * @f$ D_{\text{rot}} = k_B T/\gamma_{\text{rot}} @f$ * the rotational diffusion coefficient. */ - GammaType sigma_pos_rotation = gamma_sentinel; + GammaType sigma_pos_rotation = Thermostat::gamma_sentinel; +#endif // ROTATION /** Translational velocity noise standard deviation. * Stores @f$ \sqrt{k_B T} @f$. */ - double sigma_vel = 0; + double sigma_vel = 0.; +#ifdef ROTATION /** Angular velocity noise standard deviation. * Stores @f$ \sqrt{k_B T} @f$. */ - double sigma_vel_rotation = 0; + double sigma_vel_rotation = 0.; +#endif // ROTATION /**@}*/ }; #ifdef NPT -/** %Thermostat for isotropic NPT dynamics. */ +/** Thermostat for isotropic NPT dynamics. */ struct IsotropicNptThermostat : public BaseThermostat { private: using GammaType = Thermostat::GammaType; @@ -313,107 +299,92 @@ struct IsotropicNptThermostat : public BaseThermostat { /** @name Parameters */ /**@{*/ /** Friction coefficient of the particles @f$ \gamma^0 @f$ */ - double gamma0; + double gamma0 = 0.; /** Friction coefficient for the box @f$ \gamma^V @f$ */ - double gammav; + double gammav = 0.; /**@}*/ /** @name Prefactors */ /**@{*/ - /** %Particle velocity rescaling at half the time step. + /** Particle velocity rescaling at half the time step. * Stores @f$ \gamma^{0}\cdot\frac{dt}{2} @f$. */ - double pref_rescale_0; - /** %Particle velocity rescaling noise standard deviation. + double pref_rescale_0 = 0.; + /** Particle velocity rescaling noise standard deviation. * Stores @f$ \sqrt{k_B T \gamma^{0} dt} / \sigma_\eta @f$. */ - double pref_noise_0; + double pref_noise_0 = 0.; /** Volume rescaling at half the time step. * Stores @f$ \frac{\gamma^{V}}{Q}\cdot\frac{dt}{2} @f$. */ - double pref_rescale_V; + double pref_rescale_V = 0.; /** Volume rescaling noise standard deviation. * Stores @f$ \sqrt{k_B T \gamma^{V} dt} / \sigma_\eta @f$. */ - double pref_noise_V; + double pref_noise_V = 0.; /**@}*/ }; #endif -/** %Thermostat for thermalized bonds. */ -struct ThermalizedBondThermostat : public BaseThermostat {}; +/** Thermostat for lattice-Boltzmann particle coupling. */ +struct LBThermostat : public BaseThermostat { + /** @name Parameters */ + /**@{*/ + /** Friction coefficient. */ + double gamma = -1.; + /** Internal flag to disable particle coupling during force recalculation. */ + bool couple_to_md = false; + /**@}*/ +}; + +/** Thermostat for thermalized bonds. */ +struct ThermalizedBondThermostat : public BaseThermostat { + void recalc_prefactors(double time_step); +}; #ifdef DPD -/** %Thermostat for dissipative particle dynamics. */ +/** Thermostat for dissipative particle dynamics. */ struct DPDThermostat : public BaseThermostat {}; #endif #ifdef STOKESIAN_DYNAMICS -/** %Thermostat for Stokesian dynamics. */ +/** Thermostat for Stokesian dynamics. */ struct StokesianThermostat : public BaseThermostat { StokesianThermostat() { rng_initialize(0); } }; #endif -/************************************************ - * functions - ************************************************/ - -/** - * @brief Register MPI callbacks of thermostat objects - * - * @param thermostat The thermostat object name - */ -#define NEW_THERMOSTAT(thermostat) \ - void mpi_##thermostat##_set_rng_seed(uint32_t seed); \ - void thermostat##_set_rng_seed(uint32_t seed); \ - void thermostat##_set_rng_counter(uint64_t seed); - -NEW_THERMOSTAT(langevin) -NEW_THERMOSTAT(brownian) -#ifdef NPT -NEW_THERMOSTAT(npt_iso) -#endif -NEW_THERMOSTAT(thermalized_bond) -#ifdef DPD -NEW_THERMOSTAT(dpd) -#endif -#ifdef STOKESIAN_DYNAMICS -NEW_THERMOSTAT(stokesian) -#endif - -/* Exported thermostat globals */ -extern LangevinThermostat langevin; -extern BrownianThermostat brownian; +namespace Thermostat { +class Thermostat : public System::Leaf { +public: + /** @brief Thermal energy of the simulated heat bath. */ + double kT = -1.; + /** @brief Bitmask of currently active thermostats. */ + int thermo_switch = THERMO_OFF; + std::shared_ptr langevin; + std::shared_ptr brownian; #ifdef NPT -extern IsotropicNptThermostat npt_iso; + std::shared_ptr npt_iso; #endif -extern ThermalizedBondThermostat thermalized_bond; + std::shared_ptr lb; #ifdef DPD -extern DPDThermostat dpd; + std::shared_ptr dpd; #endif #ifdef STOKESIAN_DYNAMICS -extern StokesianThermostat stokesian; + std::shared_ptr stokesian; #endif + std::shared_ptr thermalized_bond; -/** Initialize constants of the thermostat at the start of integration */ -void thermo_init(double time_step); - -/** Increment RNG counters */ -void philox_counter_increment(); - -void mpi_set_brownian_gamma(Thermostat::GammaType const &gamma); -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_temperature(double temperature); -void mpi_set_temperature_local(double temperature); + /** Increment RNG counters */ + void philox_counter_increment(); -void mpi_set_thermo_switch(int thermo_switch); -void mpi_set_thermo_switch_local(int thermo_switch); + /** Initialize constants of all thermostats. */ + void recalc_prefactors(double time_step); -#ifdef NPT -void mpi_set_nptiso_gammas(double gamma0, double gammav); -void mpi_set_nptiso_gammas_local(double gamma0, double gammav); -#endif + void lb_coupling_activate() { + if (lb) { + lb->couple_to_md = true; + } + } + void lb_coupling_deactivate(); +}; +} // namespace Thermostat diff --git a/src/core/thermostats/brownian_inline.hpp b/src/core/thermostats/brownian_inline.hpp index f010b03eeaa..4d70e5977f8 100644 --- a/src/core/thermostats/brownian_inline.hpp +++ b/src/core/thermostats/brownian_inline.hpp @@ -35,7 +35,7 @@ /** Determine position: viscous drag driven by conservative forces. * From eq. (14.39) in @cite schlick10a. * @param[in] brownian_gamma Brownian translational gamma - * @param[in] p %Particle + * @param[in] p Particle * @param[in] dt Time step */ inline Utils::Vector3d bd_drag(Thermostat::GammaType const &brownian_gamma, @@ -89,7 +89,7 @@ inline Utils::Vector3d bd_drag(Thermostat::GammaType const &brownian_gamma, /** Set the terminal velocity driven by the conservative forces drag. * From eq. (14.34) in @cite schlick10a. * @param[in] brownian_gamma Brownian translational gamma - * @param[in] p %Particle + * @param[in] p Particle */ inline Utils::Vector3d bd_drag_vel(Thermostat::GammaType const &brownian_gamma, Particle const &p) { @@ -143,9 +143,9 @@ inline Utils::Vector3d bd_drag_vel(Thermostat::GammaType const &brownian_gamma, /** Determine the positions: random walk part. * From eq. (14.37) in @cite schlick10a. * @param[in] brownian Parameters - * @param[in] p %Particle + * @param[in] p Particle * @param[in] dt Time step - * @param[in] kT Temperature + * @param[in] kT Thermal energy */ inline Utils::Vector3d bd_random_walk(BrownianThermostat const &brownian, Particle const &p, double dt, double kT) { @@ -210,7 +210,7 @@ inline Utils::Vector3d bd_random_walk(BrownianThermostat const &brownian, /** Determine the velocities: random walk part. * From eq. (10.2.16) in @cite pottier10a. * @param[in] brownian Parameters - * @param[in] p %Particle + * @param[in] p Particle */ inline Utils::Vector3d bd_random_walk_vel(BrownianThermostat const &brownian, Particle const &p) { @@ -237,7 +237,7 @@ inline Utils::Vector3d bd_random_walk_vel(BrownianThermostat const &brownian, /** Determine quaternions: viscous drag driven by conservative torques. * An analogy of eq. (14.39) in @cite schlick10a. * @param[in] brownian_gamma_rotation Brownian rotational gamma - * @param[in] p %Particle + * @param[in] p Particle * @param[in] dt Time step */ inline Utils::Quaternion @@ -277,7 +277,7 @@ bd_drag_rot(Thermostat::GammaType const &brownian_gamma_rotation, Particle &p, /** Set the terminal angular velocity driven by the conservative torques drag. * An analogy of the 1st term of eq. (14.34) in @cite schlick10a. * @param[in] brownian_gamma_rotation Brownian rotational gamma - * @param[in] p %Particle + * @param[in] p Particle */ inline Utils::Vector3d bd_drag_vel_rot(Thermostat::GammaType const &brownian_gamma_rotation, @@ -309,9 +309,9 @@ bd_drag_vel_rot(Thermostat::GammaType const &brownian_gamma_rotation, /** Determine the quaternions: random walk part. * An analogy of eq. (14.37) in @cite schlick10a. * @param[in] brownian Parameters - * @param[in] p %Particle + * @param[in] p Particle * @param[in] dt Time step - * @param[in] kT Temperature + * @param[in] kT Thermal energy */ inline Utils::Quaternion bd_random_walk_rot(BrownianThermostat const &brownian, Particle const &p, @@ -358,7 +358,7 @@ bd_random_walk_rot(BrownianThermostat const &brownian, Particle const &p, /** Determine the angular velocities: random walk part. * An analogy of eq. (10.2.16) in @cite pottier10a. * @param[in] brownian Parameters - * @param[in] p %Particle + * @param[in] p Particle */ inline Utils::Vector3d bd_random_walk_vel_rot(BrownianThermostat const &brownian, Particle const &p) { diff --git a/src/core/thermostats/langevin_inline.hpp b/src/core/thermostats/langevin_inline.hpp index 602758c4a11..1f40e90889b 100644 --- a/src/core/thermostats/langevin_inline.hpp +++ b/src/core/thermostats/langevin_inline.hpp @@ -32,17 +32,15 @@ #include /** Langevin thermostat for particle translational velocities. - * Collects the particle velocity (different for ENGINE, PARTICLE_ANISOTROPY). - * Collects the langevin parameters kT, gamma (different for - * THERMOSTAT_PER_PARTICLE). Applies the noise and friction term. * @param[in] langevin Parameters - * @param[in] p %Particle + * @param[in] p Particle * @param[in] time_step Time step - * @param[in] kT Temperature + * @param[in] kT Thermal energy */ inline Utils::Vector3d friction_thermo_langevin(LangevinThermostat const &langevin, Particle const &p, double time_step, double kT) { + using namespace Thermostat; // Determine prefactors for the friction and the noise term #ifdef THERMOSTAT_PER_PARTICLE auto const gamma = handle_particle_gamma(p.gamma(), langevin.gamma); @@ -62,18 +60,16 @@ friction_thermo_langevin(LangevinThermostat const &langevin, Particle const &p, #ifdef ROTATION /** Langevin thermostat for particle angular velocities. - * Collects the particle velocity (different for PARTICLE_ANISOTROPY). - * Collects the langevin parameters kT, gamma_rot (different for - * THERMOSTAT_PER_PARTICLE). Applies the noise and friction term. * @param[in] langevin Parameters - * @param[in] p %Particle + * @param[in] p Particle * @param[in] time_step Time step - * @param[in] kT Temperature + * @param[in] kT Thermal energy */ inline Utils::Vector3d friction_thermo_langevin_rotation(LangevinThermostat const &langevin, Particle const &p, double time_step, double kT) { + using namespace Thermostat; #ifdef THERMOSTAT_PER_PARTICLE auto const gamma = diff --git a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp index ee604d1f2b3..257a2d7db71 100644 --- a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp +++ b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp @@ -445,6 +445,18 @@ BOOST_FIXTURE_TEST_CASE(espresso_system_stand_alone, ParticleFactory) { } } + // check propagator exceptions + { + auto &propagation = *espresso::system->propagation; + auto const old_integ_switch = propagation.integ_switch; + auto const old_default_propagation = propagation.default_propagation; + propagation.integ_switch = -1; + BOOST_CHECK_THROW(propagation.update_default_propagation(0), + std::runtime_error); + BOOST_CHECK_EQUAL(propagation.default_propagation, old_default_propagation); + propagation.integ_switch = old_integ_switch; + } + // check bond exceptions { BOOST_CHECK_THROW(throw BondResolutionError(), std::exception); diff --git a/src/core/unit_tests/Verlet_list_test.cpp b/src/core/unit_tests/Verlet_list_test.cpp index 04d2ca1f4d0..54a7fed4db4 100644 --- a/src/core/unit_tests/Verlet_list_test.cpp +++ b/src/core/unit_tests/Verlet_list_test.cpp @@ -86,7 +86,7 @@ struct IntegratorHelper : public ParticleFactory { #ifdef EXTERNAL_FORCES struct : public IntegratorHelper { void set_integrator() const override { - mpi_set_thermo_switch_local(THERMO_OFF); + espresso::system->thermostat->thermo_switch = THERMO_OFF; register_integrator(SteepestDescentParameters(0., 0.01, 100.)); espresso::system->propagation->set_integ_switch( INTEG_METHOD_STEEPEST_DESCENT); @@ -101,7 +101,7 @@ struct : public IntegratorHelper { struct : public IntegratorHelper { void set_integrator() const override { - mpi_set_thermo_switch_local(THERMO_OFF); + espresso::system->thermostat->thermo_switch = THERMO_OFF; espresso::system->propagation->set_integ_switch(INTEG_METHOD_NVT); } void set_particle_properties(int pid) const override { @@ -113,12 +113,15 @@ struct : public IntegratorHelper { #ifdef NPT struct : public IntegratorHelper { void set_integrator() const override { + auto &npt_iso = espresso::system->thermostat->npt_iso; ::nptiso = NptIsoParameters(1., 1e9, {true, true, true}, true); 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); - mpi_set_nptiso_gammas_local(0., 0.); // disable box volume change + espresso::system->thermostat->thermo_switch = THERMO_NPT_ISO; + espresso::system->thermostat->kT = 1.; + npt_iso = std::make_shared(); + npt_iso->rng_initialize(0u); + npt_iso->gammav = 0.; // disable box volume change + npt_iso->gamma0 = 0.; } void set_particle_properties(int pid) const override { set_particle_v(pid, {20., 0., 0.}); diff --git a/src/core/unit_tests/ek_interface_test.cpp b/src/core/unit_tests/ek_interface_test.cpp index fcb2880c9ea..b2f5a9659f7 100644 --- a/src/core/unit_tests/ek_interface_test.cpp +++ b/src/core/unit_tests/ek_interface_test.cpp @@ -181,14 +181,22 @@ BOOST_AUTO_TEST_CASE(ek_interface_walberla) { BOOST_REQUIRE(not espresso::ek_container->contains(ek_species)); ek.propagate(); // no-op BOOST_REQUIRE_EQUAL(get_n_runtime_errors(), 0); - } - { - // EK prevents changing most of the system state - BOOST_CHECK_THROW(ek.on_boxl_change(), std::runtime_error); - BOOST_CHECK_THROW(ek.on_timestep_change(), std::runtime_error); - BOOST_CHECK_THROW(ek.on_temperature_change(), std::runtime_error); - BOOST_CHECK_THROW(ek.on_node_grid_change(), std::runtime_error); + { + // EK prevents changing most of the system state + ek.veto_kT(params.kT + 1.); + espresso::ek_container->add(ek_species); + BOOST_CHECK_THROW(ek.veto_kT(params.kT + 1.), std::runtime_error); + BOOST_CHECK_THROW(ek.on_boxl_change(), std::runtime_error); + BOOST_CHECK_THROW(ek.veto_time_step(ek.get_tau() * 2.), + std::invalid_argument); + BOOST_CHECK_THROW(ek.veto_time_step(ek.get_tau() / 2.5), + std::invalid_argument); + BOOST_CHECK_THROW(ek.on_node_grid_change(), std::runtime_error); + ek.on_timestep_change(); + ek.on_temperature_change(); + espresso::ek_container->remove(ek_species); + } } } #endif // WALBERLA @@ -208,6 +216,7 @@ BOOST_AUTO_TEST_CASE(ek_interface_none) { BOOST_CHECK_THROW(ek.get_tau(), NoEKActive); BOOST_CHECK_THROW(ek.sanity_checks(), NoEKActive); BOOST_CHECK_THROW(ek.veto_time_step(0.), NoEKActive); + BOOST_CHECK_THROW(ek.veto_kT(0.), NoEKActive); BOOST_CHECK_THROW(ek.on_cell_structure_change(), NoEKActive); BOOST_CHECK_THROW(ek.on_boxl_change(), NoEKActive); BOOST_CHECK_THROW(ek.on_node_grid_change(), NoEKActive); diff --git a/src/core/unit_tests/lb_particle_coupling_test.cpp b/src/core/unit_tests/lb_particle_coupling_test.cpp index 50e512ceb55..542587dc89f 100644 --- a/src/core/unit_tests/lb_particle_coupling_test.cpp +++ b/src/core/unit_tests/lb_particle_coupling_test.cpp @@ -33,12 +33,13 @@ namespace utf = boost::unit_test; #include "ParticleFactory.hpp" #include "particle_management.hpp" +#include "BoxGeometry.hpp" +#include "LocalBox.hpp" #include "Particle.hpp" #include "cell_system/CellStructure.hpp" #include "cell_system/CellStructureType.hpp" #include "communication.hpp" #include "errorhandling.hpp" -#include "global_ghost_flags.hpp" #include "lb/LBNone.hpp" #include "lb/LBWalberla.hpp" #include "lb/particle_coupling.hpp" @@ -152,41 +153,38 @@ struct CleanupActorLB : public ParticleFactory { BOOST_FIXTURE_TEST_SUITE(suite, CleanupActorLB) -static void lb_lbcoupling_broadcast() { - boost::mpi::communicator world; - boost::mpi::broadcast(world, lb_particle_coupling, 0); -} - -BOOST_AUTO_TEST_CASE(activate) { - lb_lbcoupling_deactivate(); - lb_lbcoupling_broadcast(); - lb_lbcoupling_activate(); - lb_lbcoupling_broadcast(); - BOOST_CHECK(lb_particle_coupling.couple_to_md); +BOOST_AUTO_TEST_CASE(lb_reactivate) { + espresso::system->thermostat->lb_coupling_deactivate(); + espresso::system->thermostat->lb_coupling_activate(); + BOOST_CHECK(espresso::system->thermostat->lb->couple_to_md); } -BOOST_AUTO_TEST_CASE(de_activate) { - lb_lbcoupling_activate(); - lb_lbcoupling_broadcast(); - lb_lbcoupling_deactivate(); - lb_lbcoupling_broadcast(); - BOOST_CHECK(not lb_particle_coupling.couple_to_md); +BOOST_AUTO_TEST_CASE(lb_deactivate) { + espresso::system->thermostat->lb_coupling_activate(); + espresso::system->thermostat->lb_coupling_deactivate(); + BOOST_CHECK(not espresso::system->thermostat->lb->couple_to_md); } BOOST_AUTO_TEST_CASE(rng_initial_state) { - BOOST_CHECK(lb_lbcoupling_is_seed_required()); - BOOST_CHECK(!lb_particle_coupling.rng_counter_coupling); + BOOST_CHECK(espresso::system->thermostat->lb->is_seed_required()); + BOOST_CHECK(espresso::system->thermostat->lb->rng_counter() == 0ul); } BOOST_AUTO_TEST_CASE(rng) { - lb_lbcoupling_set_rng_state(17); - lb_particle_coupling.gamma = 0.2; auto &lb = espresso::system->lb; - - 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()); + auto &thermostat = *espresso::system->thermostat->lb; + auto const &box_geo = *espresso::system->box_geo; + auto const &local_box = *espresso::system->local_geo; + auto const tau = params.time_step; + thermostat.rng_initialize(17u); + thermostat.set_rng_counter(11ul); + thermostat.gamma = 0.2; + espresso::set_lb_kT(1.); + + LB::ParticleCoupling coupling{thermostat, lb, box_geo, local_box, tau}; + BOOST_CHECK_EQUAL(thermostat.rng_seed(), 17u); + BOOST_CHECK_EQUAL(thermostat.rng_counter(), 11ul); + BOOST_CHECK(not thermostat.is_seed_required()); Particle test_partcl_1{}; test_partcl_1.id() = 1; Particle test_partcl_2{}; @@ -200,16 +198,18 @@ BOOST_AUTO_TEST_CASE(rng) { // Propagation queries kT from LB, so LB needs to be initialized espresso::set_lb_kT(1E-4); - lb_lbcoupling_propagate(); + thermostat.rng_increment(); - BOOST_REQUIRE(lb_particle_coupling.rng_counter_coupling); - BOOST_CHECK_EQUAL(lb_lbcoupling_get_rng_state(), 18); + BOOST_CHECK_EQUAL(thermostat.rng_seed(), 17u); + BOOST_CHECK_EQUAL(thermostat.rng_counter(), 12ul); auto const step2_random1 = coupling.get_noise_term(test_partcl_1); auto const step2_random2 = coupling.get_noise_term(test_partcl_2); BOOST_CHECK(step1_random1 != step2_random1); BOOST_CHECK(step1_random1 != step2_random2); - LB::ParticleCoupling coupling_unthermalized{lb, params.time_step, 0.}; + espresso::set_lb_kT(0.); + LB::ParticleCoupling coupling_unthermalized{thermostat, lb, box_geo, + local_box, tau}; auto const step3_norandom = coupling_unthermalized.get_noise_term(test_partcl_2); BOOST_CHECK((step3_norandom == Utils::Vector3d{0., 0., 0.})); @@ -218,7 +218,11 @@ BOOST_AUTO_TEST_CASE(rng) { BOOST_AUTO_TEST_CASE(drift_vel_offset) { Particle p{}; auto &lb = espresso::system->lb; - LB::ParticleCoupling coupling{lb, params.time_step}; + auto &thermostat = *espresso::system->thermostat->lb; + auto const &box_geo = *espresso::system->box_geo; + auto const &local_box = *espresso::system->local_geo; + LB::ParticleCoupling coupling{thermostat, lb, box_geo, local_box, + params.time_step}; BOOST_CHECK_EQUAL(coupling.lb_drift_velocity_offset(p).norm(), 0); Utils::Vector3d expected{}; #ifdef LB_ELECTROHYDRODYNAMICS @@ -232,15 +236,16 @@ BOOST_AUTO_TEST_CASE(drift_vel_offset) { BOOST_DATA_TEST_CASE(drag_force, bdata::make(kTs), kT) { espresso::set_lb_kT(kT); auto &lb = espresso::system->lb; + auto &thermostat = *espresso::system->thermostat->lb; Particle p{}; p.v() = {-2.5, 1.5, 2.}; p.pos() = espresso::lb_fluid->get_lattice().get_local_domain().first; - lb_lbcoupling_set_gamma(0.2); + thermostat.gamma = 0.2; Utils::Vector3d drift_offset{-1., 1., 1.}; // Drag force in quiescent fluid { - auto const observed = lb_drag_force(lb, p, p.pos(), drift_offset); + auto const observed = lb_drag_force(lb, 0.2, p, p.pos(), drift_offset); const Utils::Vector3d expected{0.3, -0.1, -.2}; BOOST_CHECK_SMALL((observed - expected).norm(), eps); } @@ -250,6 +255,9 @@ BOOST_DATA_TEST_CASE(drag_force, bdata::make(kTs), kT) { BOOST_DATA_TEST_CASE(swimmer_force, bdata::make(kTs), kT) { espresso::set_lb_kT(kT); auto &lb = espresso::system->lb; + auto &thermostat = *espresso::system->thermostat->lb; + auto const &box_geo = *espresso::system->box_geo; + auto const &local_box = *espresso::system->local_geo; auto const first_lb_node = espresso::lb_fluid->get_lattice().get_local_domain().first; Particle p{}; @@ -260,8 +268,9 @@ BOOST_DATA_TEST_CASE(swimmer_force, bdata::make(kTs), kT) { // swimmer coupling { - if (in_local_halo(p.pos())) { - LB::ParticleCoupling coupling{lb, params.time_step}; + if (in_local_halo(local_box, p.pos(), params.agrid)) { + LB::ParticleCoupling coupling{thermostat, lb, box_geo, local_box, + params.time_step}; coupling.kernel(p); auto const interpolated = LB::get_force_to_be_applied(p.pos()); auto const expected = @@ -282,7 +291,7 @@ BOOST_DATA_TEST_CASE(swimmer_force, bdata::make(kTs), kT) { }; if ((pos - p.pos()).norm() < 1e-6) continue; - if (in_local_halo(pos)) { + if (in_local_halo(local_box, pos, params.agrid)) { auto const interpolated = LB::get_force_to_be_applied(pos); BOOST_CHECK_SMALL(interpolated.norm(), eps); } @@ -293,7 +302,7 @@ BOOST_DATA_TEST_CASE(swimmer_force, bdata::make(kTs), kT) { // remove force of the particle from the fluid { - if (in_local_halo(p.pos())) { + if (in_local_halo(local_box, p.pos(), params.agrid)) { add_md_force(lb, p.pos(), -Utils::Vector3d{0., 0., p.swimming().f_swim}, params.time_step); auto const reset = LB::get_force_to_be_applied(p.pos()); @@ -304,25 +313,28 @@ BOOST_DATA_TEST_CASE(swimmer_force, bdata::make(kTs), kT) { #endif // ENGINE BOOST_DATA_TEST_CASE(particle_coupling, bdata::make(kTs), kT) { - espresso::set_lb_kT(kT); - lb_lbcoupling_set_rng_state(17); auto &lb = espresso::system->lb; + auto &thermostat = *espresso::system->thermostat->lb; + espresso::set_lb_kT(kT); + thermostat.rng_initialize(17u); + auto const &box_geo = *espresso::system->box_geo; + auto const &local_box = *espresso::system->local_geo; auto const first_lb_node = espresso::lb_fluid->get_lattice().get_local_domain().first; - auto const gamma = 0.2; - lb_lbcoupling_set_gamma(gamma); + thermostat.gamma = 0.2; Particle p{}; - LB::ParticleCoupling coupling{lb, params.time_step}; + LB::ParticleCoupling coupling{thermostat, lb, box_geo, local_box, + params.time_step}; auto expected = coupling.get_noise_term(p); #ifdef LB_ELECTROHYDRODYNAMICS p.mu_E() = Utils::Vector3d{-2., 1.5, 1.}; - expected += gamma * p.mu_E(); + expected += thermostat.gamma * p.mu_E(); #endif p.pos() = first_lb_node + Utils::Vector3d::broadcast(0.5); // coupling { - if (in_local_halo(p.pos())) { + if (in_local_halo(local_box, p.pos(), params.agrid)) { coupling.kernel(p); BOOST_CHECK_SMALL((p.force() - expected).norm(), eps); @@ -334,7 +346,7 @@ BOOST_DATA_TEST_CASE(particle_coupling, bdata::make(kTs), kT) { // remove force of the particle from the fluid { - if (in_local_halo(p.pos())) { + if (in_local_halo(local_box, p.pos(), params.agrid)) { add_md_force(lb, p.pos(), -expected, params.time_step); } } @@ -342,14 +354,16 @@ BOOST_DATA_TEST_CASE(particle_coupling, bdata::make(kTs), kT) { BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, bdata::make(kTs), kT) { + auto &lb = espresso::system->lb; + auto &thermostat = *espresso::system->thermostat->lb; auto const comm = boost::mpi::communicator(); auto const rank = comm.rank(); espresso::set_lb_kT(kT); - lb_lbcoupling_set_rng_state(17); + thermostat.rng_initialize(17u); auto &system = *espresso::system; auto &cell_structure = *system.cell_structure; - auto &lb = system.lb; auto const &box_geo = *system.box_geo; + auto const &local_box = *system.local_geo; auto const first_lb_node = espresso::lb_fluid->get_lattice().get_local_domain().first; auto const gamma = 0.2; @@ -371,7 +385,8 @@ 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, params.time_step}; + LB::ParticleCoupling coupling{thermostat, lb, box_geo, local_box, + params.time_step}; auto const p_opt = copy_particle_to_head_node(comm, system, pid); auto expected = Utils::Vector3d{}; if (rank == 0) { @@ -384,12 +399,13 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, boost::mpi::broadcast(comm, expected, 0); auto const p_pos = first_lb_node + Utils::Vector3d::broadcast(0.5); set_particle_pos(pid, p_pos); - lb_lbcoupling_set_gamma(gamma); + thermostat.gamma = gamma; for (bool with_ghosts : {false, true}) { { if (with_ghosts) { - cell_structure.update_ghosts_and_resort_particle(global_ghost_flags()); + cell_structure.update_ghosts_and_resort_particle( + system.get_global_ghost_flags()); } if (rank == 0) { auto const particles = cell_structure.local_particles(); @@ -415,7 +431,8 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, auto const cutoff = 8 / world.size(); auto const agrid = params.agrid; { - auto const shifts = positions_in_halo({0., 0., 0.}, box_geo, agrid); + auto const shifts = + positions_in_halo({0., 0., 0.}, box_geo, local_box, agrid); BOOST_REQUIRE_EQUAL(shifts.size(), cutoff); for (std::size_t i = 0; i < shifts.size(); ++i) { BOOST_REQUIRE_EQUAL(shifts[i], reference_shifts[i]); @@ -423,14 +440,16 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, } { auto const reference_shift = Utils::Vector3d{1., 1., 1.}; - auto const shifts = positions_in_halo({1., 1., 1.}, box_geo, agrid); + auto const shifts = + positions_in_halo({1., 1., 1.}, box_geo, local_box, agrid); BOOST_REQUIRE_EQUAL(shifts.size(), 1); BOOST_REQUIRE_EQUAL(shifts[0], reference_shift); } { auto const reference_origin = Utils::Vector3d{1., 2., 0.}; auto const reference_shift = Utils::Vector3d{1., 2., 8.}; - auto const shifts = positions_in_halo({1., 2., 0.}, box_geo, agrid); + auto const shifts = + positions_in_halo({1., 2., 0.}, box_geo, local_box, agrid); BOOST_REQUIRE_EQUAL(shifts.size(), 2); BOOST_REQUIRE_EQUAL(shifts[0], reference_origin); BOOST_REQUIRE_EQUAL(shifts[1], reference_shift); @@ -439,11 +458,8 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, // check without LB coupling { - lb_lbcoupling_deactivate(); - lb_lbcoupling_broadcast(); - auto const particles = cell_structure.local_particles(); - auto const ghost_particles = cell_structure.ghost_particles(); - LB::couple_particles(particles, ghost_particles, params.time_step); + system.thermostat->lb_coupling_deactivate(); + system.lb_couple_particles(params.time_step); auto const p_opt = copy_particle_to_head_node(comm, system, pid); if (rank == 0) { auto const &p = *p_opt; @@ -453,10 +469,7 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, // check with LB coupling { - lb_lbcoupling_activate(); - lb_lbcoupling_broadcast(); - auto const particles = cell_structure.local_particles(); - auto const ghost_particles = cell_structure.ghost_particles(); + system.thermostat->lb_coupling_activate(); Utils::Vector3d lb_before{}; { auto const p_opt = copy_particle_to_head_node(comm, system, pid); @@ -467,7 +480,7 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, } } // couple particle to LB - LB::couple_particles(particles, ghost_particles, params.time_step); + system.lb_couple_particles(params.time_step); { auto const p_opt = copy_particle_to_head_node(comm, system, pid); if (rank == 0) { @@ -498,13 +511,6 @@ BOOST_DATA_TEST_CASE_F(CleanupActorLB, coupling_particle_lattice_ia, for (auto const &error_message : error_messages) { BOOST_CHECK_EQUAL(error_message.what(), error_message_ref); } - lb_particle_coupling.rng_counter_coupling = {}; - if (kT == 0.) { - BOOST_CHECK_EQUAL(coupling.get_noise_term(Particle{}).norm(), 0.); - } else { - BOOST_CHECK_THROW(coupling.get_noise_term(Particle{}), - std::runtime_error); - } } } @@ -514,9 +520,13 @@ BOOST_AUTO_TEST_CASE(runtime_exceptions) { // LB prevents changing most of the system state { BOOST_CHECK_THROW(lb.on_boxl_change(), std::runtime_error); - BOOST_CHECK_THROW(lb.on_timestep_change(), std::runtime_error); - BOOST_CHECK_THROW(lb.on_temperature_change(), std::runtime_error); + BOOST_CHECK_THROW(lb.veto_time_step(lb.get_tau() * 2.), + std::invalid_argument); + BOOST_CHECK_THROW(lb.veto_time_step(lb.get_tau() / 2.5), + std::invalid_argument); + BOOST_CHECK_THROW(lb.veto_kT(lb.get_kT() + 7.), std::runtime_error); BOOST_CHECK_THROW(lb.on_node_grid_change(), std::runtime_error); + lb.on_timestep_change(); } // check access out of the LB local domain { @@ -572,8 +582,6 @@ BOOST_AUTO_TEST_CASE(lb_exceptions) { BOOST_CHECK_THROW(LB::get_force_to_be_applied({-10., -10., -10.}), std::runtime_error); // coupling, interpolation, boundaries - BOOST_CHECK_THROW(lb_lbcoupling_get_rng_state(), std::runtime_error); - BOOST_CHECK_THROW(lb_lbcoupling_set_rng_state(0ul), std::runtime_error); BOOST_CHECK_THROW(lb.get_momentum(), exception); } @@ -611,6 +619,7 @@ BOOST_AUTO_TEST_CASE(lb_exceptions) { BOOST_CHECK_THROW(lb.get_momentum(), NoLBActive); BOOST_CHECK_THROW(lb.sanity_checks(), NoLBActive); BOOST_CHECK_THROW(lb.veto_time_step(0.), NoLBActive); + BOOST_CHECK_THROW(lb.veto_kT(0.), NoLBActive); BOOST_CHECK_THROW(lb.lebc_sanity_checks(0u, 1u), NoLBActive); BOOST_CHECK_THROW(lb.propagate(), NoLBActive); BOOST_CHECK_THROW(lb.on_cell_structure_change(), NoLBActive); @@ -632,6 +641,7 @@ int main(int argc, char **argv) { espresso::system->set_time_step(params.time_step); espresso::system->set_cell_structure_topology(CellStructureType::REGULAR); espresso::system->cell_structure->set_verlet_skin(params.skin); + espresso::system->thermostat->lb = std::make_shared(); ::System::set_system(espresso::system); boost::mpi::communicator world; diff --git a/src/core/unit_tests/thermostats_test.cpp b/src/core/unit_tests/thermostats_test.cpp index 2e6d00930bd..564cbcfaca1 100644 --- a/src/core/unit_tests/thermostats_test.cpp +++ b/src/core/unit_tests/thermostats_test.cpp @@ -61,7 +61,9 @@ template T thermostat_factory(Args... args) { #else thermostat.gamma = 2.0; #endif +#ifdef ROTATION thermostat.gamma_rotation = 3.0 * thermostat.gamma; +#endif thermostat.rng_initialize(0); thermostat.recalc_prefactors(args...); return thermostat; @@ -314,8 +316,6 @@ BOOST_AUTO_TEST_CASE(test_langevin_randomness) { #ifdef NPT BOOST_AUTO_TEST_CASE(test_npt_iso_randomness) { - extern int thermo_switch; - thermo_switch |= THERMO_NPT_ISO; constexpr double time_step = 1.0; constexpr double kT = 2.0; constexpr std::size_t const sample_size = 10'000; diff --git a/src/core/virtual_sites/lb_tracers.cpp b/src/core/virtual_sites/lb_tracers.cpp index b688a5ab13c..9edea02a1d7 100644 --- a/src/core/virtual_sites/lb_tracers.cpp +++ b/src/core/virtual_sites/lb_tracers.cpp @@ -21,44 +21,37 @@ #ifdef VIRTUAL_SITES_INERTIALESS_TRACERS #include "BoxGeometry.hpp" -#include "PropagationMode.hpp" +#include "LocalBox.hpp" #include "cell_system/CellStructure.hpp" #include "errorhandling.hpp" #include "forces.hpp" +#include "lb/Solver.hpp" #include "lb/particle_coupling.hpp" -#include "system/System.hpp" -struct DeferredActiveLBChecks { - DeferredActiveLBChecks() { - auto const &lb = System::get_system().lb; - m_value = lb.is_solver_set(); - } - auto operator()() const { return m_value; } - bool m_value; -}; - -static bool lb_active_check(DeferredActiveLBChecks const &check) { - if (not check()) { +static bool lb_sanity_checks(LB::Solver const &lb) { + if (not lb.is_solver_set()) { runtimeErrorMsg() << "LB needs to be active for inertialess tracers."; - return false; + return true; } - return true; + return false; } 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 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.; + BoxGeometry const &box_geo, + LocalBox const &local_box, + LB::Solver &lb, double time_step) { + if (lb_sanity_checks(lb)) { + return; + } + auto const agrid = lb.get_agrid(); + auto const to_lb_units = 1. / agrid; // Distribute summed-up forces from physical particles to ghosts init_forces_ghosts(cell_structure.ghost_particles()); cell_structure.update_ghosts_and_resort_particle(Cells::DATA_PART_FORCE); // Keep track of ghost particles (ids) that have already been coupled - LB::CouplingBookkeeping bookkeeping{}; + LB::CouplingBookkeeping bookkeeping{cell_structure}; // Apply particle forces to the LB fluid at particle positions. // For physical particles, also set particle velocity = fluid velocity. for (auto const &particle_range : @@ -66,12 +59,10 @@ void lb_tracers_add_particle_force_to_fluid(CellStructure &cell_structure, for (auto const &p : particle_range) { if (!LB::is_tracer(p)) continue; - if (!lb_active_check(check_lb_solver_set)) { - return; - } if (bookkeeping.should_be_coupled(p)) { - for (auto pos : positions_in_halo(p.pos(), box_geo, agrid)) { - add_md_force(system.lb, pos * to_lb_units, p.force(), time_step); + for (auto const &pos : + positions_in_halo(p.pos(), box_geo, local_box, agrid)) { + add_md_force(lb, pos * to_lb_units, p.force(), time_step); } } } @@ -81,10 +72,11 @@ void lb_tracers_add_particle_force_to_fluid(CellStructure &cell_structure, init_forces_ghosts(cell_structure.ghost_particles()); } -void lb_tracers_propagate(CellStructure &cell_structure, double time_step) { - DeferredActiveLBChecks check_lb_solver_set{}; - auto &system = System::get_system(); - auto const &lb = system.lb; +void lb_tracers_propagate(CellStructure &cell_structure, LB::Solver const &lb, + double time_step) { + if (lb_sanity_checks(lb)) { + return; + } auto const verlet_skin = cell_structure.get_verlet_skin(); auto const verlet_skin_sq = verlet_skin * verlet_skin; @@ -92,9 +84,6 @@ void lb_tracers_propagate(CellStructure &cell_structure, double time_step) { for (auto &p : cell_structure.local_particles()) { if (!LB::is_tracer(p)) continue; - if (!lb_active_check(check_lb_solver_set)) { - return; - } p.v() = lb.get_coupling_interpolated_velocity(p.pos()); for (unsigned int i = 0u; i < 3u; i++) { if (!p.is_fixed_along(i)) { diff --git a/src/core/virtual_sites/lb_tracers.hpp b/src/core/virtual_sites/lb_tracers.hpp index 927d82de611..bbc9b4ce90d 100644 --- a/src/core/virtual_sites/lb_tracers.hpp +++ b/src/core/virtual_sites/lb_tracers.hpp @@ -24,9 +24,15 @@ #ifdef VIRTUAL_SITES_INERTIALESS_TRACERS #include "BoxGeometry.hpp" +#include "LocalBox.hpp" +#include "cell_system/CellStructure.hpp" +#include "lb/Solver.hpp" void lb_tracers_add_particle_force_to_fluid(CellStructure &cell_structure, - double time_step); -void lb_tracers_propagate(CellStructure &cell_structure, double time_step); + BoxGeometry const &box_geo, + LocalBox const &local_box, + LB::Solver &lb, double time_step); +void lb_tracers_propagate(CellStructure &cell_structure, LB::Solver const &lb, + double time_step); #endif // VIRTUAL_SITES_INERTIALESS_TRACERS diff --git a/src/core/virtual_sites/relative.cpp b/src/core/virtual_sites/relative.cpp index 2a8887f7cde..1363db4a5e9 100644 --- a/src/core/virtual_sites/relative.cpp +++ b/src/core/virtual_sites/relative.cpp @@ -74,6 +74,7 @@ static Utils::Vector3d velocity(Particle const &p_ref, Particle const &p_vs) { /** * @brief Get real particle tracked by a virtual site. * + * @param cell_structure Cell structure. * @param p Virtual site. * @return Pointer to real particle. */ diff --git a/src/python/espressomd/CMakeLists.txt b/src/python/espressomd/CMakeLists.txt index f130cd5ae12..4c188ce6a14 100644 --- a/src/python/espressomd/CMakeLists.txt +++ b/src/python/espressomd/CMakeLists.txt @@ -17,24 +17,6 @@ # along with this program. If not, see . # -add_custom_command( - OUTPUT gen_pxiconfig.cpp - COMMAND - ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/gen_pxiconfig.py - ${CMAKE_SOURCE_DIR}/src/config/features.def - ${CMAKE_CURRENT_BINARY_DIR}/gen_pxiconfig.cpp - DEPENDS ${CMAKE_SOURCE_DIR}/src/config/features.def) - -add_executable(gen_pxiconfig gen_pxiconfig.cpp) -target_link_libraries(gen_pxiconfig espresso::config) -set_target_properties(gen_pxiconfig PROPERTIES CXX_CLANG_TIDY - "${ESPRESSO_CXX_CLANG_TIDY}") - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/myconfig.pxi - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gen_pxiconfig > - ${CMAKE_CURRENT_BINARY_DIR}/myconfig.pxi DEPENDS gen_pxiconfig) - add_custom_target(espressomd) # Make the cython_SRC, cython_HEADER and cython_AUX a cached variable to be able @@ -93,8 +75,7 @@ foreach(cython_file ${cython_SRC}) ${CMAKE_CURRENT_SOURCE_DIR} -I ${CMAKE_CURRENT_BINARY_DIR} ${cython_file} -o ${outputpath} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/.. - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/myconfig.pxi ${cython_file} - ${cython_HEADER}) + DEPENDS ${cython_file} ${cython_HEADER}) set(target "espressomd_${basename}") add_library(${target} SHARED ${outputpath}) if(NOT "${relpath}" STREQUAL "") diff --git a/src/python/espressomd/electrostatic_extensions.py b/src/python/espressomd/electrostatic_extensions.py index 18f5f1cd2c8..5133611e908 100644 --- a/src/python/espressomd/electrostatic_extensions.py +++ b/src/python/espressomd/electrostatic_extensions.py @@ -17,10 +17,8 @@ # along with this program. If not, see . # -from . import utils from .script_interface import ScriptInterfaceHelper, script_interface_register from .code_features import has_features -import numpy as np class ElectrostaticExtensions(ScriptInterfaceHelper): @@ -33,7 +31,6 @@ def __init__(self, **kwargs): if 'sip' not in kwargs: params = self.default_params() params.update(kwargs) - self.validate_params(params) super().__init__(**params) else: super().__init__(**kwargs) @@ -42,9 +39,6 @@ def _check_required_features(self): if not has_features("ELECTROSTATICS"): raise NotImplementedError("Feature ELECTROSTATICS not compiled in") - def validate_params(self, params): - raise NotImplementedError("Derived classes must implement this method") - def default_params(self): raise NotImplementedError("Derived classes must implement this method") @@ -93,43 +87,6 @@ class ICC(ElectrostaticExtensions): _so_name = "Coulomb::ICCStar" _so_creation_policy = "GLOBAL" - def validate_params(self, params): - utils.check_type_or_throw_except( - params["n_icc"], 1, int, "Invalid parameter 'n_icc'") - utils.check_type_or_throw_except( - params["first_id"], 1, int, "Invalid parameter 'first_id'") - utils.check_type_or_throw_except( - params["convergence"], 1, float, "Invalid parameter 'convergence'") - utils.check_type_or_throw_except( - params["relaxation"], 1, float, "Invalid parameter 'relaxation'") - utils.check_type_or_throw_except( - params["ext_field"], 3, float, "Invalid parameter 'ext_field'") - utils.check_type_or_throw_except( - params["max_iterations"], 1, int, "Invalid parameter 'max_iterations'") - utils.check_type_or_throw_except( - params["eps_out"], 1, float, "Invalid parameter 'eps_out'") - - n_icc = params["n_icc"] - if n_icc <= 0: - raise ValueError("Parameter 'n_icc' must be >= 1") - - if n_icc: - if np.shape(params["normals"]) != (n_icc, 3): - raise ValueError("Parameter 'normals' has incorrect shape") - utils.check_array_type_or_throw_except( - np.reshape(params["normals"], (-1,)), 3 * n_icc, float, - "Parameter 'normals' has incorrect type") - - if "sigmas" not in params: - params["sigmas"] = np.zeros(n_icc) - - for key in ("areas", "sigmas", "epsilons"): - if np.shape(params[key]) != (n_icc,): - raise ValueError(f"Parameter '{key}' has incorrect shape") - utils.check_array_type_or_throw_except( - np.reshape(params[key], (-1,)), n_icc, float, - f"Parameter '{key}' has incorrect type") - def valid_keys(self): return {"n_icc", "convergence", "relaxation", "ext_field", "max_iterations", "first_id", "eps_out", "normals", diff --git a/src/python/espressomd/gen_pxiconfig.py b/src/python/espressomd/gen_pxiconfig.py deleted file mode 100644 index eeb72e9e26f..00000000000 --- a/src/python/espressomd/gen_pxiconfig.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (C) 2016-2022 The ESPResSo project -# Copyright (C) 2014 Olaf Lenz -# -# 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 . -# -# This script generates gen_pxiconfig.cpp, which in turn generates myconfig.pxi. -# -import inspect -import sys -import os -# find featuredefs.py -moduledir = os.path.dirname(inspect.getfile(inspect.currentframe())) -sys.path.append(os.path.join(moduledir, '..', '..', 'config')) -import featuredefs - -if len(sys.argv) != 3: - print(f"Usage: {sys.argv[0]} DEFFILE CPPFILE", file=sys.stderr) - exit(2) - -deffilename, cfilename = sys.argv[1:3] - -print("Reading definitions from " + deffilename + "...") -defs = featuredefs.defs(deffilename) -print("Done.") - -# generate cpp-file -print("Writing " + cfilename + "...") -cfile = open(cfilename, 'w') - -cfile.write(""" -#include "config/config.hpp" -#include -int main() { - -std::cout << "# This file was autogenerated." << std::endl - << "# Do not modify it or your changes will be overwritten!" << std::endl; - -""") - -template = """ -#ifdef {0} -std::cout << "DEF {0} = 1" << std::endl; -#else -std::cout << "DEF {0} = 0" << std::endl; -#endif -""" - -for feature in defs.allfeatures: - cfile.write(template.format(feature)) - -cfile.write(""" -} -""") - -cfile.close() -print("Done.") diff --git a/src/python/espressomd/interactions.py b/src/python/espressomd/interactions.py index 164f22112f6..4f0600f308d 100644 --- a/src/python/espressomd/interactions.py +++ b/src/python/espressomd/interactions.py @@ -1010,33 +1010,17 @@ class ThermalizedBond(BondedInteraction): distance vector of the particle pair. r_cut: :obj:`float`, optional Maximum distance beyond which the bond is considered broken. - seed : :obj:`int` - Seed of the philox RNG. Must be positive. - Required for the first thermalized bond in the system. Subsequent - thermalized bonds don't need a seed; if one is provided nonetheless, - it will overwrite the seed of all previously defined thermalized bonds, - even if the new bond is not added to the system. """ _so_name = "Interactions::ThermalizedBond" _type_number = BONDED_IA.THERMALIZED_DIST - def __init__(self, *args, **kwargs): - if kwargs and "sip" not in kwargs: - kwargs["rng_state"] = kwargs.get("rng_state") - super().__init__(*args, **kwargs) - - def _serialize(self): - params = self._ctor_params.copy() - params["rng_state"] = self.call_method("get_rng_state") - return params - def get_default_params(self): """Gets default values of optional parameters. """ - return {"r_cut": 0., "seed": None} + return {"r_cut": 0.} @script_interface_register diff --git a/src/python/espressomd/particle_data.py b/src/python/espressomd/particle_data.py index bde3cb5218a..8272834e47a 100644 --- a/src/python/espressomd/particle_data.py +++ b/src/python/espressomd/particle_data.py @@ -22,7 +22,7 @@ import functools from .interactions import BondedInteraction from .interactions import BondedInteractions -from .utils import nesting_level, array_locked, is_valid_type, handle_errors +from .utils import nesting_level, array_locked, is_valid_type from .utils import check_type_or_throw_except from .code_features import assert_features, has_features from .script_interface import script_interface_register, ScriptInterfaceHelper @@ -598,7 +598,6 @@ def vs_auto_relate_to(self, rel_to, override_cutoff_check=False, "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 diff --git a/src/python/espressomd/script_interface.pxd b/src/python/espressomd/script_interface.pxd index bf074a0c542..7a78a2daf77 100644 --- a/src/python/espressomd/script_interface.pxd +++ b/src/python/espressomd/script_interface.pxd @@ -21,7 +21,7 @@ from libcpp.unordered_map cimport unordered_map from libcpp.string cimport string from libcpp.memory cimport shared_ptr from libcpp.vector cimport vector -from libcpp cimport bool +from libcpp cimport bool as cbool from boost cimport string_ref @@ -37,8 +37,8 @@ cdef extern from "script_interface/ScriptInterface.hpp" namespace "ScriptInterfa Variant(const Variant & ) Variant & operator = (const Variant &) - bool is_type[T](const Variant &) - bool is_none(const Variant &) + cbool is_type[T](const Variant &) + cbool is_none(const Variant &) ctypedef unordered_map[string, Variant] VariantMap Variant make_variant[T](const T & x) @@ -71,7 +71,7 @@ cdef extern from "script_interface/initialize.hpp" namespace "ScriptInterface": void initialize(Factory[ObjectHandle] *) cdef extern from "script_interface/get_value.hpp" namespace "ScriptInterface": - T get_value[T](const Variant T) + T get_value[T](const Variant T) except + cdef extern from "script_interface/code_info/CodeInfo.hpp" namespace "ScriptInterface::CodeInfo": void check_features(const vector[string] & features) except + diff --git a/src/python/espressomd/script_interface.pyx b/src/python/espressomd/script_interface.pyx index d663a96adb4..948a070f183 100644 --- a/src/python/espressomd/script_interface.pyx +++ b/src/python/espressomd/script_interface.pyx @@ -23,6 +23,7 @@ from libcpp.memory cimport shared_ptr, make_shared from libcpp.vector cimport vector from libcpp.utility cimport pair from libcpp.unordered_map cimport unordered_map +from libcpp cimport bool as cbool cdef shared_ptr[ContextManager] _om @@ -192,6 +193,7 @@ cdef class PScriptInterface: for name, value in kwargs.items(): self.sip.get().set_parameter(utils.to_char_pointer(name), python_object_to_variant(value)) + utils.handle_errors(f"while setting parameter '{name}'") def get_parameter(self, name): cdef Variant value = self.sip.get().get_parameter(utils.to_char_pointer(name)) @@ -299,7 +301,7 @@ cdef Variant python_object_to_variant(value) except *: vec_variant.push_back(python_object_to_variant(e)) return make_variant[vector[Variant]](vec_variant) elif isinstance(value, (type(True), np.bool_)): - return make_variant[bool](value) + return make_variant[cbool](value) elif np.issubdtype(np.dtype(type(value)), np.signedinteger): return make_variant[int](value) elif np.issubdtype(np.dtype(type(value)), np.floating): @@ -308,7 +310,7 @@ cdef Variant python_object_to_variant(value) except *: raise TypeError( f"No conversion from type '{type(value).__name__}' to 'Variant'") -cdef variant_to_python_object(const Variant & value) except +: +cdef variant_to_python_object(const Variant & value): """Convert C++ Variant objects to Python objects.""" cdef vector[Variant] vec @@ -324,8 +326,8 @@ cdef variant_to_python_object(const Variant & value) except +: cdef Vector4d vec4d if is_none(value): return None - if is_type[bool](value): - return get_value[bool](value) + if is_type[cbool](value): + return get_value[cbool](value) if is_type[int](value): return get_value[int](value) if is_type[double](value): diff --git a/src/python/espressomd/system.py b/src/python/espressomd/system.py index 1fd8802017c..b4d0dd5f18e 100644 --- a/src/python/espressomd/system.py +++ b/src/python/espressomd/system.py @@ -38,8 +38,6 @@ from . import thermostat from .code_features import has_features, assert_features -from . import utils - from .script_interface import script_interface_register, ScriptInterfaceHelper @@ -152,16 +150,6 @@ class System(ScriptInterfaceHelper): _so_creation_policy = "GLOBAL" _so_bind_methods = _System._so_bind_methods - def __setattr__(self, attr, value): - if attr == "periodicity": - utils.check_type_or_throw_except( - value, 3, bool, "Attribute 'periodicity' must be a list of 3 bools") - if attr == "box_l": - utils.check_type_or_throw_except( - value, 3, float, "Attribute 'box_l' must be a list of 3 floats") - super().__setattr__(attr, value) - utils.handle_errors(f"while assigning system parameter '{attr}'") - def __init__(self, **kwargs): if "sip" in kwargs: super().__init__(**kwargs) @@ -245,7 +233,6 @@ def __getstate__(self): checkpointable_properties.append("collision_detection") if has_features("WALBERLA"): checkpointable_properties += ["_lb", "_ekcontainer"] - checkpointable_properties += ["thermostat"] odict = collections.OrderedDict() odict["_system_handle"] = self.call_method("get_system_handle") @@ -429,14 +416,10 @@ def distance_vec(self, p1, p2): if isinstance(p1, particle_data.ParticleHandle): pos1 = p1.pos_folded else: - utils.check_type_or_throw_except( - p1, 3, float, "p1 must be a particle or 3 floats") pos1 = p1 if isinstance(p2, particle_data.ParticleHandle): pos2 = p2.pos_folded else: - utils.check_type_or_throw_except( - p2, 3, float, "p2 must be a particle or 3 floats") pos2 = p2 return self.call_method("distance_vec", pos1=pos1, pos2=pos2) diff --git a/src/python/espressomd/thermostat.pxd b/src/python/espressomd/thermostat.pxd deleted file mode 100644 index 5830f75948a..00000000000 --- a/src/python/espressomd/thermostat.pxd +++ /dev/null @@ -1,122 +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 . -# -from libcpp cimport bool as cbool -from libc cimport stdint - -include "myconfig.pxi" -from .utils cimport Vector3d - -cdef extern from "thermostat.hpp": - double temperature - int thermo_switch - int THERMO_OFF - int THERMO_LANGEVIN - int THERMO_LB - int THERMO_NPT_ISO - int THERMO_DPD - int THERMO_BROWNIAN - int THERMO_SD - - cdef cppclass BaseThermostat: - stdint.uint32_t rng_seed() - stdint.uint64_t rng_counter() - cbool is_seed_required() - - IF PARTICLE_ANISOTROPY: - cdef cppclass LangevinThermostat(BaseThermostat): - Vector3d gamma_rotation - Vector3d gamma - cdef cppclass BrownianThermostat(BaseThermostat): - Vector3d gamma_rotation - Vector3d gamma - ELSE: - cdef cppclass LangevinThermostat(BaseThermostat): - double gamma_rotation - double gamma - cdef cppclass BrownianThermostat(BaseThermostat): - double gamma_rotation - double gamma - cdef cppclass IsotropicNptThermostat(BaseThermostat): - double gamma0 - double gammav - cdef cppclass ThermalizedBondThermostat(BaseThermostat): - pass - IF DPD: - cdef cppclass DPDThermostat(BaseThermostat): - pass - IF STOKESIAN_DYNAMICS: - cdef cppclass StokesianThermostat(BaseThermostat): - pass - - LangevinThermostat langevin - BrownianThermostat brownian - IsotropicNptThermostat npt_iso - ThermalizedBondThermostat thermalized_bond - IF DPD: - DPDThermostat dpd - IF STOKESIAN_DYNAMICS: - StokesianThermostat stokesian - - void langevin_set_rng_seed(stdint.uint32_t seed) - void brownian_set_rng_seed(stdint.uint32_t seed) - void npt_iso_set_rng_seed(stdint.uint32_t seed) - IF DPD: - void dpd_set_rng_seed(stdint.uint32_t seed) - IF STOKESIAN_DYNAMICS: - void stokesian_set_rng_seed(stdint.uint32_t seed) - - void langevin_set_rng_counter(stdint.uint64_t counter) - void brownian_set_rng_counter(stdint.uint64_t counter) - void npt_iso_set_rng_counter(stdint.uint64_t counter) - IF DPD: - void dpd_set_rng_counter(stdint.uint64_t counter) - IF STOKESIAN_DYNAMICS: - void stokesian_set_rng_counter(stdint.uint64_t counter) - - IF PARTICLE_ANISOTROPY: - void mpi_set_brownian_gamma(const Vector3d & gamma) - void mpi_set_brownian_gamma_rot(const Vector3d & gamma) - - void mpi_set_langevin_gamma(const Vector3d & gamma) - void mpi_set_langevin_gamma_rot(const Vector3d & gamma) - ELSE: - void mpi_set_brownian_gamma(const double & gamma) - void mpi_set_brownian_gamma_rot(const double & gamma) - - void mpi_set_langevin_gamma(const double & gamma) - void mpi_set_langevin_gamma_rot(const double & gamma) - - void mpi_set_temperature(double temperature) - void mpi_set_thermo_switch(int thermo_switch) - - IF NPT: - void mpi_set_nptiso_gammas(double gamma0, double gammav) - -cdef extern from "stokesian_dynamics/sd_interface.hpp": - IF STOKESIAN_DYNAMICS: - void set_sd_kT(double kT) except + - double get_sd_kT() - -cdef extern from "lb/particle_coupling.hpp": - void lb_lbcoupling_set_rng_state(stdint.uint64_t) except + - stdint.uint64_t lb_lbcoupling_get_rng_state() except + - void lb_lbcoupling_set_gamma(double) except + - double lb_lbcoupling_get_gamma() except + - cbool lb_lbcoupling_is_seed_required() except + - void mpi_bcast_lb_particle_coupling() diff --git a/src/python/espressomd/thermostat.py b/src/python/espressomd/thermostat.py new file mode 100644 index 00000000000..0d8f02d72b4 --- /dev/null +++ b/src/python/espressomd/thermostat.py @@ -0,0 +1,187 @@ +# +# 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 . +# + +from .script_interface import script_interface_register, ScriptInterfaceHelper + + +@script_interface_register +class Thermostat(ScriptInterfaceHelper): + """ + Container for the system thermostat. Multiple thermostats can be active + simultaneously. When setting up a thermostat raises an exception, e.g. + due to invalid parameters, the thermostat gets disabled. Therefore, + updating an already active thermostat with invalid parameters will + deactivate it. + + Methods + ------- + turn_off() + Turn off all thermostats. + + set_langevin() + Set the Langevin thermostat. + + Parameters + ----------- + kT : :obj:`float` + Thermal energy of the simulated heat bath. + gamma : :obj:`float` + Contains the friction coefficient of the bath. If the feature + ``PARTICLE_ANISOTROPY`` is compiled in, then ``gamma`` can be a list + of three positive floats, for the friction coefficient in each + cardinal direction. + gamma_rotation : :obj:`float`, optional + 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. + seed : :obj:`int` + Initial counter value (or seed) of the philox RNG. + Required on first activation of the Langevin thermostat. + Must be positive. + + set_brownian() + Set the Brownian thermostat. + + Parameters + ---------- + kT : :obj:`float` + Thermal energy of the simulated heat bath. + gamma : :obj:`float` + Contains the friction coefficient of the bath. If the feature + ``PARTICLE_ANISOTROPY`` is compiled in, then ``gamma`` can be a list + of three positive floats, for the friction coefficient in each + cardinal direction. + gamma_rotation : :obj:`float`, optional + 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. + seed : :obj:`int` + Initial counter value (or seed) of the philox RNG. + Required on first activation of the Brownian thermostat. + Must be positive. + + set_lb() + Set the LB thermostat. The kT parameter is automatically extracted + from the currently active LB solver. + + This thermostat requires the feature ``WALBERLA``. + + Parameters + ---------- + seed : :obj:`int` + Seed for the random number generator, required if kT > 0. + Must be positive. + gamma : :obj:`float` + Frictional coupling constant for the MD particle coupling. + + set_npt() + Set the NPT thermostat. + + Parameters + ---------- + kT : :obj:`float` + Thermal energy of the heat bath. + gamma0 : :obj:`float` + Friction coefficient of the bath. + gammav : :obj:`float` + Artificial friction coefficient for the volume fluctuations. + seed : :obj:`int` + Initial counter value (or seed) of the philox RNG. + Required on first activation of the Langevin thermostat. + Must be positive. + + set_dpd() + Set the DPD thermostat with required parameters 'kT'. + This also activates the DPD interactions. + + Parameters + ---------- + kT : :obj:`float` + Thermal energy of the heat bath. + seed : :obj:`int` + Initial counter value (or seed) of the philox RNG. + Required on first activation of the DPD thermostat. + Must be positive. + + set_stokesian() + Set the SD thermostat with required parameters. + + This thermostat requires the feature ``STOKESIAN_DYNAMICS``. + + Parameters + ---------- + kT : :obj:`float`, optional + Temperature. + seed : :obj:`int`, optional + Seed for the random number generator. + + """ + _so_name = "Thermostat::Thermostat" + _so_creation_policy = "GLOBAL" + _so_bind_methods = ( + "set_langevin", + "set_brownian", + "set_npt", + "set_dpd", + "set_lb", + "set_stokesian", + "set_thermalized_bond", + "turn_off") + + +@script_interface_register +class Langevin(ScriptInterfaceHelper): + _so_name = "Thermostat::Langevin" + _so_creation_policy = "GLOBAL" + + +@script_interface_register +class Brownian(ScriptInterfaceHelper): + _so_name = "Thermostat::Brownian" + _so_creation_policy = "GLOBAL" + + +@script_interface_register +class IsotropicNpt(ScriptInterfaceHelper): + _so_name = "Thermostat::IsotropicNpt" + _so_creation_policy = "GLOBAL" + + +@script_interface_register +class Stokesian(ScriptInterfaceHelper): + _so_name = "Thermostat::Stokesian" + _so_creation_policy = "GLOBAL" + + +@script_interface_register +class LBThermostat(ScriptInterfaceHelper): + _so_name = "Thermostat::LB" + _so_creation_policy = "GLOBAL" + + +@script_interface_register +class DPDThermostat(ScriptInterfaceHelper): + _so_name = "Thermostat::DPD" + _so_creation_policy = "GLOBAL" + + +@script_interface_register +class ThermalizedBond(ScriptInterfaceHelper): + _so_name = "Thermostat::ThermalizedBond" + _so_creation_policy = "GLOBAL" diff --git a/src/python/espressomd/thermostat.pyx b/src/python/espressomd/thermostat.pyx deleted file mode 100644 index ae269c4d38e..00000000000 --- a/src/python/espressomd/thermostat.pyx +++ /dev/null @@ -1,663 +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 functools -include "myconfig.pxi" -from . cimport utils - - -def AssertThermostatType(*allowed_thermostats): - """Assert that only a certain group of thermostats is active at a time. - - Decorator class to ensure that only specific combinations of thermostats - can be activated together by the user. Usage: - - .. code-block:: cython - - cdef class Thermostat: - @AssertThermostatType(THERMO_LANGEVIN, THERMO_DPD) - def set_langevin(self, kT=None, gamma=None, gamma_rotation=None, - seed=None): - ... - - This will prefix an assertion that prevents setting up the Langevin - thermostat if the list of active thermostats contains anything other - than the DPD and Langevin thermostats. - - Parameters - ---------- - allowed_thermostats : :obj:`str` - Allowed list of thermostats which are known to be compatible - with one another. - - """ - def decoratorfunction(function): - @functools.wraps(function, assigned=('__name__', '__doc__')) - def wrapper(*args, **kwargs): - if (not (thermo_switch in allowed_thermostats) and - (thermo_switch != THERMO_OFF)): - raise Exception( - "This combination of thermostats is not allowed!") - function(*args, **kwargs) - return wrapper - return decoratorfunction - - -cdef class Thermostat: - - # We have to cdef the state variable because it is a cdef class - cdef _state - cdef _LB_fluid - - def __init__(self): - self._state = None - pass - - def suspend(self): - """Suspend the thermostat - - The thermostat can be suspended, e.g. to perform an energy - minimization. - - """ - self._state = self.__getstate__() - self.turn_off() - - def recover(self): - """Recover a suspended thermostat - - If the thermostat had been suspended using :meth:`suspend`, it can - be recovered with this method. - - """ - if self._state is not None: - self.__setstate__(self._state) - - # __getstate__ and __setstate__ define the pickle interaction - def __getstate__(self): - # Attributes to pickle. - thermolist = self.get_state() - return thermolist - - def __setstate__(self, thermolist): - if thermolist == []: - return - - for thmst in thermolist: - if thmst["type"] == "OFF": - self.turn_off() - if thmst["type"] == "LANGEVIN": - self.set_langevin(kT=thmst["kT"], gamma=thmst["gamma"], - gamma_rotation=thmst["gamma_rotation"], - seed=thmst["seed"]) - langevin_set_rng_counter(thmst["counter"]) - if thmst["type"] == "LB": - # The LB fluid object is a deep copy of the original, we must - # attach it to be able to set up the particle coupling global - # variable, and then detach it to allow the original LB - # fluid object to be attached by the System class. - # This workaround will be removed when the thermostat class - # gets rewritten as a ScriptInterface class (#4266, #4594) - thmst["LB_fluid"].call_method("activate") - self.set_lb( - LB_fluid=thmst["LB_fluid"], - gamma=thmst["gamma"], - seed=thmst["rng_counter_fluid"]) - thmst["LB_fluid"].call_method("deactivate") - if thmst["type"] == "NPT_ISO": - if NPT: - self.set_npt(kT=thmst["kT"], gamma0=thmst["gamma0"], - gammav=thmst["gammav"], seed=thmst["seed"]) - npt_iso_set_rng_counter(thmst["counter"]) - if thmst["type"] == "DPD": - if DPD: - self.set_dpd(kT=thmst["kT"], seed=thmst["seed"]) - dpd_set_rng_counter(thmst["counter"]) - if thmst["type"] == "BROWNIAN": - self.set_brownian(kT=thmst["kT"], gamma=thmst["gamma"], - gamma_rotation=thmst["gamma_rotation"], - seed=thmst["seed"]) - brownian_set_rng_counter(thmst["counter"]) - if thmst["type"] == "SD": - IF STOKESIAN_DYNAMICS: - self.set_stokesian(kT=thmst["kT"], seed=thmst["seed"]) - stokesian_set_rng_counter(thmst["counter"]) - - def get_ts(self): - return thermo_switch - - def get_state(self): - """Returns the thermostat status.""" - thermo_list = [] - if temperature == -1: - raise Exception("Thermostat is not initialized") - if thermo_switch == THERMO_OFF: - return [{"type": "OFF"}] - if thermo_switch & THERMO_LANGEVIN: - lang_dict = {} - lang_dict["type"] = "LANGEVIN" - lang_dict["kT"] = temperature - lang_dict["seed"] = langevin.rng_seed() - lang_dict["counter"] = langevin.rng_counter() - IF PARTICLE_ANISOTROPY: - lang_dict["gamma"] = [langevin.gamma[0], - langevin.gamma[1], - langevin.gamma[2]] - ELSE: - lang_dict["gamma"] = langevin.gamma - IF ROTATION: - IF PARTICLE_ANISOTROPY: - lang_dict["gamma_rotation"] = [langevin.gamma_rotation[0], - langevin.gamma_rotation[1], - langevin.gamma_rotation[2]] - ELSE: - lang_dict["gamma_rotation"] = langevin.gamma_rotation - ELSE: - lang_dict["gamma_rotation"] = None - - thermo_list.append(lang_dict) - if thermo_switch & THERMO_BROWNIAN: - lang_dict = {} - lang_dict["type"] = "BROWNIAN" - lang_dict["kT"] = temperature - lang_dict["seed"] = brownian.rng_seed() - lang_dict["counter"] = brownian.rng_counter() - IF PARTICLE_ANISOTROPY: - lang_dict["gamma"] = [brownian.gamma[0], - brownian.gamma[1], - brownian.gamma[2]] - ELSE: - lang_dict["gamma"] = brownian.gamma - IF ROTATION: - IF PARTICLE_ANISOTROPY: - lang_dict["gamma_rotation"] = [brownian.gamma_rotation[0], - brownian.gamma_rotation[1], - brownian.gamma_rotation[2]] - ELSE: - lang_dict["gamma_rotation"] = brownian.gamma_rotation - ELSE: - lang_dict["gamma_rotation"] = None - - thermo_list.append(lang_dict) - if thermo_switch & THERMO_LB: - lb_dict = {} - lb_dict["LB_fluid"] = self._LB_fluid - lb_dict["gamma"] = lb_lbcoupling_get_gamma() - lb_dict["type"] = "LB" - lb_dict["rng_counter_fluid"] = lb_lbcoupling_get_rng_state() - thermo_list.append(lb_dict) - if thermo_switch & THERMO_NPT_ISO: - if NPT: - npt_dict = {} - npt_dict["type"] = "NPT_ISO" - npt_dict["kT"] = temperature - npt_dict["seed"] = npt_iso.rng_seed() - npt_dict["counter"] = npt_iso.rng_counter() - npt_dict["gamma0"] = npt_iso.gamma0 - npt_dict["gammav"] = npt_iso.gammav - thermo_list.append(npt_dict) - if thermo_switch & THERMO_DPD: - IF DPD: - dpd_dict = {} - dpd_dict["type"] = "DPD" - dpd_dict["kT"] = temperature - dpd_dict["seed"] = dpd.rng_seed() - dpd_dict["counter"] = dpd.rng_counter() - thermo_list.append(dpd_dict) - if thermo_switch & THERMO_SD: - IF STOKESIAN_DYNAMICS: - sd_dict = {} - sd_dict["type"] = "SD" - sd_dict["kT"] = get_sd_kT() - sd_dict["seed"] = stokesian.rng_seed() - sd_dict["counter"] = stokesian.rng_counter() - thermo_list.append(sd_dict) - return thermo_list - - def _set_temperature(self, kT): - mpi_set_temperature(kT) - utils.handle_errors("Temperature change failed") - - def turn_off(self): - """ - Turns off all the thermostat and sets all the thermostat variables to zero. - - """ - - self._set_temperature(0.) - IF PARTICLE_ANISOTROPY: - mpi_set_langevin_gamma(utils.make_Vector3d((0., 0., 0.))) - mpi_set_brownian_gamma(utils.make_Vector3d((0., 0., 0.))) - IF ROTATION: - mpi_set_langevin_gamma_rot(utils.make_Vector3d((0., 0., 0.))) - mpi_set_brownian_gamma_rot(utils.make_Vector3d((0., 0., 0.))) - ELSE: - mpi_set_langevin_gamma(0.) - mpi_set_brownian_gamma(0.) - IF ROTATION: - mpi_set_langevin_gamma_rot(0.) - mpi_set_brownian_gamma_rot(0.) - - mpi_set_thermo_switch(THERMO_OFF) - lb_lbcoupling_set_gamma(0.0) - mpi_bcast_lb_particle_coupling() - - @AssertThermostatType(THERMO_LANGEVIN, THERMO_DPD, THERMO_LB) - def set_langevin(self, kT, gamma, gamma_rotation=None, seed=None): - """ - Sets the Langevin thermostat. - - Parameters - ----------- - kT : :obj:`float` - Thermal energy of the simulated heat bath. - gamma : :obj:`float` - Contains the friction coefficient of the bath. If the feature - ``PARTICLE_ANISOTROPY`` is compiled in, then ``gamma`` can be a list - of three positive floats, for the friction coefficient in each - cardinal direction. - gamma_rotation : :obj:`float`, optional - 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. - seed : :obj:`int` - Initial counter value (or seed) of the philox RNG. - Required on first activation of the Langevin thermostat. - Must be positive. - - """ - - scalar_gamma_def = True - scalar_gamma_rot_def = True - IF PARTICLE_ANISOTROPY: - if hasattr(gamma, "__iter__"): - scalar_gamma_def = False - else: - scalar_gamma_def = True - - IF PARTICLE_ANISOTROPY: - if hasattr(gamma_rotation, "__iter__"): - scalar_gamma_rot_def = False - else: - scalar_gamma_rot_def = True - - utils.check_type_or_throw_except(kT, 1, float, "kT must be a number") - if scalar_gamma_def: - utils.check_type_or_throw_except( - gamma, 1, float, "gamma must be a number") - else: - utils.check_type_or_throw_except( - gamma, 3, float, "diagonal elements of the gamma tensor must be numbers") - if gamma_rotation is not None: - if scalar_gamma_rot_def: - utils.check_type_or_throw_except( - gamma_rotation, 1, float, "gamma_rotation must be a number") - else: - utils.check_type_or_throw_except( - gamma_rotation, 3, float, "diagonal elements of the gamma_rotation tensor must be numbers") - - if scalar_gamma_def: - if float(kT) < 0. or float(gamma) < 0.: - raise ValueError( - "temperature and gamma must be positive numbers") - else: - if float(kT) < 0. or float(gamma[0]) < 0. or float( - gamma[1]) < 0. or float(gamma[2]) < 0.: - raise ValueError( - "temperature and diagonal elements of the gamma tensor must be positive numbers") - if gamma_rotation is not None: - if scalar_gamma_rot_def: - if float(gamma_rotation) < 0.: - raise ValueError( - "gamma_rotation must be positive number") - else: - if float(gamma_rotation[0]) < 0. or float( - gamma_rotation[1]) < 0. or float(gamma_rotation[2]) < 0.: - raise ValueError( - "diagonal elements of the gamma_rotation tensor must be positive numbers") - - # Seed is required if the RNG is not initialized - if seed is None and langevin.is_seed_required(): - raise ValueError( - "A seed has to be given as keyword argument on first activation of the thermostat") - - if seed is not None: - utils.check_type_or_throw_except( - seed, 1, int, "seed must be a positive integer") - if seed < 0: - raise ValueError("seed must be a positive integer") - langevin_set_rng_seed(seed) - - self._set_temperature(kT) - IF PARTICLE_ANISOTROPY: - cdef utils.Vector3d gamma_vec - if scalar_gamma_def: - for i in range(3): - gamma_vec[i] = gamma - else: - gamma_vec = utils.make_Vector3d(gamma) - IF ROTATION: - IF PARTICLE_ANISOTROPY: - cdef utils.Vector3d gamma_rot_vec - if gamma_rotation is None: - # rotational gamma is translational gamma - gamma_rot_vec = gamma_vec - else: - if scalar_gamma_rot_def: - for i in range(3): - gamma_rot_vec[i] = gamma_rotation - else: - gamma_rot_vec = utils.make_Vector3d(gamma_rotation) - ELSE: - if gamma_rotation is None: - # rotational gamma is translational gamma - gamma_rotation = gamma - - global thermo_switch - mpi_set_thermo_switch(thermo_switch | THERMO_LANGEVIN) - IF PARTICLE_ANISOTROPY: - mpi_set_langevin_gamma(gamma_vec) - IF ROTATION: - mpi_set_langevin_gamma_rot(gamma_rot_vec) - ELSE: - mpi_set_langevin_gamma(gamma) - IF ROTATION: - mpi_set_langevin_gamma_rot(gamma_rotation) - - @AssertThermostatType(THERMO_BROWNIAN) - def set_brownian(self, kT, gamma, gamma_rotation=None, seed=None): - """Sets the Brownian thermostat. - - Parameters - ----------- - kT : :obj:`float` - Thermal energy of the simulated heat bath. - gamma : :obj:`float` - Contains the friction coefficient of the bath. If the feature - ``PARTICLE_ANISOTROPY`` is compiled in, then ``gamma`` can be a list - of three positive floats, for the friction coefficient in each - cardinal direction. - gamma_rotation : :obj:`float`, optional - 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. - seed : :obj:`int` - Initial counter value (or seed) of the philox RNG. - Required on first activation of the Brownian thermostat. - Must be positive. - - """ - - scalar_gamma_def = True - scalar_gamma_rot_def = True - IF PARTICLE_ANISOTROPY: - if hasattr(gamma, "__iter__"): - scalar_gamma_def = False - else: - scalar_gamma_def = True - - IF PARTICLE_ANISOTROPY: - if hasattr(gamma_rotation, "__iter__"): - scalar_gamma_rot_def = False - else: - scalar_gamma_rot_def = True - - utils.check_type_or_throw_except(kT, 1, float, "kT must be a number") - if scalar_gamma_def: - utils.check_type_or_throw_except( - gamma, 1, float, "gamma must be a number") - else: - utils.check_type_or_throw_except( - gamma, 3, float, "diagonal elements of the gamma tensor must be numbers") - if gamma_rotation is not None: - if scalar_gamma_rot_def: - utils.check_type_or_throw_except( - gamma_rotation, 1, float, "gamma_rotation must be a number") - else: - utils.check_type_or_throw_except( - gamma_rotation, 3, float, "diagonal elements of the gamma_rotation tensor must be numbers") - - if scalar_gamma_def: - if float(kT) < 0. or float(gamma) < 0.: - raise ValueError( - "temperature and gamma must be positive numbers") - else: - if float(kT) < 0. or float(gamma[0]) < 0. or float( - gamma[1]) < 0. or float(gamma[2]) < 0.: - raise ValueError( - "temperature and diagonal elements of the gamma tensor must be positive numbers") - if gamma_rotation is not None: - if scalar_gamma_rot_def: - if float(gamma_rotation) < 0.: - raise ValueError( - "gamma_rotation must be positive number") - else: - if float(gamma_rotation[0]) < 0. or float( - gamma_rotation[1]) < 0. or float(gamma_rotation[2]) < 0.: - raise ValueError( - "diagonal elements of the gamma_rotation tensor must be positive numbers") - - # Seed is required if the RNG is not initialized - if seed is None and brownian.is_seed_required(): - raise ValueError( - "A seed has to be given as keyword argument on first activation of the thermostat") - - if seed is not None: - utils.check_type_or_throw_except( - seed, 1, int, "seed must be a positive integer") - if seed < 0: - raise ValueError("seed must be a positive integer") - brownian_set_rng_seed(seed) - - self._set_temperature(kT) - IF PARTICLE_ANISOTROPY: - cdef utils.Vector3d gamma_vec - if scalar_gamma_def: - for i in range(3): - gamma_vec[i] = gamma - else: - gamma_vec = utils.make_Vector3d(gamma) - IF ROTATION: - IF PARTICLE_ANISOTROPY: - cdef utils.Vector3d gamma_rot_vec - if gamma_rotation is None: - # rotational gamma is translational gamma - gamma_rot_vec = gamma_vec - else: - if scalar_gamma_rot_def: - for i in range(3): - gamma_rot_vec[i] = gamma_rotation - else: - gamma_rot_vec = utils.make_Vector3d(gamma_rotation) - ELSE: - if gamma_rotation is None: - # rotational gamma is translational gamma - gamma_rotation = gamma - - global thermo_switch - mpi_set_thermo_switch(thermo_switch | THERMO_BROWNIAN) - - IF PARTICLE_ANISOTROPY: - mpi_set_brownian_gamma(gamma_vec) - IF ROTATION: - mpi_set_brownian_gamma_rot(gamma_rot_vec) - ELSE: - mpi_set_brownian_gamma(gamma) - IF ROTATION: - mpi_set_brownian_gamma_rot(gamma_rotation) - - @AssertThermostatType(THERMO_LB, THERMO_DPD, THERMO_LANGEVIN) - def set_lb(self, seed=None, LB_fluid=None, gamma=0.0): - """ - Sets the LB thermostat. - - This thermostat requires the feature ``WALBERLA``. - - Parameters - ---------- - LB_fluid : :class:`~espressomd.lb.LBFluidWalberla` - seed : :obj:`int` - Seed for the random number generator, required if kT > 0. - Must be positive. - gamma : :obj:`float` - Frictional coupling constant for the MD particle coupling. - - """ - from .lb import HydrodynamicInteraction - if not isinstance(LB_fluid, HydrodynamicInteraction): - raise ValueError( - "The LB thermostat requires a LB instance as a keyword arg.") - - self._LB_fluid = LB_fluid - if LB_fluid.kT > 0.: - if seed is None and lb_lbcoupling_is_seed_required(): - raise ValueError( - "seed has to be given as keyword arg") - if seed is not None: - utils.check_type_or_throw_except( - seed, 1, int, "seed must be a positive integer") - if seed < 0: - raise ValueError("seed must be a positive integer") - lb_lbcoupling_set_rng_state(seed) - mpi_bcast_lb_particle_coupling() - else: - lb_lbcoupling_set_rng_state(0) - mpi_bcast_lb_particle_coupling() - - global thermo_switch - mpi_set_thermo_switch(thermo_switch | THERMO_LB) - - lb_lbcoupling_set_gamma(gamma) - mpi_bcast_lb_particle_coupling() - - IF NPT: - @AssertThermostatType(THERMO_NPT_ISO) - def set_npt(self, kT, gamma0, gammav, seed=None): - """ - Sets the NPT thermostat. - - Parameters - ---------- - kT : :obj:`float` - Thermal energy of the heat bath - gamma0 : :obj:`float` - Friction coefficient of the bath - gammav : :obj:`float` - Artificial friction coefficient for the volume fluctuations. - seed : :obj:`int` - Initial counter value (or seed) of the philox RNG. - Required on first activation of the Langevin thermostat. - Must be positive. - - """ - - utils.check_type_or_throw_except( - kT, 1, float, "kT must be a number") - - # Seed is required if the RNG is not initialized - if seed is None and npt_iso.is_seed_required(): - raise ValueError( - "A seed has to be given as keyword argument on first activation of the thermostat") - - if seed is not None: - utils.check_type_or_throw_except( - seed, 1, int, "seed must be a positive integer") - if seed < 0: - raise ValueError("seed must be a positive integer") - npt_iso_set_rng_seed(seed) - - self._set_temperature(kT) - global thermo_switch - mpi_set_thermo_switch(thermo_switch | THERMO_NPT_ISO) - mpi_set_nptiso_gammas(gamma0, gammav) - - IF DPD: - @AssertThermostatType(THERMO_DPD, THERMO_LANGEVIN, THERMO_LB) - def set_dpd(self, kT, seed=None): - """ - Sets the DPD thermostat with required parameters 'kT'. - This also activates the DPD interactions. - - Parameters - ---------- - kT : :obj:`float` - Thermal energy of the heat bath. - seed : :obj:`int` - Initial counter value (or seed) of the philox RNG. - Required on first activation of the DPD thermostat. - Must be positive. - - """ - utils.check_type_or_throw_except( - kT, 1, float, "kT must be a number") - - # Seed is required if the RNG is not initialized - if seed is None and dpd.is_seed_required(): - raise ValueError( - "A seed has to be given as keyword argument on first activation of the thermostat") - - if seed is not None: - utils.check_type_or_throw_except( - seed, 1, int, "seed must be a positive integer") - if seed < 0: - raise ValueError("seed must be a positive integer") - dpd_set_rng_seed(seed) - - self._set_temperature(kT) - global thermo_switch - mpi_set_thermo_switch(thermo_switch | THERMO_DPD) - - IF STOKESIAN_DYNAMICS: - @AssertThermostatType(THERMO_SD) - def set_stokesian(self, kT=None, seed=None): - """ - Sets the SD thermostat with required parameters. - - This thermostat requires the feature ``STOKESIAN_DYNAMICS``. - - Parameters - ---------- - kT : :obj:`float`, optional - Temperature. - seed : :obj:`int`, optional - Seed for the random number generator - - """ - - if (kT is None) or (kT == 0): - set_sd_kT(0.0) - if stokesian.is_seed_required(): - stokesian_set_rng_seed(0) - else: - utils.check_type_or_throw_except( - kT, 1, float, "kT must be a float") - set_sd_kT(kT) - - # Seed is required if the RNG is not initialized - if seed is None and stokesian.is_seed_required(): - raise ValueError( - "A seed has to be given as keyword argument on first activation of the thermostat") - if seed is not None: - utils.check_type_or_throw_except( - seed, 1, int, "seed must be an integer") - if seed < 0: - raise ValueError("seed must be a positive integer") - stokesian_set_rng_seed(seed) - - global thermo_switch - mpi_set_thermo_switch(thermo_switch | THERMO_SD) diff --git a/src/python/espressomd/utils.pxd b/src/python/espressomd/utils.pxd index 6217a275c4d..4ae8f9ba0d1 100644 --- a/src/python/espressomd/utils.pxd +++ b/src/python/espressomd/utils.pxd @@ -21,8 +21,6 @@ from libcpp.string cimport string # import std::string as string from libcpp.vector cimport vector # import std::vector as vector from libcpp cimport bool as cbool -cpdef check_type_or_throw_except(x, n, t, msg) - cdef extern from "error_handling/RuntimeError.hpp" namespace "ErrorHandling::RuntimeError": cdef cppclass ErrorLevel: pass @@ -40,8 +38,6 @@ cdef extern from "error_handling/RuntimeError.hpp" namespace "ErrorHandling": cdef extern from "errorhandling.hpp" namespace "ErrorHandling": cdef vector[RuntimeError] mpi_gather_runtime_errors() -cpdef handle_errors(msg) - cdef extern from "utils/Vector.hpp" namespace "Utils": cppclass Vector2d: double & operator[](int i) @@ -62,6 +58,3 @@ cdef extern from "utils/Vector.hpp" namespace "Utils": cppclass Vector3i: int & operator[](int i) int * data() - -cdef make_array_locked(Vector3d) -cdef Vector3d make_Vector3d(a) diff --git a/src/python/espressomd/utils.pyx b/src/python/espressomd/utils.pyx index a63837faccc..2538be38f6d 100644 --- a/src/python/espressomd/utils.pyx +++ b/src/python/espressomd/utils.pyx @@ -20,52 +20,67 @@ cimport numpy as np import numpy as np -cdef _check_type_or_throw_except_assertion(x, t): - return isinstance(x, t) or (t == int and is_valid_type(x, int)) or ( - t == float and (is_valid_type(x, int) or is_valid_type(x, float))) or ( - t == bool and is_valid_type(x, bool)) +def is_valid_type(value, t): + """ + Extended checks for numpy int, float and bool types. + Handles 0-dimensional arrays. + + """ + if value is None: + return False + if isinstance(value, np.ndarray) and value.shape == (): + value = value[()] + if t is int: + return isinstance(value, (int, np.integer)) + elif t is float: + float_types = [ + float, np.float16, np.float32, np.float64, np.longdouble] + if hasattr(np, "float128"): + float_types.append(np.float128) + return isinstance(value, tuple(float_types)) + elif t is bool: + return isinstance(value, (bool, np.bool_)) + else: + return isinstance(value, t) -cpdef check_array_type_or_throw_except(x, n, t, msg): +def check_type_or_throw_except(x, n, t, msg): """ Check that ``x`` is of type ``t`` and that ``n`` values are given, - otherwise raise a ``ValueError`` with message ``msg``. + otherwise raise a ``ValueError`` with message ``msg``. If ``x`` is an + array/list/tuple, the type checking is done on the elements, and all + elements are checked. If ``n`` is 1, ``x`` is assumed to be a scalar. Integers are accepted when a float was asked for. """ + + def check_type(x, t): + return isinstance(x, t) or \ + (t is int and is_valid_type(x, int)) or \ + (t is bool and is_valid_type(x, bool)) or \ + (t is float and (is_valid_type(x, float) or is_valid_type(x, int))) + + if n == 1: + if not check_type(x, t): + raise ValueError(f"{msg} -- Got an {type(x).__name__}") + return + if not hasattr(x, "__getitem__"): raise ValueError( - msg + f" -- A single value was given but {n} were expected.") + f"{msg} -- A single value was given but {n} were expected.") if len(x) != n: raise ValueError( - msg + f" -- {len(x)} values were given but {n} were expected.") + f"{msg} -- {len(x)} values were given but {n} were expected.") if isinstance(x, np.ndarray): value = x.dtype.type() # default-constructed value of the same type - if not _check_type_or_throw_except_assertion(value, t): + if not check_type(value, t): raise ValueError( - msg + f" -- Array was of type {type(value).__name__}") + f"{msg} -- Array was of type {type(value).__name__}") return for i in range(len(x)): - if not _check_type_or_throw_except_assertion(x[i], t): + if not check_type(x[i], t): raise ValueError( - msg + f" -- Item {i} was of type {type(x[i]).__name__}") - - -cpdef check_type_or_throw_except(x, n, t, msg): - """ - Check that ``x`` is of type ``t`` and that ``n`` values are given, - otherwise raise a ``ValueError`` with message ``msg``. If ``x`` is an - array/list/tuple, the type checking is done on the elements, and all - elements are checked. If ``n`` is 1, ``x`` is assumed to be a scalar. - Integers are accepted when a float was asked for. - - """ - # Check whether x is an array/list/tuple or a single value - if n > 1: - check_array_type_or_throw_except(x, n, t, msg) - else: - if not _check_type_or_throw_except_assertion(x, t): - raise ValueError(msg + f" -- Got an {type(x).__name__}") + f"{msg} -- Item {i} was of type {type(x[i]).__name__}") def to_char_pointer(s): @@ -176,18 +191,7 @@ Use numpy.copy() to get a writable copy." raise ValueError(array_locked.ERR_MSG) -cdef make_array_locked(Vector3d v): - return array_locked([v[0], v[1], v[2]]) - - -cdef Vector3d make_Vector3d(a): - cdef Vector3d v - for i, ai in enumerate(a): - v[i] = ai - return v - - -cpdef handle_errors(msg): +def handle_errors(msg): """ Gathers runtime errors. @@ -227,30 +231,6 @@ def nesting_level(obj): return max_level + 1 -def is_valid_type(value, t): - """ - Extended checks for numpy int, float and bool types. - Handles 0-dimensional arrays. - - """ - if value is None: - return False - if isinstance(value, np.ndarray) and value.shape == (): - value = value[()] - if t == int: - return isinstance(value, (int, np.integer)) - elif t == float: - float_types = [ - float, np.float16, np.float32, np.float64, np.longdouble] - if hasattr(np, 'float128'): - float_types.append(np.float128) - return isinstance(value, tuple(float_types)) - elif t == bool: - return isinstance(value, (bool, np.bool_)) - else: - return isinstance(value, t) - - def check_required_keys(required_keys, obtained_keys): a = required_keys b = obtained_keys diff --git a/src/script_interface/CMakeLists.txt b/src/script_interface/CMakeLists.txt index 94030a06391..14a9f75bd9f 100644 --- a/src/script_interface/CMakeLists.txt +++ b/src/script_interface/CMakeLists.txt @@ -50,6 +50,7 @@ add_subdirectory(reaction_methods) add_subdirectory(scafacos) add_subdirectory(shapes) add_subdirectory(system) +add_subdirectory(thermostat) add_subdirectory(walberla) install(TARGETS espresso_script_interface diff --git a/src/script_interface/Context.hpp b/src/script_interface/Context.hpp index a4640439e71..38c91f3ce8c 100644 --- a/src/script_interface/Context.hpp +++ b/src/script_interface/Context.hpp @@ -103,7 +103,7 @@ class Context : public std::enable_shared_from_this { public: /** - * @brief Get the class name for an ObjectHandle instance. + * @brief Get the class name for an @ref ObjectHandle instance. * * This returns the name by which the object can be created. */ diff --git a/src/script_interface/electrostatics/ICCStar.hpp b/src/script_interface/electrostatics/ICCStar.hpp index 3e45def0250..9e995e7528e 100644 --- a/src/script_interface/electrostatics/ICCStar.hpp +++ b/src/script_interface/electrostatics/ICCStar.hpp @@ -77,13 +77,21 @@ class ICCStar : public AutoParameters { } void do_construct(VariantMap const ¶ms) override { + auto const n_icc = get_value(params, "n_icc"); + // by default, sigmas are zeros + std::vector sigmas{}; + if (params.count("sigmas")) { + sigmas = get_value>(params, "sigmas"); + } else if (n_icc >= 1) { + sigmas.resize(n_icc); + } auto icc_parameters = ::icc_data{ - get_value(params, "n_icc"), + n_icc, get_value(params, "max_iterations"), get_value(params, "eps_out"), get_value>(params, "areas"), get_value>(params, "epsilons"), - get_value>(params, "sigmas"), + sigmas, get_value(params, "convergence"), get_value>(params, "normals"), get_value(params, "ext_field"), diff --git a/src/script_interface/get_value.hpp b/src/script_interface/get_value.hpp index 5cf71638794..8f1bc914ffd 100644 --- a/src/script_interface/get_value.hpp +++ b/src/script_interface/get_value.hpp @@ -75,22 +75,28 @@ auto simplify_symbol(Utils::Vector const *) { } /** @overload */ -template auto simplify_symbol(std::vector const *) { +template auto simplify_symbol(std::vector const *vec) { auto const name_val = simplify_symbol(static_cast(nullptr)); - return "std::vector<" + name_val + ">"; + std::string metadata{""}; + if (vec) { + metadata += "{.size=" + std::to_string(vec->size()) + "}"; + } + return "std::vector<" + name_val + ">" + metadata; } /** @overload */ inline auto simplify_symbol(std::vector const *vec) { auto value_type_name = std::string("ScriptInterface::Variant"); + std::string metadata{""}; if (vec) { std::set types = {}; for (auto const &v : *vec) { types.insert(simplify_symbol_variant(v)); } value_type_name += "{" + boost::algorithm::join(types, ", ") + "}"; + metadata += "{.size=" + std::to_string(vec->size()) + "}"; } - return "std::vector<" + value_type_name + ">"; + return "std::vector<" + value_type_name + ">" + metadata; } /** @overload */ @@ -214,7 +220,7 @@ struct vector_conversion_visitor : boost::static_visitor> { throw boost::bad_get{}; } - Utils::Vector ret; + Utils::Vector ret{}; boost::transform(vv, ret.begin(), [](const Variant &v) { return get_value_helper{}(v); }); @@ -344,14 +350,18 @@ struct get_value_helper< * is a container. * @tparam T Which type the variant was supposed to convert to */ -template inline void handle_bad_get(Variant const &v) { +template +inline void handle_bad_get(Variant const &v, std::string const &name) { auto const container_name = demangle::simplify_symbol_variant(v); auto const containee_name = demangle::simplify_symbol_containee_variant(v); auto const expected_containee_name = demangle::simplify_symbol_containee(static_cast(nullptr)); auto const from_container = !containee_name.empty(); auto const to_container = !expected_containee_name.empty(); - auto const what = "Provided argument of type '" + container_name + "'"; + auto what = "Provided argument of type '" + container_name + "'"; + if (not name.empty()) { + what += " for parameter '" + name + "'"; + } try { throw; } catch (bad_get_nullptr const &) { @@ -369,6 +379,15 @@ template inline void handle_bad_get(Variant const &v) { } } +template T get_value(Variant const &v, std::string const &name) { + try { + return detail::get_value_helper{}(v); + } catch (...) { + detail::handle_bad_get(v, name); + throw; + } +} + } // namespace detail /** @@ -381,27 +400,20 @@ template inline void handle_bad_get(Variant const &v) { * to a requested type. */ template T get_value(Variant const &v) { - try { - return detail::get_value_helper{}(v); - } catch (...) { - detail::handle_bad_get(v); - throw; - } + return detail::get_value(v, ""); } /** * @brief Get a value from a VariantMap by name, or throw * if it does not exist or is not convertible to * the target type. - * */ template T get_value(VariantMap const &vals, std::string const &name) { - try { - return get_value(vals.at(name)); - } catch (std::out_of_range const &) { + if (vals.count(name) == 0ul) { throw Exception("Parameter '" + name + "' is missing."); } + return detail::get_value(vals.at(name), name); } /** diff --git a/src/script_interface/initialize.cpp b/src/script_interface/initialize.cpp index a114aae664e..8ddda473d59 100644 --- a/src/script_interface/initialize.cpp +++ b/src/script_interface/initialize.cpp @@ -45,6 +45,7 @@ #include "reaction_methods/initialize.hpp" #include "shapes/initialize.hpp" #include "system/initialize.hpp" +#include "thermostat/initialize.hpp" #include "walberla/initialize.hpp" namespace ScriptInterface { @@ -71,6 +72,7 @@ void initialize(Utils::Factory *f) { Profiler::initialize(f); Shapes::initialize(f); System::initialize(f); + Thermostat::initialize(f); ReactionMethods::initialize(f); #ifdef H5MD Writer::initialize(f); diff --git a/src/script_interface/interactions/BondedInteraction.hpp b/src/script_interface/interactions/BondedInteraction.hpp index fa22352491e..9843ed82ff5 100644 --- a/src/script_interface/interactions/BondedInteraction.hpp +++ b/src/script_interface/interactions/BondedInteraction.hpp @@ -17,15 +17,14 @@ * along with this program. If not, see . */ +#pragma once + /** @file * The ScriptInterface counterparts of the bonded interactions parameters * structs from the core are defined here. * */ -#ifndef SCRIPT_INTERFACE_INTERACTIONS_BONDED_INTERACTION_HPP -#define SCRIPT_INTERFACE_INTERACTIONS_BONDED_INTERACTION_HPP - #include "core/bonded_interactions/bonded_interaction_data.hpp" #include "core/immersed_boundaries.hpp" #include "core/thermostat.hpp" @@ -114,7 +113,7 @@ class BondedInteraction : public AutoParameters { virtual void construct_bond(VariantMap const ¶ms) = 0; public: - bool operator==(BondedInteraction const &other) { + bool operator==(BondedInteraction const &other) const { return m_bonded_ia == other.m_bonded_ia; } @@ -139,7 +138,6 @@ class BondedInteraction : public AutoParameters { }; template class BondedInteractionImpl : public BondedInteraction { - public: using CoreBondedInteraction = CoreIA; CoreBondedInteraction &get_struct() { @@ -411,22 +409,9 @@ class ThermalizedBond : public BondedInteractionImpl<::ThermalizedBond> { [this]() { return get_struct().gamma_distance; }}, {"r_cut", AutoParameter::read_only, [this]() { return get_struct().r_cut; }}, - {"seed", AutoParameter::read_only, - []() { return static_cast(thermalized_bond.rng_seed()); }}, }); } - Variant do_call_method(std::string const &name, - VariantMap const ¶ms) override { - if (name == "get_rng_state") { - auto const state = ::thermalized_bond.rng_counter(); - // check it's safe to forward the current state as an integer - assert(state < static_cast(std::numeric_limits::max())); - return static_cast(state); - } - return BondedInteraction::do_call_method(name, params); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>( @@ -435,33 +420,6 @@ class ThermalizedBond : public BondedInteractionImpl<::ThermalizedBond> { get_value(params, "temp_distance"), get_value(params, "gamma_distance"), get_value(params, "r_cut"))); - - if (is_none(params.at("seed"))) { - if (::thermalized_bond.is_seed_required()) { - throw std::invalid_argument("A parameter 'seed' has to be given on " - "first activation of a thermalized bond"); - } - } else { - auto const seed = get_value(params, "seed"); - if (seed < 0) { - throw std::domain_error("Parameter 'seed' must be >= 0"); - } - ::thermalized_bond.rng_initialize(static_cast(seed)); - } - - // handle checkpointing - if (params.count("rng_state") and not is_none(params.at("rng_state"))) { - auto const state = get_value(params, "rng_state"); - assert(state >= 0); - ::thermalized_bond.set_rng_counter(static_cast(state)); - } - } - - std::set get_valid_parameters() const override { - auto names = - BondedInteractionImpl::get_valid_parameters(); - names.insert("rng_state"); - return names; } }; @@ -679,5 +637,3 @@ class VirtualBond : public BondedInteractionImpl<::VirtualBond> { } // namespace Interactions } // namespace ScriptInterface - -#endif diff --git a/src/script_interface/observables/Observable.hpp b/src/script_interface/observables/Observable.hpp index 238b5dcc258..4477963b1a8 100644 --- a/src/script_interface/observables/Observable.hpp +++ b/src/script_interface/observables/Observable.hpp @@ -34,7 +34,7 @@ namespace ScriptInterface { namespace Observables { -/** Base class for script interfaces to core %Observables classes */ +/** Base class for script interfaces to core observables classes */ class Observable : public ObjectHandle { public: virtual std::shared_ptr<::Observables::Observable> observable() const = 0; diff --git a/src/script_interface/observables/initialize.cpp b/src/script_interface/observables/initialize.cpp index 91203492156..ab5ea5e0530 100644 --- a/src/script_interface/observables/initialize.cpp +++ b/src/script_interface/observables/initialize.cpp @@ -57,7 +57,7 @@ namespace ScriptInterface { namespace Observables { -/** @name %Observables registration +/** @name Observables registration * Convenience macro functions to automatize the registration of observable * interfaces via a factory. */ diff --git a/src/script_interface/system/System.cpp b/src/script_interface/system/System.cpp index 1a8f1a45225..a6ffb25e8e1 100644 --- a/src/script_interface/system/System.cpp +++ b/src/script_interface/system/System.cpp @@ -45,6 +45,7 @@ #include "script_interface/integrators/IntegratorHandle.hpp" #include "script_interface/lees_edwards/LeesEdwards.hpp" #include "script_interface/magnetostatics/Container.hpp" +#include "script_interface/thermostat/thermostat.hpp" #include #include @@ -72,6 +73,7 @@ struct System::Leaves { Leaves() = default; std::shared_ptr cell_system; std::shared_ptr integrator; + std::shared_ptr thermostat; std::shared_ptr analysis; std::shared_ptr comfixed; std::shared_ptr galilei; @@ -143,6 +145,7 @@ System::System() : m_instance{}, m_leaves{std::make_shared()} { }); add_parameter("cell_system", &Leaves::cell_system); add_parameter("integrator", &Leaves::integrator); + add_parameter("thermostat", &Leaves::thermostat); add_parameter("analysis", &Leaves::analysis); add_parameter("comfixed", &Leaves::comfixed); add_parameter("galilei", &Leaves::galilei); diff --git a/src/script_interface/tests/get_value_test.cpp b/src/script_interface/tests/get_value_test.cpp index 05cc8360025..00baf0cf18f 100644 --- a/src/script_interface/tests/get_value_test.cpp +++ b/src/script_interface/tests/get_value_test.cpp @@ -208,7 +208,7 @@ BOOST_AUTO_TEST_CASE(check_exceptions) { auto const int_variant = Variant{1.5}; auto const vec_variant = Variant{std::vector{{so_obj}}}; auto const vec_variant_pattern = "std::vector<" + variant_sip_name + ">"; - auto const what = msg_prefix + "'" + vec_variant_pattern + "'"; + auto const what = msg_prefix + "'" + vec_variant_pattern + "\\{.size=1\\}'"; auto const predicate_nullptr = exception_message_predicate( what + " contains a value that is a null pointer"); auto const predicate_conversion_containee = exception_message_predicate( diff --git a/src/script_interface/thermostat/CMakeLists.txt b/src/script_interface/thermostat/CMakeLists.txt new file mode 100644 index 00000000000..f1f07590b39 --- /dev/null +++ b/src/script_interface/thermostat/CMakeLists.txt @@ -0,0 +1,20 @@ +# +# 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 . +# + +target_sources(espresso_script_interface PRIVATE initialize.cpp) diff --git a/src/script_interface/thermostat/initialize.cpp b/src/script_interface/thermostat/initialize.cpp new file mode 100644 index 00000000000..b7908f13451 --- /dev/null +++ b/src/script_interface/thermostat/initialize.cpp @@ -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 . + */ + +#include "initialize.hpp" + +#include "thermostat.hpp" + +namespace ScriptInterface { +namespace Thermostat { + +void initialize(Utils::Factory *om) { + om->register_new("Thermostat::Thermostat"); + om->register_new("Thermostat::Langevin"); + om->register_new("Thermostat::Brownian"); +#ifdef NPT + om->register_new("Thermostat::IsotropicNpt"); +#endif +#ifdef WALBERLA + om->register_new("Thermostat::LB"); +#endif +#ifdef DPD + om->register_new("Thermostat::DPD"); +#endif +#ifdef STOKESIAN_DYNAMICS + om->register_new("Thermostat::Stokesian"); +#endif + om->register_new("Thermostat::ThermalizedBond"); +} + +} // namespace Thermostat +} // namespace ScriptInterface diff --git a/src/core/global_ghost_flags.hpp b/src/script_interface/thermostat/initialize.hpp similarity index 71% rename from src/core/global_ghost_flags.hpp rename to src/script_interface/thermostat/initialize.hpp index 38d0dc58683..a86be5e1b04 100644 --- a/src/core/global_ghost_flags.hpp +++ b/src/script_interface/thermostat/initialize.hpp @@ -1,7 +1,5 @@ /* - * 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 + * Copyright (C) 2023 The ESPResSo project * * This file is part of ESPResSo. * @@ -21,4 +19,14 @@ #pragma once -unsigned global_ghost_flags(); +#include + +#include + +namespace ScriptInterface { +namespace Thermostat { + +void initialize(Utils::Factory *om); + +} // namespace Thermostat +} // namespace ScriptInterface diff --git a/src/script_interface/thermostat/thermostat.hpp b/src/script_interface/thermostat/thermostat.hpp new file mode 100644 index 00000000000..c2494362d8e --- /dev/null +++ b/src/script_interface/thermostat/thermostat.hpp @@ -0,0 +1,731 @@ +/* + * 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 "core/PropagationMode.hpp" +#include "core/bonded_interactions/bonded_interaction_data.hpp" +#include "core/thermostat.hpp" + +#include +#include +#include +#ifdef WALBERLA +#include +#endif + +#include +#include +#include +#include +#include + +namespace ScriptInterface { +namespace Thermostat { + +template +class Interface : public AutoParameters, System::Leaf> { + using BaseClass = AutoParameters, System::Leaf>; + +public: + using CoreThermostat = CoreClass; + using BaseClass::do_set_parameter; + using BaseClass::get_parameter; + using System::Leaf::bind_system; + +protected: + using BaseClass::add_parameters; + using BaseClass::context; + using BaseClass::get_parameter_insertion_order; + using System::Leaf::m_system; + + bool is_active = false; + std::shared_ptr m_handle; + /** @brief Basic lock mechanism that follows RAII. */ + std::weak_ptr m_edit_lock; + + void check_lock() { + if (m_edit_lock.expired()) { + throw AutoParameter::WriteError{}; + } + } + + void on_bind_system(::System::System &system) override { + get_member_handle(*system.thermostat) = m_handle; + system.on_thermostat_param_change(); + is_active = true; + } + + void on_detach_system(::System::System &system) override { + get_member_handle(*system.thermostat).reset(); + system.on_thermostat_param_change(); + is_active = false; + } + + void sanity_checks_positive(double value, std::string const &name) const { + if (value < 0.) { + throw std::domain_error("Parameter '" + name + "' cannot be negative"); + } + } + + void sanity_checks_positive(Utils::Vector3d const &value, + std::string const &name) const { + if (not(value >= Utils::Vector3d::broadcast(0.))) { + throw std::domain_error("Parameter '" + name + "' cannot be negative"); + } + } + + virtual bool invalid_rng_state(VariantMap const ¶ms) const { + return (not params.count("seed") or is_none(params.at("seed"))) and + is_seed_required(); + } + +private: + virtual std::shared_ptr & + get_member_handle(::Thermostat::Thermostat &thermostat) = 0; + + void set_new_parameters(VariantMap const ¶ms) { + if (params.count("__check_rng_state") and invalid_rng_state(params)) { + context()->parallel_try_catch([]() { + throw std::invalid_argument("Parameter 'seed' is needed on first " + "activation of the thermostat"); + }); + } + for (auto const &key : get_parameter_insertion_order()) { + if (params.count(key)) { + auto const &v = params.at(key); + if (key == "is_active") { + is_active = get_value(v); + } else { + do_set_parameter(key.c_str(), v); + } + } + } + } + +protected: + template + auto make_autoparameter(T CoreThermostat::*member, char const *name) { + return AutoParameter{ + name, + [this, member, name = std::string(name)](Variant const &v) { + check_lock(); + auto const value = get_value(v); + context()->parallel_try_catch( + [&]() { sanity_checks_positive(value, name); }); + m_handle.get()->*member = std::move(value); + }, + [this, member]() { return m_handle.get()->*member; }}; + } + + template + auto make_autogamma(T CoreThermostat::*member, char const *name) { + return AutoParameter{ + name, + [this, member, name = std::string(name)](Variant const &v) { + check_lock(); + if (is_none(v)) { + return; + } +#ifdef PARTICLE_ANISOTROPY + static_assert(std::is_same_v); + T gamma{}; + if (is_type(v) or is_type(v)) { + gamma = T::broadcast(get_value(v)); + } else { + gamma = get_value(v); + } +#else + auto const gamma = get_value(v); +#endif // PARTICLE_ANISOTROPY + context()->parallel_try_catch( + [&]() { sanity_checks_positive(gamma, name); }); + m_handle.get()->*member = gamma; + }, + [this, member]() { + auto constexpr gamma_null = ::Thermostat::gamma_null; + auto const gamma = m_handle.get()->*member; + return (gamma >= gamma_null) ? Variant{gamma} : Variant{None{}}; + }}; + } + + virtual VariantMap extend_parameters(VariantMap const ¶meters) const { + auto params = parameters; + if (not is_seed_required()) { + for (auto key : {std::string("seed"), std::string("philox_counter")}) { + if (params.count(key) == 0ul) { + params[key] = get_parameter(key); + } + } + } + return params; + } + + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "override_philox_counter") { + // only call this method if you know what you are doing + set_rng_counter(params.at("counter")); + return {}; + } + return {}; + } + +public: + Interface() { + add_parameters({ + {"seed", + [this](Variant const &v) { + check_lock(); + context()->parallel_try_catch([&]() { + if (not is_none(v)) + set_rng_seed(v); + }); + }, + [this]() { + return m_handle->is_seed_required() ? Variant{None{}} + : Variant{get_rng_seed()}; + }}, + {"philox_counter", + [this](Variant const &v) { + check_lock(); + context()->parallel_try_catch([&]() { + if (not is_none(v)) + set_rng_counter(v); + }); + }, + [this]() { return get_rng_counter(); }}, + {"is_active", AutoParameter::read_only, [this]() { return is_active; }}, + }); + } + + virtual std::optional extract_kT(VariantMap const ¶ms) const { + if (params.count("kT")) { + auto const value = get_value(params, "kT"); + sanity_checks_positive(value, "kT"); + return value; + } + return {std::nullopt}; + } + + auto release_lock() { + auto lock = std::make_shared(false); + m_edit_lock = lock; + return lock; + } + + auto is_activated() const { return is_active; } + + virtual bool is_seed_required() const { return m_handle->is_seed_required(); } + + auto get_rng_seed() const { + auto const seed = m_handle->rng_seed(); + assert(seed <= static_cast(std::numeric_limits::max())); + return static_cast(seed); + } + + auto get_rng_counter() const { + auto const counter = m_handle->rng_counter(); + assert(counter <= static_cast(std::numeric_limits::max())); + return static_cast(counter); + } + + void set_rng_seed(Variant const &value) { + auto const seed = get_value(value); + if (seed < 0) { + throw std::domain_error("Parameter 'seed' must be a positive integer"); + } + assert(static_cast(seed) <= + static_cast(std::numeric_limits::max())); + m_handle->rng_initialize(static_cast(seed)); + } + + void set_rng_counter(Variant const &value) { + auto const counter = get_value(value); + assert(counter >= 0); + assert(static_cast(counter) <= + std::numeric_limits::max()); + m_handle->set_rng_counter(static_cast(counter)); + } + + void do_construct(VariantMap const ¶ms) override { + m_handle = std::make_shared(); + if (not params.empty()) { + auto const read_write_lock = release_lock(); + set_new_parameters(params); + } + } + + void update_and_bind(VariantMap const ¶ms, bool was_active, + std::shared_ptr<::System::System> system) { + auto const old_handle = m_handle; + auto new_params = extend_parameters(params); + new_params["__global_kT"] = system->thermostat->kT; + new_params["__check_rng_state"] = true; + try { + m_handle = std::make_shared(); + set_new_parameters(new_params); + bind_system(system); + } catch (...) { + assert(not is_active); + m_handle = old_handle; + if (was_active) { + bind_system(system); + } + throw; + } + } + + virtual ::ThermostatFlags get_thermo_flag() const = 0; +}; + +class Langevin : public Interface<::LangevinThermostat> { + std::shared_ptr & + get_member_handle(::Thermostat::Thermostat &thermostat) override { + return thermostat.langevin; + } + +public: + Langevin() { + add_parameters({ + make_autogamma(&CoreThermostat::gamma, "gamma"), +#ifdef ROTATION + make_autogamma(&CoreThermostat::gamma_rotation, "gamma_rotation"), +#endif + }); + } + + ::ThermostatFlags get_thermo_flag() const final { return THERMO_LANGEVIN; } + +protected: + VariantMap extend_parameters(VariantMap const ¶meters) const override { + auto params = + Interface<::LangevinThermostat>::extend_parameters(parameters); +#ifdef ROTATION + // If gamma_rotation is not set explicitly, use the translational one. + if (params.count("gamma_rotation") == 0ul and params.count("gamma")) { + params["gamma_rotation"] = params.at("gamma"); + } +#endif // ROTATION + return params; + } +}; + +class Brownian : public Interface<::BrownianThermostat> { + std::shared_ptr & + get_member_handle(::Thermostat::Thermostat &thermostat) override { + return thermostat.brownian; + } + +public: + Brownian() { + add_parameters({ + make_autogamma(&CoreThermostat::gamma, "gamma"), +#ifdef ROTATION + make_autogamma(&CoreThermostat::gamma_rotation, "gamma_rotation"), +#endif + }); + } + + ::ThermostatFlags get_thermo_flag() const final { return THERMO_BROWNIAN; } + +protected: + VariantMap extend_parameters(VariantMap const ¶meters) const override { + auto params = + Interface<::BrownianThermostat>::extend_parameters(parameters); +#ifdef ROTATION + // If gamma_rotation is not set explicitly, use the translational one. + if (params.count("gamma_rotation") == 0ul and params.count("gamma")) { + params["gamma_rotation"] = params.at("gamma"); + } +#endif // ROTATION + return params; + } +}; + +#ifdef NPT +class IsotropicNpt : public Interface<::IsotropicNptThermostat> { + std::shared_ptr & + get_member_handle(::Thermostat::Thermostat &thermostat) override { + return thermostat.npt_iso; + } + +public: + IsotropicNpt() { + add_parameters({ + make_autoparameter(&CoreThermostat::gamma0, "gamma0"), + make_autoparameter(&CoreThermostat::gammav, "gammav"), + }); + } + + ::ThermostatFlags get_thermo_flag() const final { return THERMO_NPT_ISO; } +}; +#endif // NPT + +#ifdef WALBERLA +class LBThermostat : public Interface<::LBThermostat> { + std::shared_ptr & + get_member_handle(::Thermostat::Thermostat &thermostat) override { + return thermostat.lb; + } + +public: + LBThermostat() { + add_parameters({ + {"gamma", + [this](Variant const &v) { + check_lock(); + if (is_none(v)) { + return; + } + auto const gamma = get_value(v); + context()->parallel_try_catch( + [&]() { sanity_checks_positive(gamma, "gamma"); }); + m_handle->gamma = gamma; + }, + [this]() { + auto const gamma = m_handle->gamma; + return (gamma >= 0.) ? Variant{gamma} : Variant{None{}}; + }}, + }); + } + + ::ThermostatFlags get_thermo_flag() const final { return THERMO_LB; } + + std::optional extract_kT(VariantMap const ¶ms) const override { + auto const obj = + get_value>(params, "LB_fluid"); + auto const value = get_value(obj->get_parameter("kT")); + sanity_checks_positive(value, "kT"); + return value; + } + +protected: + bool invalid_rng_state(VariantMap const ¶ms) const override { + return (not params.count("seed") or is_none(params.at("seed"))) and + params.count("__global_kT") and is_seed_required() and + get_value(params, "__global_kT") != 0.; + } +}; +#endif // WALBERLA + +#ifdef DPD +class DPDThermostat : public Interface<::DPDThermostat> { + std::shared_ptr & + get_member_handle(::Thermostat::Thermostat &thermostat) override { + return thermostat.dpd; + } + +public: + ::ThermostatFlags get_thermo_flag() const final { return THERMO_DPD; } +}; +#endif + +#ifdef STOKESIAN_DYNAMICS +class Stokesian : public Interface<::StokesianThermostat> { + std::shared_ptr & + get_member_handle(::Thermostat::Thermostat &thermostat) override { + return thermostat.stokesian; + } + +public: + ::ThermostatFlags get_thermo_flag() const final { return THERMO_SD; } +}; +#endif + +class ThermalizedBond : public Interface<::ThermalizedBondThermostat> { + std::shared_ptr & + get_member_handle(::Thermostat::Thermostat &thermostat) override { + return thermostat.thermalized_bond; + } + +public: + ::ThermostatFlags get_thermo_flag() const final { return THERMO_BOND; } +}; + +class Thermostat : public AutoParameters { + std::shared_ptr langevin; + std::shared_ptr brownian; +#ifdef NPT + std::shared_ptr npt_iso; +#endif +#ifdef WALBERLA + std::shared_ptr lb; +#endif +#ifdef DPD + std::shared_ptr dpd; +#endif +#ifdef STOKESIAN_DYNAMICS + std::shared_ptr stokesian; +#endif + std::shared_ptr thermalized_bond; + std::shared_ptr<::Thermostat::Thermostat> m_handle; + std::unique_ptr m_params; + + template void apply(Fun fun) { + fun(*langevin); + fun(*brownian); +#ifdef NPT + fun(*npt_iso); +#endif +#ifdef WALBERLA + fun(*lb); +#endif +#ifdef DPD + fun(*dpd); +#endif +#ifdef STOKESIAN_DYNAMICS + fun(*stokesian); +#endif + fun(*thermalized_bond); + } + +protected: + template + auto make_autoparameter(T Thermostat::*member, char const *name) { + return AutoParameter{ + name, + [this, member, name = std::string(name)](Variant const &v) { + auto &thermostat = this->*member; + if (thermostat) { + throw WriteError{name}; + } + thermostat = get_value(v); + }, + [this, member]() { return this->*member; }}; + } + + template + void setup_thermostat(std::shared_ptr &thermostat, + VariantMap const ¶ms) { + auto const original_kT = m_handle->kT; + std::optional new_kT; + context()->parallel_try_catch( + [&]() { new_kT = thermostat->extract_kT(params); }); + auto const thermo_flag = thermostat->get_thermo_flag(); + if (new_kT) { + context()->parallel_try_catch( + [&]() { update_global_kT(original_kT, *new_kT, thermo_flag); }); + } + auto const was_active = thermostat->is_activated(); + turn_thermostat_off(*thermostat); + auto read_write_lock = thermostat->release_lock(); + context()->parallel_try_catch([&]() { + try { + if (new_kT) { + m_handle->kT = *new_kT; + } + thermostat->update_and_bind(params, was_active, m_system.lock()); + m_handle->thermo_switch |= thermo_flag; + } catch (...) { + auto success = false; + try { + m_handle->kT = original_kT; + if (was_active) { + m_handle->thermo_switch |= thermo_flag; + thermostat->bind_system(m_system.lock()); + } + success = true; + throw success; + } catch (...) { + assert(success && + "An exception occurred when setting up the thermostat. " + "An exception also occurred when attempting to restore the " + "original thermostat. The system is now in an invalid state."); + } + throw; + } + }); + } + + template void turn_thermostat_off(T &thermostat) { + auto const thermo_flag = thermostat.get_thermo_flag(); + if (m_handle->thermo_switch & thermo_flag) { + thermostat.detach_system(); + m_handle->thermo_switch &= ~thermo_flag; + if (m_handle->thermo_switch == 0) { + m_handle->kT = -1.; + } + } + } + + void update_global_kT(double old_kT, double new_kT, int thermo_flag) { + if (new_kT >= 0.) { + auto const same_kT = ::Thermostat::are_kT_equal(old_kT, new_kT); + auto const thermo_switch = m_handle->thermo_switch; + if (thermo_switch != THERMO_OFF and thermo_switch != thermo_flag and + thermo_switch != THERMO_BOND and not same_kT and old_kT >= 0.) { + throw std::runtime_error( + "Cannot set parameter 'kT' to " + std::to_string(new_kT) + + ": there are currently active thermostats with kT=" + + std::to_string(old_kT)); + } + get_system().check_kT(new_kT); + if (not same_kT) { + m_handle->kT = new_kT; + get_system().on_temperature_change(); + } + } + } + +public: + Thermostat() { + add_parameters({ + {"kT", AutoParameter::read_only, + [this]() { + return (m_handle->kT >= 0.) ? Variant{m_handle->kT} + : Variant{None{}}; + }}, + make_autoparameter(&Thermostat::langevin, "langevin"), + make_autoparameter(&Thermostat::brownian, "brownian"), +#ifdef NPT + make_autoparameter(&Thermostat::npt_iso, "npt_iso"), +#endif +#ifdef WALBERLA + make_autoparameter(&Thermostat::lb, "lb"), +#endif +#ifdef DPD + make_autoparameter(&Thermostat::dpd, "dpd"), +#endif +#ifdef STOKESIAN_DYNAMICS + make_autoparameter(&Thermostat::stokesian, "stokesian"), +#endif + make_autoparameter(&Thermostat::thermalized_bond, "thermalized_bond"), + }); + } + + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "set_langevin") { + setup_thermostat(langevin, params); + return {}; + } + if (name == "set_brownian") { + setup_thermostat(brownian, params); + return {}; + } +#ifdef NPT + if (name == "set_npt") { + setup_thermostat(npt_iso, params); + return {}; + } +#endif // NPT +#ifdef WALBERLA + if (name == "set_lb") { + setup_thermostat(lb, params); + return {}; + } +#endif // WALBERLA +#ifdef DPD + if (name == "set_dpd") { + setup_thermostat(dpd, params); + return {}; + } +#endif // DPD +#ifdef STOKESIAN_DYNAMICS + if (name == "set_stokesian") { + setup_thermostat(stokesian, params); + return {}; + } +#endif // STOKESIAN_DYNAMICS + if (name == "set_thermalized_bond") { + setup_thermostat(thermalized_bond, params); + return {}; + } + if (name == "turn_off") { + apply([this](auto &thermostat) { turn_thermostat_off(thermostat); }); + assert(m_handle->thermo_switch == THERMO_OFF); + get_system().on_temperature_change(); + return {}; + } + return {}; + } + + void do_construct(VariantMap const ¶ms) override { + m_params = std::make_unique(params); + } + + void on_bind_system(::System::System &system) override { + assert(m_params != nullptr); + m_handle = system.thermostat; + auto const ¶ms = *m_params; + if (not params.empty()) { + reload_checkpointed_thermostats(params); + m_params.reset(); + return; + } + m_params.reset(); + if (not context()->is_head_node()) { + return; + } + make_default_constructed_thermostats(); + } + +private: + /** + * @brief Reload thermostats from checkpointed data. + */ + void reload_checkpointed_thermostats(VariantMap const ¶ms) { + for (auto const &key : get_parameter_insertion_order()) { + if (key != "kT") { + auto const &v = params.at(key); + do_set_parameter(key.c_str(), v); + } + } + if (not is_none(params.at("kT"))) { + m_handle->kT = get_value(params, "kT"); + } + apply([this](auto &thermostat) { + if (get_value(thermostat.get_parameter("is_active"))) { + thermostat.bind_system(m_system.lock()); + m_handle->thermo_switch |= thermostat.get_thermo_flag(); + } + }); + get_system().on_thermostat_param_change(); + } + + /** + * @brief Instantiate default-contructed thermostats. + * Can only be run on the head node! + */ + void make_default_constructed_thermostats() { + assert(context()->is_head_node()); + auto const make_thermostat = [this](char const *name, char const *so_name) { + set_parameter(name, Variant{context()->make_shared(so_name, {})}); + }; + make_thermostat("langevin", "Thermostat::Langevin"); + make_thermostat("brownian", "Thermostat::Brownian"); +#ifdef NPT + make_thermostat("npt_iso", "Thermostat::IsotropicNpt"); +#endif +#ifdef WALBERLA + make_thermostat("lb", "Thermostat::LB"); +#endif +#ifdef DPD + make_thermostat("dpd", "Thermostat::DPD"); +#endif +#ifdef STOKESIAN_DYNAMICS + make_thermostat("stokesian", "Thermostat::Stokesian"); +#endif + make_thermostat("thermalized_bond", "Thermostat::ThermalizedBond"); + } +}; + +} // namespace Thermostat +} // namespace ScriptInterface diff --git a/src/script_interface/walberla/LatticeWalberla.hpp b/src/script_interface/walberla/LatticeWalberla.hpp index 6c9c99a9de8..999513f0a7b 100644 --- a/src/script_interface/walberla/LatticeWalberla.hpp +++ b/src/script_interface/walberla/LatticeWalberla.hpp @@ -57,7 +57,7 @@ class LatticeWalberla : public AutoParameters { } void do_construct(VariantMap const &args) override { - auto const &box_geo = *System::get_system().box_geo; + auto const &box_geo = *::System::get_system().box_geo; m_agrid = get_value(args, "agrid"); m_box_l = get_value_or(args, "_box_l", box_geo.length()); auto const n_ghost_layers = get_value(args, "n_ghost_layers"); diff --git a/src/shapes/include/shapes/Shape.hpp b/src/shapes/include/shapes/Shape.hpp index 2068150bc44..cdc9e1ca2fd 100644 --- a/src/shapes/include/shapes/Shape.hpp +++ b/src/shapes/include/shapes/Shape.hpp @@ -55,8 +55,8 @@ class Shape { /** * @brief Rasterize a shape on a regular grid. * @param grid_size Number of grid points in every direction. - * @param grid_spacing %Lattice distance. - * @param grid_offset %Lattice offset. + * @param grid_spacing Lattice distance. + * @param grid_offset Lattice offset. * @return Flattened 3D matrix with 1's inside the shape and 0's outside. */ std::vector rasterize(Utils::Vector3i const &grid_size, diff --git a/src/utils/include/utils/Cache.hpp b/src/utils/include/utils/Cache.hpp index 18bffbf5347..25f84b62394 100644 --- a/src/utils/include/utils/Cache.hpp +++ b/src/utils/include/utils/Cache.hpp @@ -166,7 +166,7 @@ template class Cache { /** @brief Get a value. * - * The value is owned by the Cache and can not be modified. + * The value is owned by the cache and can not be modified. * Pointers into the cache can be invalidated at any point * and should not be stored beyond the calling function. */ diff --git a/src/utils/include/utils/Counter.hpp b/src/utils/include/utils/Counter.hpp index 0501eefa24e..dca2726b897 100644 --- a/src/utils/include/utils/Counter.hpp +++ b/src/utils/include/utils/Counter.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 UTILS_COUNTER_HPP -#define UTILS_COUNTER_HPP + +#pragma once #include @@ -44,4 +44,3 @@ template class Counter { T initial_value() const { return m_initial; } }; } // namespace Utils -#endif // UTILS_COUNTER_HPP diff --git a/src/utils/include/utils/linear_interpolation.hpp b/src/utils/include/utils/linear_interpolation.hpp index 32ef3c12ce8..63328bccdbb 100644 --- a/src/utils/include/utils/linear_interpolation.hpp +++ b/src/utils/include/utils/linear_interpolation.hpp @@ -24,7 +24,7 @@ namespace Utils { /** Linear interpolation between two data points. * @param[in] table Tabulated values, equally-spaced along the x-axis - * @param[in] hi %Distance on the x-axis between tabulated values + * @param[in] hi Distance on the x-axis between tabulated values * @param[in] offset Position on the x-axis of the first tabulated value * @param[in] x Position on the x-axis at which to interpolate the value * @return Interpolated value on the y-axis at @p x. diff --git a/src/utils/include/utils/math/coordinate_transformation.hpp b/src/utils/include/utils/math/coordinate_transformation.hpp index e15debc24bc..c5808511be1 100644 --- a/src/utils/include/utils/math/coordinate_transformation.hpp +++ b/src/utils/include/utils/math/coordinate_transformation.hpp @@ -66,7 +66,7 @@ inline Vector3d basis_change(Vector3d const &b1, Vector3d const &b2, * The origins and z-axis of the coordinate systems co-incide. * The @f$ \phi = 0 @f$ direction corresponds to the x-axis in the * original coordinate system. - * @param pos %Vector to transform + * @param pos Vector to transform */ inline Vector3d transform_coordinate_cartesian_to_cylinder(Vector3d const &pos) { @@ -85,7 +85,7 @@ transform_coordinate_cartesian_to_cylinder(Vector3d const &pos) { * in Cartesian coordinates that will be used as the reference point * (i.e. such that @f$ \phi = 0 @f$), by default it is the x-axis. * - * @param pos %Vector to transform + * @param pos Vector to transform * @param axis Longitudinal axis of the cylindrical coordinates * @param orientation Reference point (in untransformed coordinates) for * which @f$ \phi = 0 @f$ @@ -105,7 +105,7 @@ inline Vector3d transform_coordinate_cartesian_to_cylinder( * The origins and z-axis of the coordinate systems co-incide. * The @f$ \phi = 0 @f$ direction corresponds to the x-axis in the * transformed coordinate system. - * @param pos %Vector to transform + * @param pos Vector to transform */ inline Vector3d transform_coordinate_cylinder_to_cartesian(Vector3d const &pos) { @@ -125,7 +125,7 @@ transform_coordinate_cylinder_to_cartesian(Vector3d const &pos) { * in Cartesian coordinates that will be used as the reference point * (i.e. such that @f$ \phi = 0 @f$). * - * @param pos %Vector to transform + * @param pos Vector to transform * @param axis Longitudinal axis of the cylindrical coordinates * @param orientation Reference point (in Cartesian coordinates) for * which @f$ \phi = 0 @f$ @@ -142,7 +142,7 @@ inline Vector3d transform_coordinate_cylinder_to_cartesian( /** * @brief Vector transformation from Cartesian to cylindrical coordinates. - * @param vec %Vector to transform + * @param vec Vector to transform * @param axis Longitudinal axis of the cylindrical coordinates * @param pos Origin of the vector */ diff --git a/src/utils/include/utils/math/vec_rotate.hpp b/src/utils/include/utils/math/vec_rotate.hpp index b46140f313f..2c42fb005ed 100644 --- a/src/utils/include/utils/math/vec_rotate.hpp +++ b/src/utils/include/utils/math/vec_rotate.hpp @@ -34,7 +34,7 @@ namespace Utils { * * @param axis The axis to rotate about * @param angle Angle to rotate - * @param vector %Vector to act on + * @param vector Vector to act on * @return Rotated vector */ inline Vector3d vec_rotate(const Vector3d &axis, double angle, diff --git a/src/utils/include/utils/serialization/memcpy_archive.hpp b/src/utils/include/utils/serialization/memcpy_archive.hpp index 3622bca1ba1..04e0ba10aef 100644 --- a/src/utils/include/utils/serialization/memcpy_archive.hpp +++ b/src/utils/include/utils/serialization/memcpy_archive.hpp @@ -160,7 +160,7 @@ template class BasicMemcpyArchive { * e.g. that serialize to the same number of bytes independent of * the state of the object. This can either be automatically detected * for types that are trivially copyable, or by explicitly assured - * by specializing @c is_statically_serializable to std::true_type. + * by specializing @ref is_statically_serializable to @c std::true_type. */ class MemcpyIArchive : public detail::BasicMemcpyArchive { private: diff --git a/src/walberla_bridge/include/walberla_bridge/electrokinetics/reactions/EKReactionBase.hpp b/src/walberla_bridge/include/walberla_bridge/electrokinetics/reactions/EKReactionBase.hpp index bdfb8c1eb52..049bd3226da 100644 --- a/src/walberla_bridge/include/walberla_bridge/electrokinetics/reactions/EKReactionBase.hpp +++ b/src/walberla_bridge/include/walberla_bridge/electrokinetics/reactions/EKReactionBase.hpp @@ -51,7 +51,9 @@ class EKReactionBase { return m_coefficient; } [[nodiscard]] auto get_lattice() const noexcept { return m_lattice; } - [[nodiscard]] auto get_reactants() const noexcept { return m_reactants; } + [[nodiscard]] auto const &get_reactants() const noexcept { + return m_reactants; + } virtual void perform_reaction() = 0; }; diff --git a/testsuite/python/CMakeLists.txt b/testsuite/python/CMakeLists.txt index c89f315f0a8..23763096f20 100644 --- a/testsuite/python/CMakeLists.txt +++ b/testsuite/python/CMakeLists.txt @@ -397,7 +397,7 @@ 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_langevin.py MAX_NUM_PROC 2) 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) diff --git a/testsuite/python/box_geometry.py b/testsuite/python/box_geometry.py index c27c4880d03..ec950e3b59f 100644 --- a/testsuite/python/box_geometry.py +++ b/testsuite/python/box_geometry.py @@ -36,7 +36,7 @@ def test_box_length_interface(self): # the box length should not be updated np.testing.assert_equal(self.box_l, np.copy(self.system.box_l)) - with self.assertRaisesRegex(ValueError, "Attribute 'box_l' must be a list of 3 floats"): + with self.assertRaisesRegex(RuntimeError, "Provided argument of type 'std::vector{.size=2}' is not convertible to 'Utils::Vector'"): self.system.box_l = self.box_l[:2] def test_periodicity(self): @@ -50,7 +50,7 @@ def test_periodicity(self): default_periodicity = (True, True, True) self.system.periodicity = default_periodicity - with self.assertRaisesRegex(ValueError, "Attribute 'periodicity' must be a list of 3 bools"): + with self.assertRaisesRegex(RuntimeError, "Provided argument of type 'std::vector{.size=2}' is not convertible to 'Utils::Vector'"): self.system.periodicity = (True, True) # the periodicity should not be updated diff --git a/testsuite/python/coulomb_interface.py b/testsuite/python/coulomb_interface.py index b70012b1807..b2257fe49d8 100644 --- a/testsuite/python/coulomb_interface.py +++ b/testsuite/python/coulomb_interface.py @@ -253,7 +253,7 @@ def test_elc_p3m_exceptions(self): self.assertEqual( list(self.system.cell_system.node_grid), list(self.original_node_grid)) - with self.assertRaisesRegex(Exception, "while assigning system parameter 'box_l': ERROR: ELC gap size .+ larger than box length in z-direction"): + with self.assertRaisesRegex(Exception, "while setting parameter 'box_l': ERROR: ELC gap size .+ larger than box length in z-direction"): self.system.box_l = [10., 10., 2.5] self.system.box_l = [10., 10., 10.] self.system.electrostatics.solver = None diff --git a/testsuite/python/dpd.py b/testsuite/python/dpd.py index 64f9dfc003a..d69f4fe64a3 100644 --- a/testsuite/python/dpd.py +++ b/testsuite/python/dpd.py @@ -59,9 +59,12 @@ def reset_particles(): gamma = 1.5 # No seed should throw exception - with self.assertRaisesRegex(ValueError, "A seed has to be given as keyword argument on first activation of the thermostat"): + with self.assertRaisesRegex(ValueError, "Parameter 'seed' is needed on first activation of the thermostat"): system.thermostat.set_dpd(kT=kT) + self.assertIsNone(system.thermostat.kT) + self.assertFalse(system.thermostat.dpd.is_active) + system.thermostat.set_dpd(kT=kT, seed=41) system.non_bonded_inter[0, 0].dpd.set_params( weight_function=0, gamma=gamma, r_cut=1.5, @@ -71,14 +74,20 @@ def reset_particles(): # force p = reset_particles() system.integrator.run(0, recalc_forces=True) + self.assertEqual(system.thermostat.dpd.seed, 41) + self.assertEqual(system.thermostat.dpd.philox_counter, 0) force0 = np.copy(p.f) system.integrator.run(0, recalc_forces=True) + self.assertEqual(system.thermostat.dpd.seed, 41) + self.assertEqual(system.thermostat.dpd.philox_counter, 0) force1 = np.copy(p.f) np.testing.assert_almost_equal(force0, force1) # run(1) should give a different force p = reset_particles() system.integrator.run(1) + self.assertEqual(system.thermostat.dpd.seed, 41) + self.assertEqual(system.thermostat.dpd.philox_counter, 1) force2 = np.copy(p.f) self.assertTrue(np.all(np.not_equal(force1, force2))) @@ -87,9 +96,13 @@ def reset_particles(): # force3: dpd.rng_counter() = 1, dpd.rng_seed() = 42 p = reset_particles() system.integrator.run(0, recalc_forces=True) + self.assertEqual(system.thermostat.dpd.seed, 41) + self.assertEqual(system.thermostat.dpd.philox_counter, 1) force2 = np.copy(p.f) system.thermostat.set_dpd(kT=kT, seed=42) system.integrator.run(0, recalc_forces=True) + self.assertEqual(system.thermostat.dpd.seed, 42) + self.assertEqual(system.thermostat.dpd.philox_counter, 1) force3 = np.copy(p.f) self.assertTrue(np.all(np.not_equal(force2, force3))) @@ -97,6 +110,8 @@ def reset_particles(): p = reset_particles() system.thermostat.set_dpd(kT=kT, seed=42) system.integrator.run(1) + self.assertEqual(system.thermostat.dpd.seed, 42) + self.assertEqual(system.thermostat.dpd.philox_counter, 2) force4 = np.copy(p.f) self.assertTrue(np.all(np.not_equal(force3, force4))) @@ -106,8 +121,12 @@ def reset_particles(): reset_particles() system.thermostat.set_dpd(kT=kT, seed=41) system.integrator.run(1) + self.assertEqual(system.thermostat.dpd.seed, 41) + self.assertEqual(system.thermostat.dpd.philox_counter, 3) p = reset_particles() system.integrator.run(0, recalc_forces=True) + self.assertEqual(system.thermostat.dpd.seed, 41) + self.assertEqual(system.thermostat.dpd.philox_counter, 3) force5 = np.copy(p.f) self.assertTrue(np.all(np.not_equal(force4, force5))) diff --git a/testsuite/python/drude.py b/testsuite/python/drude.py index d432cd87ca4..72ba60832e6 100644 --- a/testsuite/python/drude.py +++ b/testsuite/python/drude.py @@ -155,6 +155,7 @@ def test(self): kT=temperature_com, gamma=gamma_com, seed=42) + system.thermostat.set_thermalized_bond(seed=123) p3m = espressomd.electrostatics.P3M(prefactor=coulomb_prefactor, accuracy=1e-4, mesh=3 * [18], cao=5) @@ -165,7 +166,7 @@ def test(self): thermalized_dist_bond = espressomd.interactions.ThermalizedBond( temp_com=temperature_com, gamma_com=gamma_com, r_cut=1.0, - temp_distance=temperature_drude, gamma_distance=gamma_drude, seed=123) + temp_distance=temperature_drude, gamma_distance=gamma_drude) harmonic_bond = espressomd.interactions.HarmonicBond( k=k_drude, r_0=0.0, r_cut=1.0) system.bonded_inter.add(thermalized_dist_bond) diff --git a/testsuite/python/ek_interface.py b/testsuite/python/ek_interface.py index a4da6f2fde1..e439edea90e 100644 --- a/testsuite/python/ek_interface.py +++ b/testsuite/python/ek_interface.py @@ -218,9 +218,10 @@ def test_parameter_change_exceptions(self): self.system.ekcontainer.add(ek_species) self.system.ekcontainer.solver = ek_solver with self.assertRaisesRegex(Exception, "Temperature change not supported by EK"): - self.system.thermostat.turn_off() - with self.assertRaisesRegex(Exception, "Time step change not supported by EK"): - self.system.time_step /= 2. + self.system.thermostat.set_langevin(kT=1., seed=42, gamma=1.) + with self.assertRaisesRegex(ValueError, "must be an integer multiple of the MD time_step"): + self.system.time_step /= 1.7 + self.system.time_step *= 1. if espressomd.has_features("ELECTROSTATICS"): self.system.electrostatics.solver = espressomd.electrostatics.DH( prefactor=1., kappa=1., r_cut=1.) # should not fail diff --git a/testsuite/python/field_test.py b/testsuite/python/field_test.py index 476cd356e5d..dd07b44be63 100644 --- a/testsuite/python/field_test.py +++ b/testsuite/python/field_test.py @@ -59,14 +59,16 @@ def test_gravity(self): self.system.integrator.run(0) - np.testing.assert_almost_equal(g_const, np.copy(p.f) / p.mass) + np.testing.assert_allclose(np.copy(p.f / p.mass), g_const) self.assertAlmostEqual(self.system.analysis.energy()['total'], 0.) # Virtual sites don't feel gravity - if espressomd.has_features("VIRTUAL_SITES_INERTIALESS_TRACERS"): - p.propagation = espressomd.propagation.Propagation.TRANS_LB_TRACER + if espressomd.has_features("VIRTUAL_SITES_RELATIVE"): + p_vs = self.system.part.add(pos=[0, 1, 0]) + p_vs.vs_auto_relate_to(p) self.system.integrator.run(0) - np.testing.assert_allclose(np.copy(p.f), 0) + np.testing.assert_allclose(np.copy(p.f / p.mass), g_const) + np.testing.assert_allclose(np.copy(p_vs.f), [0., 0., 0.]) @utx.skipIfMissingFeatures("ELECTROSTATICS") def test_linear_electric_potential(self): diff --git a/testsuite/python/icc_interface.py b/testsuite/python/icc_interface.py index e447861077c..2d2cb5c41ae 100644 --- a/testsuite/python/icc_interface.py +++ b/testsuite/python/icc_interface.py @@ -98,13 +98,33 @@ def test_invalid_parameters(self): "Parameter 'relaxation' must be >= 0 and <= 2"), ({"relaxation": 2.1}, "Parameter 'relaxation' must be >= 0 and <= 2"), + ({"eps_out": "1"}, + "parameter 'eps_out' is not convertible to 'double'"), + ({"epsilons": [1.]}, + "Parameter 'epsilons' has incorrect shape"), + ({"areas": [1.]}, + "Parameter 'areas' has incorrect shape"), + ({"sigmas": [1.]}, + "Parameter 'sigmas' has incorrect shape"), + ({"normals": [[1., 2., 3.]]}, + "Parameter 'normals' has incorrect shape"), + ({"epsilons": len(areas) * ["str"]}, + "parameter 'epsilons' is not convertible to 'std::vector'"), + ({"areas": len(areas) * ["str"]}, + "parameter 'areas' is not convertible to 'std::vector'"), + ({"sigmas": len(areas) * ["str"]}, + "parameter 'sigmas' is not convertible to 'std::vector'"), + ({"normals": len(areas) * [3 * ["str"]]}, + "parameter 'normals' is not convertible to 'std::vector>'"), ({"eps_out": -1.}, "Parameter 'eps_out' must be > 0"), - ({"ext_field": 0.}, 'A single value was given but 3 were expected'), ] + ({"ext_field": 0.}, + "parameter 'ext_field' is not convertible to 'Utils::Vector'"), + ] for kwargs, error in invalid_params: params = valid_params.copy() params.update(kwargs) - with self.assertRaisesRegex(ValueError, error): + with self.assertRaisesRegex((ValueError, RuntimeError), error): espressomd.electrostatic_extensions.ICC(**params) @utx.skipIfMissingFeatures(["P3M"]) diff --git a/testsuite/python/integrator_exceptions.py b/testsuite/python/integrator_exceptions.py index f02d6e06c19..6e11c04c36c 100644 --- a/testsuite/python/integrator_exceptions.py +++ b/testsuite/python/integrator_exceptions.py @@ -21,6 +21,7 @@ import espressomd.lees_edwards import espressomd.shapes import espressomd.propagation +import numpy as np import unittest as ut import unittest_decorators as utx @@ -56,6 +57,8 @@ def test_00_common_interface(self): with self.assertRaisesRegex(ValueError, 'cannot reuse old forces and recalculate forces'): self.system.integrator.run(recalc_forces=True, reuse_forces=True) self.assertIsNone(self.system.integrator.integrator.call_method("unk")) + self.assertIsNone(self.system.thermostat.call_method("unk")) + self.assertIsNone(self.system.thermostat.kT) if espressomd.has_features("WCA"): wca = self.system.non_bonded_inter[0, 0].wca wca.set_params(epsilon=1., sigma=0.01) @@ -75,6 +78,27 @@ def test_00_common_interface(self): p.rotation = [False, False, True] self.system.integrator.run(0, recalc_forces=True) + def test_01_statefulness(self): + # setting a thermostat with invalid values should be a no-op + self.assertIsNone(self.system.thermostat.kT) + self.assertIsNone(self.system.thermostat.langevin.seed) + self.assertIsNone(self.system.thermostat.langevin.gamma) + with self.assertRaisesRegex(ValueError, "Parameter 'seed' must be a positive integer"): + self.system.thermostat.set_langevin(kT=1., gamma=1., seed=-1) + self.assertIsNone(self.system.thermostat.kT) + self.assertIsNone(self.system.thermostat.langevin.seed) + self.assertIsNone(self.system.thermostat.langevin.gamma) + with self.assertRaisesRegex(ValueError, "Parameter 'kT' cannot be negative"): + self.system.thermostat.set_langevin(kT=-1., gamma=1., seed=42) + self.assertIsNone(self.system.thermostat.kT) + self.assertIsNone(self.system.thermostat.langevin.seed) + self.assertIsNone(self.system.thermostat.langevin.gamma) + with self.assertRaisesRegex(ValueError, "Parameter 'gamma' cannot be negative"): + self.system.thermostat.set_langevin(kT=1., gamma=-1., seed=42) + self.assertIsNone(self.system.thermostat.kT) + self.assertIsNone(self.system.thermostat.langevin.seed) + self.assertIsNone(self.system.thermostat.langevin.gamma) + def test_vv_integrator(self): self.system.cell_system.skin = 0.4 self.system.thermostat.set_brownian(kT=1.0, gamma=1.0, seed=42) @@ -85,8 +109,50 @@ def test_vv_integrator(self): def test_brownian_integrator(self): self.system.cell_system.skin = 0.4 self.system.integrator.set_brownian_dynamics() + self.assertIsNone(self.system.thermostat.kT) with self.assertRaisesRegex(Exception, self.msg + 'The BD integrator requires the BD thermostat'): self.system.integrator.run(0) + with self.assertRaisesRegex(RuntimeError, "Parameter 'brownian' is read-only."): + self.system.thermostat.brownian = 1 + self.assertIsNone(self.system.thermostat.kT) + + def test_langevin_integrator(self): + self.system.cell_system.skin = 0.4 + self.system.integrator.set_vv() + self.system.thermostat.set_langevin(kT=2., gamma=3., seed=42) + + def check_original_params(thermo_off): + langevin = self.system.thermostat.langevin + np.testing.assert_allclose(np.copy(langevin.gamma), 3.) + np.testing.assert_allclose(np.copy(langevin.seed), 42) + if thermo_off: + self.assertIsNone(self.system.thermostat.kT) + else: + np.testing.assert_allclose(self.system.thermostat.kT, 2.) + + # updating a thermostat with invalid parameters should raise an + # exception and roll back to the last valid state of the thermostat + for thermo_off in [False, True]: + if thermo_off: + self.system.thermostat.turn_off() + with self.assertRaisesRegex(ValueError, "Parameter 'seed' must be a positive integer"): + self.system.thermostat.set_langevin(kT=1., gamma=1., seed=-1) + check_original_params(thermo_off) + with self.assertRaisesRegex(ValueError, "Parameter 'kT' cannot be negative"): + self.system.thermostat.set_langevin(kT=-1., gamma=1., seed=42) + check_original_params(thermo_off) + with self.assertRaisesRegex(ValueError, "Parameter 'gamma' cannot .* negative"): + self.system.thermostat.set_langevin(kT=1., gamma=-1., seed=42) + check_original_params(thermo_off) + + with self.assertRaisesRegex(RuntimeError, "Parameter 'langevin' is read-only."): + self.system.thermostat.langevin = 1 + with self.assertRaisesRegex(RuntimeError, "Parameter 'gamma' is read-only."): + self.system.thermostat.langevin.gamma = 1 + with self.assertRaisesRegex(RuntimeError, "Parameter 'seed' is read-only."): + self.system.thermostat.langevin.seed = 1 + with self.assertRaisesRegex(RuntimeError, "Parameter 'philox_counter' is read-only."): + self.system.thermostat.langevin.philox_counter = 1 @utx.skipIfMissingFeatures("NPT") def test_npt_integrator(self): @@ -134,6 +200,16 @@ def test_steepest_descent_integrator(self): with self.assertRaisesRegex(Exception, self.msg + 'The steepest descent integrator is incompatible with thermostats'): self.system.integrator.run(0) + def test_temperature_change(self): + # temperature change only allowed when no other thermostat is active + self.system.thermostat.set_langevin(kT=1., gamma=1., seed=42) + self.system.thermostat.set_langevin(kT=2., gamma=1., seed=42) + self.system.thermostat.set_brownian(kT=2., gamma=1., seed=42) + with self.assertRaisesRegex(RuntimeError, "Cannot set parameter 'kT' to 1.0*: there are currently active thermostats with kT=2.0*"): + self.system.thermostat.set_brownian(kT=1., gamma=1., seed=42) + with self.assertRaisesRegex(RuntimeError, f"Parameter 'kT' is read-only"): + self.system.thermostat.kT = 2. + if __name__ == "__main__": ut.main() diff --git a/testsuite/python/interactions_bonded_interface.py b/testsuite/python/interactions_bonded_interface.py index e8c27588611..57062a5bed3 100644 --- a/testsuite/python/interactions_bonded_interface.py +++ b/testsuite/python/interactions_bonded_interface.py @@ -253,14 +253,6 @@ def test_exceptions(self): with self.assertRaisesRegex(ValueError, "Invalid value for parameter 'elasticLaw': 'Unknown'"): espressomd.interactions.IBM_Triel( ind1=0, ind2=1, ind3=2, k1=1.1, k2=1.2, maxDist=1.6, elasticLaw='Unknown') - with self.assertRaisesRegex(ValueError, "A parameter 'seed' has to be given on first activation of a thermalized bond"): - espressomd.interactions.ThermalizedBond( - temp_com=1., gamma_com=1., temp_distance=1., gamma_distance=1., - r_cut=2.) - with self.assertRaisesRegex(ValueError, "Parameter 'seed' must be >= 0"): - espressomd.interactions.ThermalizedBond( - temp_com=1., gamma_com=1., temp_distance=1., gamma_distance=1., - r_cut=2., seed=-1) # sanity checks when removing bonds self.system.bonded_inter.clear() diff --git a/testsuite/python/lb.py b/testsuite/python/lb.py index f0d0cd4d52d..704c8669282 100644 --- a/testsuite/python/lb.py +++ b/testsuite/python/lb.py @@ -386,15 +386,28 @@ def test_lb_node_set_get(self): with self.assertRaisesRegex(ValueError, "Parameter 'rng_state' must be >= 0"): lbf.rng_state = -5 + def test_temperature_mismatch(self): + self.system.thermostat.set_langevin(kT=2., seed=23, gamma=2.) + lbf = self.lb_class(kT=1., seed=42, **self.params, **self.lb_params) + self.system.lb = lbf + with self.assertRaisesRegex(RuntimeError, "Cannot set parameter 'kT' to 1.0*: there are currently active thermostats with kT=2.0*"): + self.system.thermostat.set_lb(LB_fluid=lbf, seed=23, gamma=2.) + self.assertFalse(self.system.thermostat.lb.is_active) + self.assertTrue(self.system.thermostat.langevin.is_active) + def test_parameter_change_without_seed(self): lbf = self.lb_class(kT=1.0, seed=42, **self.params, **self.lb_params) self.system.lb = lbf self.system.thermostat.set_lb(LB_fluid=lbf, seed=23, gamma=2.0) self.system.thermostat.set_lb(LB_fluid=lbf, gamma=3.0) - with self.assertRaisesRegex(Exception, "Temperature change not supported by LB"): + self.assertAlmostEqual(self.system.thermostat.lb.gamma, 3.) + with self.assertRaisesRegex(RuntimeError, "Temperature change not supported by LB"): self.system.thermostat.turn_off() - with self.assertRaisesRegex(Exception, "Time step change not supported by LB"): - self.system.time_step /= 2. + self.system.thermostat.set_langevin(kT=2., seed=23, gamma=2.) + self.assertFalse(self.system.thermostat.langevin.is_active) + with self.assertRaisesRegex(ValueError, "must be an integer multiple of the MD time_step"): + self.system.time_step /= 1.7 + self.system.time_step *= 1. if espressomd.has_features("ELECTROSTATICS"): self.system.electrostatics.solver = espressomd.electrostatics.DH( prefactor=1., kappa=1., r_cut=1.) # should not fail diff --git a/testsuite/python/lb_thermostat.py b/testsuite/python/lb_thermostat.py index 046b4801db6..8a56d94080b 100644 --- a/testsuite/python/lb_thermostat.py +++ b/testsuite/python/lb_thermostat.py @@ -63,13 +63,15 @@ class LBThermostatCommon(thermostats_common.ThermostatsCommon): # relaxation time 0.2 sim time units partcl_mass = 0.2 * partcl_gamma - np.random.seed(41) - def setUp(self): self.lbf = self.lb_class(**LB_PARAMS, **self.lb_params) self.system.lb = self.lbf + # make test results independent of execution order + np.random.seed(41) self.system.thermostat.set_lb( LB_fluid=self.lbf, seed=5, gamma=self.global_gamma) + self.system.thermostat.lb.call_method( + "override_philox_counter", counter=0) def tearDown(self): self.system.lb = None diff --git a/testsuite/python/long_range_actors.py b/testsuite/python/long_range_actors.py index 8611542187f..ae8249fb004 100644 --- a/testsuite/python/long_range_actors.py +++ b/testsuite/python/long_range_actors.py @@ -105,9 +105,12 @@ def test_electrostatics_registration(self): self.system.electrostatics.extension = icc if espressomd.has_features(["NPT"]): with self.assertRaisesRegex(Exception, "ERROR: ICC does not work in the NPT ensemble"): + self.system.thermostat.set_npt( + kT=1., gamma0=2., gammav=0., seed=42) self.system.integrator.set_isotropic_npt( ext_pressure=2., piston=0.01) self.system.integrator.run(0) + self.system.thermostat.turn_off() self.system.integrator.set_vv() with self.assertRaisesRegex(RuntimeError, "Cannot change solver when an extension is active"): self.system.electrostatics.solver = p3m_new diff --git a/testsuite/python/pairs.py b/testsuite/python/pairs.py index 5f78a814e5f..b50bc5fa3f2 100644 --- a/testsuite/python/pairs.py +++ b/testsuite/python/pairs.py @@ -83,7 +83,7 @@ def check_pairs(self): def test_input_exceptions(self): with self.assertRaisesRegex(ValueError, "Unknown argument types='none'"): self.system.cell_system.get_pairs(0.1, types="none") - with self.assertRaisesRegex(RuntimeError, "Provided argument of type 'int' is not convertible to 'std::vector'"): + with self.assertRaisesRegex(RuntimeError, "Provided argument of type 'int' for parameter 'types' is not convertible to 'std::vector'"): self.system.cell_system.get_pairs(0.1, types=3) with self.assertRaisesRegex(RuntimeError, "Provided argument of type .+ because it contains a value that is not convertible to 'int'"): self.system.cell_system.get_pairs(0.1, types={'3.': 6.}) diff --git a/testsuite/python/particle.py b/testsuite/python/particle.py index d887bc9814a..75e4f630eab 100644 --- a/testsuite/python/particle.py +++ b/testsuite/python/particle.py @@ -113,6 +113,8 @@ 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} + # Python 3.11+ skips empty bitfields during enum iteration + flags_si["NONE"] = Propagation.NONE self.assertEqual(flags_si, flags_core) test_bonds_property = generateTestForScalarProperty( diff --git a/testsuite/python/propagation_langevin.py b/testsuite/python/propagation_langevin.py index 05785c738f2..0834265a98f 100644 --- a/testsuite/python/propagation_langevin.py +++ b/testsuite/python/propagation_langevin.py @@ -19,6 +19,7 @@ import unittest as ut import unittest_decorators as utx import espressomd +import espressomd.lb import espressomd.propagation import itertools import numpy as np @@ -27,9 +28,10 @@ class LangevinThermostat(ut.TestCase): """Test Langevin Dynamics""" - system = espressomd.System(box_l=[1.0, 1.0, 1.0]) + system = espressomd.System(box_l=[12., 12., 12.]) system.cell_system.set_regular_decomposition(use_verlet_lists=True) - system.cell_system.skin = 0 + system.cell_system.skin = 0. + system.min_global_cut = 2. system.periodicity = [False, False, False] def setUp(self): @@ -41,6 +43,8 @@ def setUp(self): def tearDown(self): self.system.part.clear() self.system.thermostat.turn_off() + if espressomd.has_features("WALBERLA"): + self.system.lb = None def check_rng(self, per_particle_gamma=False): """Test for RNG consistency.""" @@ -66,9 +70,22 @@ def reset_particle(): system = self.system system.time_step = 0.01 + self.assertIsNone(system.thermostat.kT) + self.assertFalse(system.thermostat.langevin.is_active) + system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=41) + system.thermostat.langevin.call_method( + "override_philox_counter", counter=0) system.integrator.set_vv() + self.assertIsNotNone(system.thermostat.kT) + np.testing.assert_almost_equal(system.thermostat.kT, kT) + np.testing.assert_almost_equal( + np.copy(system.thermostat.langevin.gamma), gamma) + if espressomd.has_features("ROTATION"): + np.testing.assert_almost_equal( + np.copy(system.thermostat.langevin.gamma_rotation), gamma) + # run(0) does not increase the philox counter and should give the same # force p = reset_particle() @@ -86,6 +103,7 @@ def reset_particle(): # run(1) should give a different force p = reset_particle() system.integrator.run(1) + self.assertEqual(system.thermostat.langevin.philox_counter, 1) force2 = np.copy(p.f) self.assertTrue(np.all(np.not_equal(force1, force2))) if espressomd.has_features("ROTATION"): @@ -97,11 +115,15 @@ def reset_particle(): # force3: langevin.rng_counter() = 1, langevin.rng_seed() = 42 p = reset_particle() system.integrator.run(0, recalc_forces=True) + self.assertEqual(system.thermostat.langevin.seed, 41) + self.assertEqual(system.thermostat.langevin.philox_counter, 1) 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) + self.assertEqual(system.thermostat.langevin.seed, 42) + self.assertEqual(system.thermostat.langevin.philox_counter, 1) force3 = np.copy(p.f) self.assertTrue(np.all(np.not_equal(force2, force3))) if espressomd.has_features("ROTATION"): @@ -112,6 +134,8 @@ def reset_particle(): p = reset_particle() system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=42) system.integrator.run(1) + self.assertEqual(system.thermostat.langevin.seed, 42) + self.assertEqual(system.thermostat.langevin.philox_counter, 2) force4 = np.copy(p.f) self.assertTrue(np.all(np.not_equal(force3, force4))) if espressomd.has_features("ROTATION"): @@ -124,6 +148,8 @@ def reset_particle(): reset_particle() system.thermostat.set_langevin(kT=kT, gamma=gamma, seed=41) system.integrator.run(1) + self.assertEqual(system.thermostat.langevin.seed, 41) + self.assertEqual(system.thermostat.langevin.philox_counter, 3) p = reset_particle() system.integrator.run(0, recalc_forces=True) force5 = np.copy(p.f) @@ -132,6 +158,15 @@ def reset_particle(): torque5 = np.copy(p.torque_lab) self.assertTrue(np.all(np.not_equal(torque4, torque5))) + with self.assertRaises(ValueError): + system.thermostat.set_langevin(kT=-1., gamma=2.) + with self.assertRaises(ValueError): + system.thermostat.set_langevin(kT=1., gamma=-2.) + + self.system.thermostat.turn_off() + self.assertFalse(system.thermostat.langevin.is_active) + self.assertIsNone(system.thermostat.kT) + def test_01__rng(self): """Test for RNG consistency.""" # No seed should throw exception @@ -219,12 +254,16 @@ def test_07__virtual(self): 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"]) + @utx.skipIfMissingFeatures(["VIRTUAL_SITES_RELATIVE", "WALBERLA"]) def test_virtual_sites_relative(self): Propagation = espressomd.propagation.Propagation system = self.system + system.time_step = 0.01 o0 = np.array([0, 0, 0]) + system.lb = espressomd.lb.LBFluidWalberla( + tau=0.01, agrid=2., density=1., kinematic_viscosity=1., kT=0.) + system.thermostat.set_lb(LB_fluid=system.lb, seed=42, gamma=1.) system.thermostat.set_langevin( kT=0., gamma=1., gamma_rotation=1., seed=42) @@ -232,6 +271,8 @@ def test_virtual_sites_relative(self): 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) + system.part.all().propagation = ( + Propagation.TRANS_LANGEVIN | Propagation.ROT_LANGEVIN) refs = { "real.f": [-3, -4, -5], "real.torque_lab": [0, 0, 0], diff --git a/testsuite/python/propagation_lb.py b/testsuite/python/propagation_lb.py index 09bd1710cd5..91582099a2d 100644 --- a/testsuite/python/propagation_lb.py +++ b/testsuite/python/propagation_lb.py @@ -41,6 +41,104 @@ def tearDown(self): self.system.thermostat.turn_off() self.system.part.clear() + def test_01__rng(self): + """Test for RNG consistency.""" + + system = self.system + system.time_step = 0.01 + kT = 1.1 + gamma = 3.5 + + def reset_fluid(): + lb_fluid = self.lb_class(agrid=1., tau=0.01, density=1., kT=kT, + kinematic_viscosity=1., **self.lb_params) + self.system.lb = lb_fluid + return lb_fluid + + def reset_particle(): + self.system.part.clear() + return system.part.add(pos=[0, 0, 0]) + + self.assertIsNone(system.thermostat.kT) + self.assertFalse(system.thermostat.lb.is_active) + + lb_fluid = reset_fluid() + system.thermostat.set_lb(LB_fluid=lb_fluid, gamma=gamma, seed=41) + system.thermostat.lb.call_method("override_philox_counter", counter=0) + system.integrator.set_vv() + + self.assertIsNotNone(system.thermostat.kT) + self.assertTrue(system.thermostat.lb.is_active) + np.testing.assert_almost_equal(system.thermostat.kT, kT) + np.testing.assert_almost_equal( + np.copy(system.thermostat.lb.gamma), gamma) + + # 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) + p = reset_particle() + system.integrator.run(0, recalc_forces=True) + force1 = np.copy(p.f) + np.testing.assert_almost_equal(force0, force1) + np.testing.assert_almost_equal(force0, [0., 0., 0.]) + + # run(1) should give a different force + p = reset_particle() + system.integrator.run(1) + self.assertEqual(system.thermostat.lb.seed, 41) + self.assertEqual(system.thermostat.lb.philox_counter, 1) + force2 = np.copy(p.f) + self.assertTrue(np.all(np.not_equal(force1, force2))) + + # Different seed should give a different force with same counter state + # force2: lb.rng_counter() = 2, lb.rng_seed() = 41 + # force3: lb.rng_counter() = 2, lb.rng_seed() = 42 + reset_fluid() + p = reset_particle() + system.integrator.run(1) + self.assertEqual(system.thermostat.lb.seed, 41) + self.assertEqual(system.thermostat.lb.philox_counter, 2) + force2 = np.copy(p.f) + lb_fluid = reset_fluid() + system.thermostat.set_lb(LB_fluid=lb_fluid, gamma=gamma, seed=42) + system.thermostat.lb.call_method("override_philox_counter", counter=1) + system.integrator.run(1) + self.assertEqual(system.thermostat.lb.seed, 42) + self.assertEqual(system.thermostat.lb.philox_counter, 2) + force3 = np.copy(p.f) + self.assertTrue(np.all(np.not_equal(force2, force3))) + + # Same seed should not give the same force with different counter state + # force3: lb.rng_counter() = 2, lb.rng_seed() = 42 + # force4: lb.rng_counter() = 3, lb.rng_seed() = 42 + lb_fluid = reset_fluid() + p = reset_particle() + system.thermostat.set_lb(LB_fluid=lb_fluid, gamma=gamma, seed=42) + system.integrator.run(1) + self.assertEqual(system.thermostat.lb.seed, 42) + self.assertEqual(system.thermostat.lb.philox_counter, 3) + force4 = np.copy(p.f) + self.assertTrue(np.all(np.not_equal(force3, force4))) + + # Seed offset should not give the same force with a lag + # force4: lb.rng_counter() = 3, lb.rng_seed() = 42 + # force5: lb.rng_counter() = 4, lb.rng_seed() = 41 + lb_fluid = reset_fluid() + reset_particle() + system.thermostat.set_lb( + LB_fluid=lb_fluid, kT=kT, gamma=gamma, seed=41) + system.integrator.run(1) + self.assertEqual(system.thermostat.lb.seed, 41) + self.assertEqual(system.thermostat.lb.philox_counter, 4) + force5 = np.copy(p.f) + self.assertTrue(np.all(np.not_equal(force4, force5))) + + self.system.thermostat.turn_off() + self.assertFalse(system.thermostat.langevin.is_active) + self.assertIsNone(system.thermostat.kT) + @utx.skipIfMissingFeatures(["VIRTUAL_SITES_RELATIVE", "VIRTUAL_SITES_INERTIALESS_TRACERS"]) def test_virtual_sites_relative(self): @@ -118,12 +216,12 @@ def calc_trajectory(p, r0): 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: + 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 + 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) diff --git a/testsuite/python/propagation_npt.py b/testsuite/python/propagation_npt.py index 032444ade89..0df370dd087 100644 --- a/testsuite/python/propagation_npt.py +++ b/testsuite/python/propagation_npt.py @@ -42,21 +42,14 @@ def tearDown(self): self.system.electrostatics.clear() if espressomd.has_features(["DIPOLES"]): self.system.magnetostatics.clear() - self.reset_rng_counter() + # reset RNG counter to make tests independent of execution order + self.system.thermostat.npt_iso.call_method( + "override_philox_counter", counter=0) 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 with self.assertRaises(RuntimeError): @@ -248,6 +241,7 @@ def run_with_p3m(self, container, p3m, method): system.integrator.integrator, espressomd.integrate.VelocityVerlet) container.solver = None + system.thermostat.set_npt(kT=1.0, gamma0=2, gammav=0.04, seed=42) system.integrator.set_isotropic_npt(**npt_kwargs_rectangular) container.solver = p3m with self.assertRaisesRegex(Exception, err_msg): diff --git a/testsuite/python/propagation_stokesian.py b/testsuite/python/propagation_stokesian.py index 9a7ac25393f..a1bdd81a355 100644 --- a/testsuite/python/propagation_stokesian.py +++ b/testsuite/python/propagation_stokesian.py @@ -58,7 +58,7 @@ def reset_particle(): viscosity = 2.4 # invalid parameters should throw exceptions - with self.assertRaisesRegex(ValueError, "kT has an invalid value"): + with self.assertRaisesRegex(ValueError, "Parameter 'kT' cannot be negative"): system.thermostat.set_stokesian(kT=-1) with self.assertRaises(ValueError): system.thermostat.set_stokesian(kT=1, seed=-1) @@ -72,34 +72,44 @@ def reset_particle(): # run(0) does not increase the philox counter and should give no force p = reset_particle() system.integrator.run(0) + self.assertEqual(system.thermostat.stokesian.seed, 41) + self.assertEqual(system.thermostat.stokesian.philox_counter, 0) force0 = np.copy(p.pos) / pos2force np.testing.assert_almost_equal(force0, 0) # run(1) should give a force p = reset_particle() system.integrator.run(1) + self.assertEqual(system.thermostat.stokesian.seed, 41) + self.assertEqual(system.thermostat.stokesian.philox_counter, 1) force1 = np.copy(p.pos) / pos2force self.assertTrue(np.all(np.not_equal(force1, [0, 0, 0]))) # Same seed should not give the same force with different counter state - # force1: brownian.rng_counter() = 0, brownian.rng_seed() = 41 - # force2: brownian.rng_counter() = 1, brownian.rng_seed() = 41 + # force1: brownian.rng_counter() = 1, brownian.rng_seed() = 41 + # force2: brownian.rng_counter() = 2, brownian.rng_seed() = 41 p = reset_particle() system.thermostat.set_stokesian(kT=kT, seed=41) system.integrator.run(1) + self.assertEqual(system.thermostat.stokesian.seed, 41) + self.assertEqual(system.thermostat.stokesian.philox_counter, 2) force2 = np.copy(p.pos) / pos2force self.assertTrue(np.all(np.not_equal(force2, force1))) # Seed offset should not give the same force with a lag - # force3: brownian.rng_counter() = 2, brownian.rng_seed() = 42 - # force4: brownian.rng_counter() = 3, brownian.rng_seed() = 41 + # force3: brownian.rng_counter() = 3, brownian.rng_seed() = 42 + # force4: brownian.rng_counter() = 4, brownian.rng_seed() = 41 p = reset_particle() system.thermostat.set_stokesian(kT=kT, seed=42) system.integrator.run(1) + self.assertEqual(system.thermostat.stokesian.seed, 42) + self.assertEqual(system.thermostat.stokesian.philox_counter, 3) force3 = np.copy(p.pos) / pos2force p = reset_particle() system.thermostat.set_stokesian(kT=kT, seed=41) system.integrator.run(1) + self.assertEqual(system.thermostat.stokesian.seed, 41) + self.assertEqual(system.thermostat.stokesian.philox_counter, 4) force4 = np.copy(p.pos) / pos2force self.assertTrue(np.all(np.not_equal(force3, force4))) diff --git a/testsuite/python/save_checkpoint.py b/testsuite/python/save_checkpoint.py index 13bb2740ae5..04b752e4434 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -262,24 +262,26 @@ harmonic_bond = espressomd.interactions.HarmonicBond(r_0=0.0, k=1.0) system.bonded_inter.add(harmonic_bond) p2.add_bond((harmonic_bond, p1)) -# create 3 thermalized bonds that will overwrite each other's seed -therm_params = dict(temp_com=0.1, temp_distance=0.2, gamma_com=0.3, - gamma_distance=0.5, r_cut=2.) -therm_bond1 = espressomd.interactions.ThermalizedBond(seed=1, **therm_params) -therm_bond2 = espressomd.interactions.ThermalizedBond(seed=2, **therm_params) -therm_bond3 = espressomd.interactions.ThermalizedBond(seed=3, **therm_params) -system.bonded_inter.add(therm_bond1) -p2.add_bond((therm_bond1, p1)) -checkpoint.register("therm_bond2") -checkpoint.register("therm_params") -# create Drude particles -if espressomd.has_features(['ELECTROSTATICS', 'MASS', 'ROTATION']): - dh = espressomd.drude_helpers.DrudeHelpers() - dh.add_drude_particle_to_core( - system=system, harmonic_bond=harmonic_bond, - thermalized_bond=therm_bond1, p_core=p2, type_drude=10, - alpha=1., mass_drude=0.6, coulomb_prefactor=0.8, thole_damping=2.) - checkpoint.register("dh") +if 'THERM.LB' in modes or 'THERM.LANGEVIN' in modes: + # create Drude particles + system.thermostat.set_thermalized_bond(seed=3) + system.thermostat.thermalized_bond.call_method( + "override_philox_counter", counter=5) + therm_params = dict(temp_com=0.1, temp_distance=0.2, gamma_com=0.3, + gamma_distance=0.5, r_cut=2.) + therm_bond1 = espressomd.interactions.ThermalizedBond(**therm_params) + therm_bond2 = espressomd.interactions.ThermalizedBond(**therm_params) + system.bonded_inter.add(therm_bond1) + p2.add_bond((therm_bond1, p1)) + checkpoint.register("therm_bond2") + checkpoint.register("therm_params") + if espressomd.has_features(['ELECTROSTATICS', 'MASS', 'ROTATION']): + dh = espressomd.drude_helpers.DrudeHelpers() + dh.add_drude_particle_to_core( + system=system, harmonic_bond=harmonic_bond, + thermalized_bond=therm_bond1, p_core=p2, type_drude=10, + alpha=1., mass_drude=0.6, coulomb_prefactor=0.8, thole_damping=2.) + checkpoint.register("dh") strong_harmonic_bond = espressomd.interactions.HarmonicBond(r_0=0.0, k=5e5) system.bonded_inter.add(strong_harmonic_bond) p4.add_bond((strong_harmonic_bond, p3)) @@ -360,9 +362,9 @@ if lbf_class: system.lb = lbf - system.ekcontainer = ekcontainer if 'THERM.LB' in modes: system.thermostat.set_lb(LB_fluid=lbf, seed=23, gamma=2.0) + system.ekcontainer = ekcontainer # Create a 3D grid with deterministic values to fill the LB fluid lattice m = np.pi / 12 grid_3D = np.fromfunction( @@ -435,7 +437,10 @@ if espressomd.has_features('THERMOSTAT_PER_PARTICLE'): gamma = 2. if espressomd.has_features('PARTICLE_ANISOTROPY'): - gamma = np.array([2., 3., 4.]) + if 'THERM.LB' in modes: + gamma = np.array([2., 2., 2.]) + else: + gamma = np.array([2., 3., 4.]) p4.gamma = gamma if espressomd.has_features('ROTATION'): p3.gamma_rot = 2. * gamma diff --git a/testsuite/python/test_checkpoint.py b/testsuite/python/test_checkpoint.py index 93054bbe6eb..550a8c33a7c 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -50,6 +50,9 @@ has_lb_mode = ('LB.WALBERLA' in modes and espressomd.has_features('WALBERLA') and ('LB.CPU' in modes or 'LB.GPU' in modes and is_gpu_available)) has_p3m_mode = 'P3M.CPU' in modes or 'P3M.GPU' in modes and is_gpu_available +has_thermalized_bonds = 'THERM.LB' in modes or 'THERM.LANGEVIN' in modes +has_drude = (espressomd.has_features(['ELECTROSTATICS' and 'MASS', 'ROTATION']) + and has_thermalized_bonds) class CheckpointTest(ut.TestCase): @@ -397,7 +400,7 @@ def test_particle_properties(self): np.testing.assert_allclose(np.copy(p4.rinertia), [1., 1., 1.]) if espressomd.has_features('ELECTROSTATICS'): np.testing.assert_allclose(p1.q, 1.) - if espressomd.has_features(['MASS', 'ROTATION']): + if has_drude: # check Drude particles p5 = system.part.by_id(5) np.testing.assert_allclose(p2.q, +0.118, atol=1e-3) @@ -428,7 +431,10 @@ def test_particle_properties(self): if espressomd.has_features('THERMOSTAT_PER_PARTICLE'): gamma = 2. if espressomd.has_features('PARTICLE_ANISOTROPY'): - gamma = np.array([2., 3., 4.]) + if 'THERM.LB' in modes: + gamma = np.array([2., 2., 2.]) + else: + gamma = np.array([2., 3., 4.]) np.testing.assert_allclose(p4.gamma, gamma) if espressomd.has_features('ROTATION'): np.testing.assert_allclose(p3.gamma_rot, 2. * gamma) @@ -497,63 +503,85 @@ def test_shape_based_constraints_serialization(self): @ut.skipIf(not has_lb_mode, "Skipping test due to missing LB feature.") @ut.skipIf('THERM.LB' not in modes, 'LB thermostat not in modes') def test_thermostat_LB(self): - thmst = system.thermostat.get_state()[0] - self.assertEqual(thmst['type'], 'LB') - # rng_counter_fluid = seed, seed is 0 because kT=0 - self.assertEqual(thmst['rng_counter_fluid'], 0) - self.assertEqual(thmst['gamma'], 2.0) + thmst = system.thermostat.lb + self.assertTrue(thmst.is_active) + self.assertEqual(thmst.seed, 23) + self.assertEqual(thmst.philox_counter, 0) + self.assertAlmostEqual(thmst.gamma, 2., delta=1e-10) + self.assertAlmostEqual(system.thermostat.kT, 0., delta=1e-10) @ut.skipIf('THERM.LANGEVIN' not in modes, 'Langevin thermostat not in modes') def test_thermostat_Langevin(self): - thmst = system.thermostat.get_state()[0] - self.assertEqual(thmst['type'], 'LANGEVIN') - self.assertEqual(thmst['kT'], 1.0) - self.assertEqual(thmst['seed'], 42) - self.assertEqual(thmst['counter'], 0) - np.testing.assert_array_equal(thmst['gamma'], 3 * [2.0]) + thmst = system.thermostat.langevin + self.assertTrue(thmst.is_active) + self.assertEqual(thmst.seed, 42) + self.assertEqual(thmst.philox_counter, 0) + self.assertAlmostEqual(system.thermostat.kT, 1., delta=1e-10) + np.testing.assert_allclose(np.copy(thmst.gamma), 2., atol=1e-10) if espressomd.has_features('ROTATION'): - np.testing.assert_array_equal(thmst['gamma_rotation'], 3 * [2.0]) + np.testing.assert_allclose( + np.copy(thmst.gamma_rotation), 2., atol=1e-10) @ut.skipIf('THERM.BD' not in modes, 'Brownian thermostat not in modes') def test_thermostat_Brownian(self): - thmst = system.thermostat.get_state()[0] - self.assertEqual(thmst['type'], 'BROWNIAN') - self.assertEqual(thmst['kT'], 1.0) - self.assertEqual(thmst['seed'], 42) - self.assertEqual(thmst['counter'], 0) - np.testing.assert_array_equal(thmst['gamma'], 3 * [2.0]) + thmst = system.thermostat.brownian + self.assertTrue(thmst.is_active) + self.assertEqual(thmst.seed, 42) + self.assertEqual(thmst.philox_counter, 0) + self.assertAlmostEqual(system.thermostat.kT, 1., delta=1e-10) + np.testing.assert_allclose(np.copy(thmst.gamma), 2., atol=1e-10) if espressomd.has_features('ROTATION'): - np.testing.assert_array_equal(thmst['gamma_rotation'], 3 * [2.0]) + np.testing.assert_allclose( + np.copy(thmst.gamma_rotation), 2., atol=1e-10) @utx.skipIfMissingFeatures('DPD') @ut.skipIf('THERM.DPD' not in modes, 'DPD thermostat not in modes') def test_thermostat_DPD(self): - thmst = system.thermostat.get_state()[0] - self.assertEqual(thmst['type'], 'DPD') - self.assertEqual(thmst['kT'], 1.0) - self.assertEqual(thmst['seed'], 42) - self.assertEqual(thmst['counter'], 0) + thmst = system.thermostat.dpd + self.assertTrue(thmst.is_active) + self.assertEqual(thmst.seed, 42) + self.assertEqual(thmst.philox_counter, 0) + self.assertAlmostEqual(system.thermostat.kT, 1., delta=1e-10) @utx.skipIfMissingFeatures('NPT') @ut.skipIf('THERM.NPT' not in modes, 'NPT thermostat not in modes') def test_thermostat_NPT(self): - thmst = system.thermostat.get_state()[0] - self.assertEqual(thmst['type'], 'NPT_ISO') - self.assertEqual(thmst['seed'], 42) - self.assertEqual(thmst['counter'], 0) - self.assertEqual(thmst['gamma0'], 2.0) - self.assertEqual(thmst['gammav'], 0.1) + thmst = system.thermostat.npt_iso + self.assertTrue(thmst.is_active) + self.assertEqual(thmst.seed, 42) + self.assertEqual(thmst.philox_counter, 0) + self.assertAlmostEqual(thmst.gamma0, 2.0, delta=1e-10) + self.assertAlmostEqual(thmst.gammav, 0.1, delta=1e-10) + self.assertAlmostEqual(system.thermostat.kT, 1., delta=1e-10) @utx.skipIfMissingFeatures('STOKESIAN_DYNAMICS') @ut.skipIf('THERM.SDM' not in modes, 'SDM thermostat not in modes') def test_thermostat_SDM(self): - thmst = system.thermostat.get_state()[0] - self.assertEqual(thmst['type'], 'SD') - self.assertEqual(thmst['kT'], 1.0) - self.assertEqual(thmst['seed'], 42) - self.assertEqual(thmst['counter'], 0) + thmst = system.thermostat.stokesian + self.assertTrue(thmst.is_active) + self.assertEqual(thmst.seed, 42) + self.assertEqual(thmst.philox_counter, 0) + self.assertAlmostEqual(system.thermostat.kT, 1., delta=1e-10) + + @ut.skipIf(not has_thermalized_bonds, + 'thermalized bond thermostat not in modes') + def test_thermostat_thermalized_bond(self): + thmst = system.thermostat.thermalized_bond + self.assertEqual(thmst.seed, 3) + self.assertEqual(thmst.philox_counter, 5) + therm_bonds = [therm_bond2] + for bond in system.bonded_inter: + if isinstance(bond, espressomd.interactions.ThermalizedBond): + therm_bonds.append(bond) + self.assertEqual(len(therm_bonds), 2) + for bond in therm_bonds: + self.assertAlmostEqual(bond.temp_com, 0.1, delta=1e-10) + self.assertAlmostEqual(bond.gamma_com, 0.3, delta=1e-10) + self.assertAlmostEqual(bond.temp_distance, 0.2, delta=1e-10) + self.assertAlmostEqual(bond.gamma_distance, 0.5, delta=1e-10) + self.assertAlmostEqual(bond.r_cut, 2., delta=1e-10) def test_integrator(self): params = system.integrator.get_params() @@ -655,10 +683,11 @@ def test_bonded_inter(self): self.assertEqual(partcl_1.bonds[0][0].params, reference) self.assertEqual(system.bonded_inter[0].params, reference) # all thermalized bonds should be identical - reference = {**therm_params, 'seed': 3} - self.assertEqual(partcl_1.bonds[1][0].params, reference) - self.assertEqual(system.bonded_inter[1].params, reference) - self.assertEqual(therm_bond2.params, reference) + if has_drude: + reference = therm_params + self.assertEqual(partcl_1.bonds[1][0].params, reference) + self.assertEqual(system.bonded_inter[1].params, reference) + self.assertEqual(therm_bond2.params, reference) # immersed boundary bonds self.assertEqual( ibm_volcons_bond.params, {'softID': 15, 'kappaV': 0.01}) @@ -686,7 +715,7 @@ def test_bond_breakage_specs(self): delta=1e-10) self.assertEqual(break_spec.action_type, cpt_spec.action_type) - @utx.skipIfMissingFeatures(['ELECTROSTATICS', 'MASS', 'ROTATION']) + @ut.skipIf(not has_drude, 'no Drude particles') def test_drude_helpers(self): drude_type = 10 core_type = 0 @@ -973,6 +1002,7 @@ def test_constraints(self): @utx.skipIfMissingFeatures("WCA") @ut.skipIf(has_lb_mode, "LB not supported") @ut.skipIf("INT.SDM" in modes, "Stokesian integrator not supported") + @ut.skipIf("INT.NPT" in modes, "NPT integrator not supported") @ut.skipIf("INT.BD" in modes, "Brownian integrator not supported") @ut.skipIf("INT.SD" in modes, "Steepest descent not supported") def test_union(self): diff --git a/testsuite/python/thermalized_bond.py b/testsuite/python/thermalized_bond.py index a89717b8060..527bef5095d 100644 --- a/testsuite/python/thermalized_bond.py +++ b/testsuite/python/thermalized_bond.py @@ -28,25 +28,30 @@ @utx.skipIfMissingFeatures(["MASS"]) class ThermalizedBond(ut.TestCase, thermostats_common.ThermostatsCommon): - """Tests the two velocity distributions for COM and distance created by the - thermalized bond independently against the single component Maxwell - distribution. Adapted from langevin_thermostat testcase.""" + """ + Test the two velocity distributions for COM and distance created by the + thermalized bond independently against the single component Maxwell + distribution. + """ - box_l = 10.0 - system = espressomd.System(box_l=[box_l] * 3) + system = espressomd.System(box_l=[10., 10., 10.]) system.cell_system.set_n_square() system.cell_system.skin = 0.3 - @classmethod - def setUpClass(cls): + def setUp(self): np.random.seed(42) + self.system.time_step = 0.01 + self.system.periodicity = [True, True, True] + + def tearDown(self): + self.system.part.clear() + self.system.thermostat.turn_off() def test_com_langevin(self): """Test for COM thermalization.""" N = 200 N_half = int(N / 2) - self.system.part.clear() self.system.time_step = 0.02 self.system.periodicity = [False, False, False] @@ -66,9 +71,10 @@ def test_com_langevin(self): t_com = 2.0 g_com = 4.0 + self.system.thermostat.set_thermalized_bond(seed=55) thermalized_dist_bond = espressomd.interactions.ThermalizedBond( temp_com=t_com, gamma_com=g_com, temp_distance=t_dist, - gamma_distance=g_dist, r_cut=2.0, seed=55) + gamma_distance=g_dist, r_cut=2.0) self.system.bonded_inter.add(thermalized_dist_bond) for p1, p2 in zip(partcls_m1, partcls_m2): @@ -98,7 +104,6 @@ def test_dist_langevin(self): N = 100 N_half = int(N / 2) - self.system.part.clear() self.system.time_step = 0.02 self.system.periodicity = [True, True, True] @@ -117,9 +122,10 @@ def test_dist_langevin(self): t_com = 0.0 g_com = 0.0 + self.system.thermostat.set_thermalized_bond(seed=51) thermalized_dist_bond = espressomd.interactions.ThermalizedBond( temp_com=t_com, gamma_com=g_com, temp_distance=t_dist, - gamma_distance=g_dist, r_cut=9, seed=51) + gamma_distance=g_dist, r_cut=9) self.system.bonded_inter.add(thermalized_dist_bond) for p1, p2 in zip(partcls_m1, partcls_m2): @@ -142,6 +148,13 @@ def test_dist_langevin(self): self.check_velocity_distribution( v_stored, v_minmax, bins, error_tol, t_dist) + def test_exceptions(self): + espressomd.interactions.ThermalizedBond( + temp_com=1., gamma_com=1., temp_distance=1., gamma_distance=1., + r_cut=3.) + with self.assertRaisesRegex(Exception, "Thermalized bonds require the thermalized_bond thermostat"): + self.system.integrator.run(0) + if __name__ == "__main__": ut.main() diff --git a/testsuite/python/virtual_sites_relative.py b/testsuite/python/virtual_sites_relative.py index dc01d655dc6..be74b08c389 100644 --- a/testsuite/python/virtual_sites_relative.py +++ b/testsuite/python/virtual_sites_relative.py @@ -19,6 +19,7 @@ import unittest as ut import unittest_decorators as utx import espressomd +import espressomd.lb import espressomd.propagation import numpy as np import tests_common @@ -31,7 +32,7 @@ class VirtualSites(ut.TestCase): np.random.seed(42) def setUp(self): - self.system.box_l = [10.0, 10.0, 10.0] + self.system.box_l = [12.0, 12.0, 12.0] self.system.cell_system.set_regular_decomposition( use_verlet_lists=True) @@ -40,6 +41,8 @@ def tearDown(self): self.system.thermostat.turn_off() self.system.integrator.set_vv() self.system.non_bonded_inter[0, 0].lennard_jones.deactivate() + if espressomd.has_features("WALBERLA"): + self.system.lb = None def multiply_quaternions(self, a, b): return np.array( @@ -82,9 +85,13 @@ 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) + @utx.skipIfMissingFeatures(["WALBERLA"]) def test_vs_quat(self): self.system.time_step = 0.01 self.system.min_global_cut = 0.23 + self.system.lb = lb_fluid = espressomd.lb.LBFluidWalberla( + tau=0.01, agrid=2., density=1., kinematic_viscosity=1., kT=0.) + self.system.thermostat.set_lb(LB_fluid=lb_fluid, seed=42, gamma=1.) Propagation = espressomd.propagation.Propagation p1 = self.system.part.add(pos=[1, 1, 1], rotation=3 * [True], omega_lab=[1, 1, 1]) @@ -256,16 +263,10 @@ def run_test_lj(self): rotation=3 * [True], id=3 * i, pos=np.random.random(3) * l, type=1, omega_lab=0.3 * np.random.random(3), v=np.random.random(3)) # lj spheres - p3ip1 = system.part.add(rotation=3 * [True], - id=3 * i + 1, - pos=p3i.pos + - p3i.director / 2., - type=0) - p3ip2 = system.part.add(rotation=3 * [True], - id=3 * i + 2, - pos=p3i.pos - - p3i.director / 2., - type=0) + p3ip1 = system.part.add(rotation=3 * [True], id=3 * i + 1, + pos=p3i.pos + p3i.director / 2., type=0) + p3ip2 = system.part.add(rotation=3 * [True], id=3 * i + 2, + pos=p3i.pos - p3i.director / 2., type=0) p3ip1.vs_auto_relate_to(p3i.id) self.verify_vs(p3ip1, verify_velocity=False) p3ip2.vs_auto_relate_to(p3i.id) diff --git a/testsuite/python/virtual_sites_tracers_common.py b/testsuite/python/virtual_sites_tracers_common.py index 06aca47ae80..df44fa77fe9 100644 --- a/testsuite/python/virtual_sites_tracers_common.py +++ b/testsuite/python/virtual_sites_tracers_common.py @@ -153,14 +153,20 @@ def test_advection(self): def test_zz_exceptions_without_lb(self): """ - Check behaviour without LB. Ignore real particles, complain on tracers. + Check behaviour without LB. """ self.set_lb() system = self.system + lbf = system.lb system.lb = None system.part.clear() p = system.part.add(pos=(0, 0, 0)) - system.integrator.run(1) + with self.assertRaisesRegex(Exception, "The LB thermostat requires a LB fluid"): + system.integrator.run(1) p.propagation = espressomd.propagation.Propagation.TRANS_LB_TRACER with self.assertRaisesRegex(Exception, "LB needs to be active for inertialess tracers"): system.integrator.run(1) + system.lb = lbf + self.system.thermostat.turn_off() + with self.assertRaisesRegex(Exception, "The LB integrator requires the LB thermostat"): + system.integrator.run(1) diff --git a/testsuite/scripts/tutorials/test_polymers.py b/testsuite/scripts/tutorials/test_polymers.py index 2da7532db8c..f938bf0e0c6 100644 --- a/testsuite/scripts/tutorials/test_polymers.py +++ b/testsuite/scripts/tutorials/test_polymers.py @@ -50,7 +50,7 @@ def test_diffusion_coefficients(self): # polymer diffusion ref_D = [0.0363, 0.0269, 0.0234] np.testing.assert_allclose(tutorial.diffusion_msd, ref_D, rtol=0.30) - np.testing.assert_allclose(tutorial.diffusion_gk, ref_D, rtol=0.15) + np.testing.assert_allclose(tutorial.diffusion_gk, ref_D, rtol=0.30) # monomer diffusion if tutorial.POLYMER_MODEL == 'Rouse': ref_D0 = tutorial.KT / tutorial.GAMMA