From da1628008a4661c7734f1413ccb7019067f2ffd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 8 Jan 2024 18:54:25 +0100 Subject: [PATCH 1/5] Cleanup Doxygen documentation Fix outdated instructions in chapter on bonded interactions. Remove automatic linking of words that collide with class names and namespaces due to the large number of false positives. --- doc/doxygen/Doxyfile.in | 2 +- src/core/TabulatedPotential.hpp | 2 +- .../bonded_interactions/bonded_coulomb.hpp | 8 +- .../bonded_interactions/bonded_coulomb_sr.hpp | 10 +-- .../bonded_interactions.dox | 76 +++++++++---------- src/core/bonded_interactions/bonded_tab.hpp | 4 +- src/core/bonded_interactions/fene.hpp | 4 +- src/core/bonded_interactions/harmonic.hpp | 4 +- src/core/bonded_interactions/quartic.hpp | 4 +- .../thermalized_bond_kernel.hpp | 2 +- src/core/ek/Solver.hpp | 18 ++--- src/core/electrostatics/mmm1d.hpp | 2 +- src/core/electrostatics/mmm1d_gpu.hpp | 2 +- src/core/electrostatics/p3m.hpp | 4 +- src/core/energy_inline.hpp | 4 +- src/core/forces_inline.hpp | 4 +- src/core/immersed_boundary/ibm_common.hpp | 2 +- src/core/io/writer/h5md_core.hpp | 2 +- src/core/lb/Solver.hpp | 32 ++++---- src/core/magnetostatics/dlc.cpp | 4 +- src/core/object-in-fluid/oif_local_forces.hpp | 4 +- src/core/observables/Observable.hpp | 2 +- src/core/observables/PidObservable.hpp | 6 +- src/core/pressure_inline.hpp | 4 +- src/core/rotation.cpp | 2 +- src/core/system/System.dox | 4 +- src/core/thermostat.hpp | 16 ++-- src/core/thermostats/brownian_inline.hpp | 16 ++-- src/core/thermostats/langevin_inline.hpp | 4 +- src/core/virtual_sites/relative.cpp | 1 + src/script_interface/Context.hpp | 2 +- .../interactions/BondedInteraction.hpp | 1 - .../observables/Observable.hpp | 2 +- .../observables/initialize.cpp | 2 +- src/shapes/include/shapes/Shape.hpp | 4 +- src/utils/include/utils/Cache.hpp | 2 +- .../include/utils/linear_interpolation.hpp | 2 +- .../utils/math/coordinate_transformation.hpp | 10 +-- src/utils/include/utils/math/vec_rotate.hpp | 2 +- .../utils/serialization/memcpy_archive.hpp | 2 +- 40 files changed, 136 insertions(+), 142 deletions(-) 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/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/bonded_coulomb.hpp b/src/core/bonded_interactions/bonded_coulomb.hpp index 9665bffe422..f341012f144 100644 --- a/src/core/bonded_interactions/bonded_coulomb.hpp +++ b/src/core/bonded_interactions/bonded_coulomb.hpp @@ -33,9 +33,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 +58,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 +74,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 { diff --git a/src/core/bonded_interactions/bonded_coulomb_sr.hpp b/src/core/bonded_interactions/bonded_coulomb_sr.hpp index d59f865c11e..2c3d5aa4f9c 100644 --- a/src/core/bonded_interactions/bonded_coulomb_sr.hpp +++ b/src/core/bonded_interactions/bonded_coulomb_sr.hpp @@ -37,7 +37,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 +67,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 +84,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, diff --git a/src/core/bonded_interactions/bonded_interactions.dox b/src/core/bonded_interactions/bonded_interactions.dox index b559a6c6996..211500de17a 100644 --- a/src/core/bonded_interactions/bonded_interactions.dox +++ b/src/core/bonded_interactions/bonded_interactions.dox @@ -34,13 +34,13 @@ * 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 * @@ -48,7 +48,7 @@ * 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. * - * 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.hpp b/src/core/bonded_interactions/bonded_tab.hpp index 70739fad20d..440c6e30bcd 100644 --- a/src/core/bonded_interactions/bonded_tab.hpp +++ b/src/core/bonded_interactions/bonded_tab.hpp @@ -120,7 +120,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 +139,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 { diff --git a/src/core/bonded_interactions/fene.hpp b/src/core/bonded_interactions/fene.hpp index 68091784336..25399a11011 100644 --- a/src/core/bonded_interactions/fene.hpp +++ b/src/core/bonded_interactions/fene.hpp @@ -70,7 +70,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 +95,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 { diff --git a/src/core/bonded_interactions/harmonic.hpp b/src/core/bonded_interactions/harmonic.hpp index d4af01869f3..bd47c14a79a 100644 --- a/src/core/bonded_interactions/harmonic.hpp +++ b/src/core/bonded_interactions/harmonic.hpp @@ -65,7 +65,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 +89,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 { diff --git a/src/core/bonded_interactions/quartic.hpp b/src/core/bonded_interactions/quartic.hpp index 24b556d26fc..5b9379cfb2b 100644 --- a/src/core/bonded_interactions/quartic.hpp +++ b/src/core/bonded_interactions/quartic.hpp @@ -63,7 +63,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 +88,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 { diff --git a/src/core/bonded_interactions/thermalized_bond_kernel.hpp b/src/core/bonded_interactions/thermalized_bond_kernel.hpp index c6301e2e6d4..ef143c593ab 100644 --- a/src/core/bonded_interactions/thermalized_bond_kernel.hpp +++ b/src/core/bonded_interactions/thermalized_bond_kernel.hpp @@ -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> diff --git a/src/core/ek/Solver.hpp b/src/core/ek/Solver.hpp index c25034fe5f1..634b1b8b510 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,17 +71,17 @@ 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; 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_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_inline.hpp b/src/core/forces_inline.hpp index f3e54f1da13..d9a95b01561 100644 --- a/src/core/forces_inline.hpp +++ b/src/core/forces_inline.hpp @@ -195,7 +195,7 @@ 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] coulomb_kernel Coulomb force kernel. * @param[in] dipoles_kernel Dipolar force kernel. * @param[in] elc_kernel ELC force correction kernel. */ @@ -287,7 +287,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/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/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/Solver.hpp b/src/core/lb/Solver.hpp index 78a707b1f35..fdf2241ffaf 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,33 @@ 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 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 +110,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 +119,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 +129,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/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/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/thermostat.hpp b/src/core/thermostat.hpp index 88878664599..2a002f39ad0 100644 --- a/src/core/thermostat.hpp +++ b/src/core/thermostat.hpp @@ -144,7 +144,7 @@ struct BaseThermostat { boost::optional m_rng_seed; }; -/** %Thermostat for Langevin dynamics. */ +/** Thermostat for Langevin dynamics. */ struct LangevinThermostat : public BaseThermostat { private: using GammaType = Thermostat::GammaType; @@ -196,7 +196,7 @@ struct LangevinThermostat : public BaseThermostat { /**@}*/ }; -/** %Thermostat for Brownian dynamics. +/** Thermostat for Brownian dynamics. * Default particle mass is assumed to be unitary in these global parameters. */ struct BrownianThermostat : public BaseThermostat { @@ -282,7 +282,7 @@ struct BrownianThermostat : public BaseThermostat { }; #ifdef NPT -/** %Thermostat for isotropic NPT dynamics. */ +/** Thermostat for isotropic NPT dynamics. */ struct IsotropicNptThermostat : public BaseThermostat { private: using GammaType = Thermostat::GammaType; @@ -319,11 +319,11 @@ struct IsotropicNptThermostat : public BaseThermostat { /**@}*/ /** @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. + /** Particle velocity rescaling noise standard deviation. * Stores @f$ \sqrt{k_B T \gamma^{0} dt} / \sigma_\eta @f$. */ double pref_noise_0; @@ -339,16 +339,16 @@ struct IsotropicNptThermostat : public BaseThermostat { }; #endif -/** %Thermostat for thermalized bonds. */ +/** Thermostat for thermalized bonds. */ struct ThermalizedBondThermostat : public BaseThermostat {}; #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); } }; diff --git a/src/core/thermostats/brownian_inline.hpp b/src/core/thermostats/brownian_inline.hpp index f010b03eeaa..3f2a74cf0ad 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,7 +143,7 @@ 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 */ @@ -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,7 +309,7 @@ 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 */ @@ -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..64935d0f4ef 100644 --- a/src/core/thermostats/langevin_inline.hpp +++ b/src/core/thermostats/langevin_inline.hpp @@ -36,7 +36,7 @@ * 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 */ @@ -66,7 +66,7 @@ friction_thermo_langevin(LangevinThermostat const &langevin, Particle const &p, * 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 */ 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/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/interactions/BondedInteraction.hpp b/src/script_interface/interactions/BondedInteraction.hpp index fa22352491e..ecffe8a9540 100644 --- a/src/script_interface/interactions/BondedInteraction.hpp +++ b/src/script_interface/interactions/BondedInteraction.hpp @@ -139,7 +139,6 @@ class BondedInteraction : public AutoParameters { }; template class BondedInteractionImpl : public BondedInteraction { - public: using CoreBondedInteraction = CoreIA; CoreBondedInteraction &get_struct() { 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/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/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: From 993cb26debd577a058680c0e316c9ae1bb70818f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Tue, 9 Jan 2024 20:41:22 +0100 Subject: [PATCH 2/5] Encapsulate bond parameters --- src/core/bonded_interactions/CMakeLists.txt | 11 +--- src/core/bonded_interactions/angle_common.hpp | 7 +-- src/core/bonded_interactions/angle_cosine.cpp | 35 ----------- src/core/bonded_interactions/angle_cosine.hpp | 14 +++-- .../bonded_interactions/angle_cossquare.cpp | 33 ---------- .../bonded_interactions/angle_cossquare.hpp | 14 +++-- .../bonded_interactions/angle_harmonic.hpp | 7 +-- .../bonded_interactions/bonded_coulomb.hpp | 7 +-- .../bonded_interactions/bonded_coulomb_sr.hpp | 7 +-- .../bonded_interaction_data.cpp | 20 +++++- .../bonded_interaction_data.hpp | 8 +++ .../bonded_interaction_utils.hpp | 6 +- .../bonded_interactions.dox | 6 +- src/core/bonded_interactions/bonded_tab.cpp | 61 ------------------- src/core/bonded_interactions/bonded_tab.hpp | 33 +++++++--- src/core/bonded_interactions/dihedral.hpp | 7 +-- src/core/bonded_interactions/fene.cpp | 37 ----------- src/core/bonded_interactions/fene.hpp | 19 +++--- src/core/bonded_interactions/harmonic.hpp | 7 +-- src/core/bonded_interactions/quartic.hpp | 7 +-- src/core/bonded_interactions/rigid_bond.cpp | 36 ----------- src/core/bonded_interactions/rigid_bond.hpp | 21 +++---- .../bonded_interactions/thermalized_bond.cpp | 47 -------------- .../bonded_interactions/thermalized_bond.hpp | 34 +++++++---- .../thermalized_bond_kernel.hpp | 5 +- .../thermalized_bond_utils.cpp | 39 ------------ .../thermalized_bond_utils.hpp | 31 ---------- src/core/global_ghost_flags.cpp | 4 +- src/core/integrate.cpp | 11 ++-- src/core/thermostat.cpp | 18 +++++- .../interactions/BondedInteraction.hpp | 9 +-- 31 files changed, 167 insertions(+), 434 deletions(-) delete mode 100644 src/core/bonded_interactions/angle_cosine.cpp delete mode 100644 src/core/bonded_interactions/angle_cossquare.cpp delete mode 100644 src/core/bonded_interactions/bonded_tab.cpp delete mode 100644 src/core/bonded_interactions/fene.cpp delete mode 100644 src/core/bonded_interactions/rigid_bond.cpp delete mode 100644 src/core/bonded_interactions/thermalized_bond.cpp delete mode 100644 src/core/bonded_interactions/thermalized_bond_utils.cpp delete mode 100644 src/core/bonded_interactions/thermalized_bond_utils.hpp 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 f341012f144..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. @@ -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 2c3d5aa4f9c..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 @@ -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 211500de17a..a7d454ce1cf 100644 --- a/src/core/bonded_interactions/bonded_interactions.dox +++ b/src/core/bonded_interactions/bonded_interactions.dox @@ -44,9 +44,9 @@ * * @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 * bond type inside the .hpp file. It needs to have the following members: 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 440c6e30bcd..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, @@ -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 25399a11011..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; @@ -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 bd47c14a79a..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. */ @@ -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 5b9379cfb2b..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. */ @@ -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 ef143c593ab..d8585a70e53 100644 --- a/src/core/bonded_interactions/thermalized_bond_kernel.hpp +++ b/src/core/bonded_interactions/thermalized_bond_kernel.hpp @@ -19,8 +19,7 @@ * along with this program. If not, see . */ -#ifndef THERMALIZED_DIST_KERNEL_H -#define THERMALIZED_DIST_KERNEL_H +#pragma once #include "thermalized_bond.hpp" @@ -88,5 +87,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/global_ghost_flags.cpp b/src/core/global_ghost_flags.cpp index ff38d61b402..3f590e700de 100644 --- a/src/core/global_ghost_flags.cpp +++ b/src/core/global_ghost_flags.cpp @@ -21,7 +21,7 @@ #include "global_ghost_flags.hpp" -#include "bonded_interactions/thermalized_bond.hpp" +#include "bonded_interactions/bonded_interaction_data.hpp" #include "collision.hpp" #include "config/config.hpp" #include "system/System.hpp" @@ -42,7 +42,7 @@ unsigned global_ghost_flags() { if (::thermo_switch & THERMO_DPD) data_parts |= Cells::DATA_PART_MOMENTUM; - if (::n_thermalized_bonds) { + if (::bonded_ia_params.get_n_thermalized_bonds()) { data_parts |= Cells::DATA_PART_MOMENTUM; data_parts |= Cells::DATA_PART_BONDS; } diff --git a/src/core/integrate.cpp b/src/core/integrate.cpp index 9b2bcd50bb2..e2c944c7f1a 100644 --- a/src/core/integrate.cpp +++ b/src/core/integrate.cpp @@ -39,7 +39,7 @@ #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" @@ -416,6 +416,9 @@ 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(); @@ -497,7 +500,7 @@ 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 @@ -527,7 +530,7 @@ int System::System::integrate(int n_steps, int reuse_forces) { #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 @@ -571,7 +574,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 diff --git a/src/core/thermostat.cpp b/src/core/thermostat.cpp index cc44101ac32..ef92a2e185f 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,6 +33,8 @@ #include "system/System.hpp" #include "thermostat.hpp" +#include + #include #include @@ -95,9 +97,11 @@ REGISTER_THERMOSTAT_CALLBACKS(dpd) REGISTER_THERMOSTAT_CALLBACKS(stokesian) #endif +static void thermalized_bond_init(double time_step); + void thermo_init(double time_step) { // initialize thermalized bond regardless of the current thermostat - if (n_thermalized_bonds) { + if (::bonded_ia_params.get_n_thermalized_bonds()) { thermalized_bond_init(time_step); } if (thermo_switch == THERMO_OFF) { @@ -140,7 +144,7 @@ void philox_counter_increment() { stokesian.rng_increment(); } #endif - if (n_thermalized_bonds) { + if (::bonded_ia_params.get_n_thermalized_bonds()) { thermalized_bond.rng_increment(); } } @@ -222,3 +226,11 @@ void mpi_set_nptiso_gammas(double gamma0, double gammav) { mpi_call_all(mpi_set_nptiso_gammas_local, gamma0, gammav); } #endif + +void thermalized_bond_init(double time_step) { + for (auto &kv : ::bonded_ia_params) { + if (auto *bond = boost::get(&(*kv.second))) { + bond->recalc_prefactors(time_step); + } + } +} diff --git a/src/script_interface/interactions/BondedInteraction.hpp b/src/script_interface/interactions/BondedInteraction.hpp index ecffe8a9540..2c04ad1055d 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; } @@ -678,5 +677,3 @@ class VirtualBond : public BondedInteractionImpl<::VirtualBond> { } // namespace Interactions } // namespace ScriptInterface - -#endif From 9550aa1f24b215c090717a6b870b293293e9f535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Wed, 10 Jan 2024 23:33:01 +0100 Subject: [PATCH 3/5] Encapsulate thermostats API changes: thermalized bond random number generator seed moved to the thermostat class to avoid accidentally modifying the global seed, LB thermostat seed parameter is now interpreted as the RNG seed instead of the RNG Philox counter. --- doc/sphinx/integration.rst | 15 +- doc/sphinx/inter_bonded.rst | 3 +- doc/tutorials/polymers/polymers.ipynb | 5 +- samples/dancing.py | 1 + samples/drude_bmimpf6.py | 3 +- samples/load_checkpoint.py | 4 - src/core/CMakeLists.txt | 1 - src/core/PropagationMode.hpp | 20 +- .../thermalized_bond_kernel.hpp | 4 +- src/core/cell_system/CellStructure.cpp | 25 +- src/core/cell_system/HybridDecomposition.cpp | 12 +- src/core/cell_system/HybridDecomposition.hpp | 8 +- src/core/constraints/ShapeBasedConstraint.cpp | 18 +- src/core/dpd.cpp | 14 +- src/core/dpd.hpp | 16 +- src/core/ek/EKNone.hpp | 1 + src/core/ek/EKWalberla.cpp | 9 + src/core/ek/EKWalberla.hpp | 9 +- src/core/ek/Solver.cpp | 6 + src/core/ek/Solver.hpp | 5 + src/core/energy.cpp | 3 +- src/core/forces.cpp | 22 +- src/core/forces_inline.hpp | 8 +- src/core/ghosts.cpp | 36 +- src/core/ghosts.hpp | 14 +- src/core/global_ghost_flags.cpp | 57 -- src/core/integrate.cpp | 94 ++- src/core/integrators/Propagation.hpp | 2 +- .../integrators/stokesian_dynamics_inline.hpp | 6 +- src/core/integrators/velocity_verlet_npt.cpp | 32 +- src/core/integrators/velocity_verlet_npt.hpp | 9 +- src/core/lb/LBNone.hpp | 1 + src/core/lb/LBWalberla.cpp | 11 + src/core/lb/LBWalberla.hpp | 9 +- src/core/lb/Solver.cpp | 7 + src/core/lb/Solver.hpp | 5 + src/core/lb/particle_coupling.cpp | 180 ++--- src/core/lb/particle_coupling.hpp | 100 +-- src/core/stokesian_dynamics/sd_interface.cpp | 17 +- src/core/stokesian_dynamics/sd_interface.hpp | 7 +- src/core/system/System.cpp | 50 +- src/core/system/System.hpp | 17 +- src/core/system/System.impl.hpp | 1 + src/core/thermostat.cpp | 192 +---- src/core/thermostat.hpp | 237 +++--- src/core/thermostats/brownian_inline.hpp | 4 +- src/core/thermostats/langevin_inline.hpp | 12 +- .../EspressoSystemStandAlone_test.cpp | 12 + src/core/unit_tests/Verlet_list_test.cpp | 15 +- src/core/unit_tests/ek_interface_test.cpp | 23 +- .../unit_tests/lb_particle_coupling_test.cpp | 158 ++-- src/core/unit_tests/thermostats_test.cpp | 4 +- src/core/virtual_sites/lb_tracers.cpp | 57 +- src/core/virtual_sites/lb_tracers.hpp | 10 +- src/python/espressomd/interactions.py | 18 +- src/python/espressomd/system.py | 1 - src/python/espressomd/thermostat.pxd | 122 --- src/python/espressomd/thermostat.py | 187 +++++ src/python/espressomd/thermostat.pyx | 663 ---------------- src/script_interface/CMakeLists.txt | 1 + src/script_interface/initialize.cpp | 2 + .../interactions/BondedInteraction.hpp | 40 - src/script_interface/system/System.cpp | 3 + .../thermostat/CMakeLists.txt | 20 + .../thermostat/initialize.cpp | 47 ++ .../thermostat/initialize.hpp} | 16 +- .../thermostat/thermostat.hpp | 731 ++++++++++++++++++ src/utils/include/utils/Counter.hpp | 5 +- .../reactions/EKReactionBase.hpp | 4 +- testsuite/python/CMakeLists.txt | 2 +- testsuite/python/dpd.py | 21 +- testsuite/python/drude.py | 3 +- testsuite/python/ek_interface.py | 7 +- testsuite/python/field_test.py | 10 +- testsuite/python/integrator_exceptions.py | 76 ++ .../python/interactions_bonded_interface.py | 8 - testsuite/python/lb.py | 19 +- testsuite/python/lb_thermostat.py | 6 +- testsuite/python/long_range_actors.py | 3 + testsuite/python/propagation_langevin.py | 47 +- testsuite/python/propagation_lb.py | 104 ++- testsuite/python/propagation_npt.py | 14 +- testsuite/python/propagation_stokesian.py | 20 +- testsuite/python/save_checkpoint.py | 45 +- testsuite/python/test_checkpoint.py | 114 ++- testsuite/python/thermalized_bond.py | 35 +- testsuite/python/virtual_sites_relative.py | 23 +- .../python/virtual_sites_tracers_common.py | 10 +- testsuite/scripts/tutorials/test_polymers.py | 2 +- 89 files changed, 2164 insertions(+), 1856 deletions(-) delete mode 100644 src/core/global_ghost_flags.cpp delete mode 100644 src/python/espressomd/thermostat.pxd create mode 100644 src/python/espressomd/thermostat.py delete mode 100644 src/python/espressomd/thermostat.pyx create mode 100644 src/script_interface/thermostat/CMakeLists.txt create mode 100644 src/script_interface/thermostat/initialize.cpp rename src/{core/global_ghost_flags.hpp => script_interface/thermostat/initialize.hpp} (71%) create mode 100644 src/script_interface/thermostat/thermostat.hpp 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/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/bonded_interactions/thermalized_bond_kernel.hpp b/src/core/bonded_interactions/thermalized_bond_kernel.hpp index d8585a70e53..6f0dc132f2a 100644 --- a/src/core/bonded_interactions/thermalized_bond_kernel.hpp +++ b/src/core/bonded_interactions/thermalized_bond_kernel.hpp @@ -25,6 +25,7 @@ #include "Particle.hpp" #include "random.hpp" +#include "system/System.hpp" #include "thermostat.hpp" #include @@ -54,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( 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 634b1b8b510..d5cc229d9d1 100644 --- a/src/core/ek/Solver.hpp +++ b/src/core/ek/Solver.hpp @@ -85,6 +85,11 @@ struct Solver : public System::Leaf { */ 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/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/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 d9a95b01561..f6d643d1e71 100644 --- a/src/core/forces_inline.hpp +++ b/src/core/forces_inline.hpp @@ -195,6 +195,8 @@ 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] 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. @@ -202,6 +204,7 @@ inline ParticleForce calc_opposing_force(ParticleForce const &pf, 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; } 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 3f590e700de..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/bonded_interaction_data.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 (::bonded_ia_params.get_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/integrate.cpp b/src/core/integrate.cpp index e2c944c7f1a..61b925b2669 100644 --- a/src/core/integrate.cpp +++ b/src/core/integrate.cpp @@ -46,7 +46,6 @@ #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 } @@ -421,7 +448,7 @@ int System::System::integrate(int n_steps, int reuse_forces) { #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(); @@ -436,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()) { @@ -445,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 @@ -460,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; @@ -506,7 +533,7 @@ int System::System::integrate(int n_steps, int reuse_forces) { 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; @@ -526,7 +553,7 @@ 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 @@ -551,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); } @@ -602,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; @@ -610,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; @@ -619,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/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 fdf2241ffaf..e6012696d0c 100644 --- a/src/core/lb/Solver.hpp +++ b/src/core/lb/Solver.hpp @@ -79,6 +79,11 @@ struct Solver : public System::Leaf { */ void veto_time_step(double time_step) const; + /** + * @brief Check if a thermostat is compatible with the LB temperature. + */ + void veto_kT(double kT) const; + /** * @brief Perform LB LEbc parameter checks. */ 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..bacdd1061ff 100644 --- a/src/core/lb/particle_coupling.hpp +++ b/src/core/lb/particle_coupling.hpp @@ -20,55 +20,30 @@ #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 +58,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 +74,40 @@ 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, double kT = -1.) + : m_thermostat{thermostat}, m_lb{lb}, m_box_geo{box_geo}, + m_local_box{local_box}, m_time_step{time_step} { + assert(kT >= 0. or kT == -1.); /* 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.; + kT = (kT >= 0.) ? 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 +131,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/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.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 ef92a2e185f..ed55999f4ff 100644 --- a/src/core/thermostat.cpp +++ b/src/core/thermostat.cpp @@ -35,199 +35,69 @@ #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 - -static void thermalized_bond_init(double time_step); - -void thermo_init(double time_step) { - // initialize thermalized bond regardless of the current thermostat - if (::bonded_ia_params.get_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 (::bonded_ia_params.get_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); -} -#endif - -void thermalized_bond_init(double time_step) { +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); diff --git a/src/core/thermostat.hpp b/src/core/thermostat.hpp index 2a002f39ad0..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,30 +86,45 @@ 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. */ @@ -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,24 +157,28 @@ 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 /**@}*/ }; @@ -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,21 +245,25 @@ 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 /**@}*/ }; @@ -313,34 +299,47 @@ 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. * Stores @f$ \gamma^{0}\cdot\frac{dt}{2} @f$. */ - double pref_rescale_0; + 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 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 {}; +struct ThermalizedBondThermostat : public BaseThermostat { + void recalc_prefactors(double time_step); +}; #ifdef DPD /** Thermostat for dissipative particle dynamics. */ @@ -354,66 +353,38 @@ struct StokesianThermostat : public BaseThermostat { }; #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 3f2a74cf0ad..4d70e5977f8 100644 --- a/src/core/thermostats/brownian_inline.hpp +++ b/src/core/thermostats/brownian_inline.hpp @@ -145,7 +145,7 @@ inline Utils::Vector3d bd_drag_vel(Thermostat::GammaType const &brownian_gamma, * @param[in] brownian Parameters * @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) { @@ -311,7 +311,7 @@ bd_drag_vel_rot(Thermostat::GammaType const &brownian_gamma_rotation, * @param[in] brownian Parameters * @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, diff --git a/src/core/thermostats/langevin_inline.hpp b/src/core/thermostats/langevin_inline.hpp index 64935d0f4ef..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] 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] 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..2d4e6893dc5 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,37 @@ 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; + thermostat.rng_initialize(17u); + thermostat.set_rng_counter(11ul); + thermostat.gamma = 0.2; + + LB::ParticleCoupling coupling{thermostat, lb, box_geo, local_box, + params.time_step, 1.}; + 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 +197,17 @@ 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.}; + LB::ParticleCoupling coupling_unthermalized{ + thermostat, lb, box_geo, local_box, params.time_step, 0.}; auto const step3_norandom = coupling_unthermalized.get_noise_term(test_partcl_2); BOOST_CHECK((step3_norandom == Utils::Vector3d{0., 0., 0.})); @@ -218,7 +216,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 +234,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 +253,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 +266,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 +289,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 +300,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 +311,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 +344,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 +352,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 +383,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 +397,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 +429,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 +438,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 +456,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 +467,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 +478,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 +509,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 +518,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 +580,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 +617,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 +639,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/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/system.py b/src/python/espressomd/system.py index 1fd8802017c..ed4e594b655 100644 --- a/src/python/espressomd/system.py +++ b/src/python/espressomd/system.py @@ -245,7 +245,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") 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/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/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 2c04ad1055d..9843ed82ff5 100644 --- a/src/script_interface/interactions/BondedInteraction.hpp +++ b/src/script_interface/interactions/BondedInteraction.hpp @@ -409,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>( @@ -433,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; } }; 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/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..1125f04200e --- /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{}; + try { + gamma = T::broadcast(get_value(v)); + } catch (...) { + 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/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/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/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/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/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 From d5b68bc68cba803b917a1f80bf08804da16c38dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 11 Jan 2024 00:19:57 +0100 Subject: [PATCH 4/5] Remove legacy Cython features Several Cython features have been deprecated in Cython 3.0 and will generate errors in future Cython releases. This change removes .pxi include files, all `IF` conditional statements, and cpdef functions. Almost all Cython 3.0 deprecation warnings were addressed, a silent API change from Python 3.11 was mitigated, and some of the custom Cython code for type checking was replaced by equivalent C++ code. --- .github/actions/build_and_check/action.yml | 2 +- CMakeLists.txt | 2 +- maintainer/CI/build_cmake.sh | 2 +- requirements.txt | 2 +- src/core/electrostatics/icc.cpp | 17 +-- src/python/espressomd/CMakeLists.txt | 21 +--- .../espressomd/electrostatic_extensions.py | 43 ------- src/python/espressomd/gen_pxiconfig.py | 69 ----------- src/python/espressomd/particle_data.py | 3 +- src/python/espressomd/script_interface.pxd | 8 +- src/python/espressomd/script_interface.pyx | 10 +- src/python/espressomd/system.py | 16 --- src/python/espressomd/utils.pxd | 7 -- src/python/espressomd/utils.pyx | 110 +++++++----------- .../electrostatics/ICCStar.hpp | 12 +- src/script_interface/get_value.hpp | 44 ++++--- src/script_interface/tests/get_value_test.cpp | 2 +- .../walberla/LatticeWalberla.hpp | 2 +- testsuite/python/box_geometry.py | 4 +- testsuite/python/coulomb_interface.py | 2 +- testsuite/python/icc_interface.py | 24 +++- testsuite/python/pairs.py | 2 +- testsuite/python/particle.py | 2 + 23 files changed, 139 insertions(+), 267 deletions(-) delete mode 100644 src/python/espressomd/gen_pxiconfig.py 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/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/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/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/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 ed4e594b655..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) @@ -428,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/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/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/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/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/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/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/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( From d84950c542e1441977cd5bd209da2413f4519236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Fri, 19 Jan 2024 15:20:06 +0100 Subject: [PATCH 5/5] core: apply code review suggestions --- src/core/lb/particle_coupling.hpp | 6 ++---- src/core/unit_tests/lb_particle_coupling_test.cpp | 10 ++++++---- src/script_interface/thermostat/thermostat.hpp | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/lb/particle_coupling.hpp b/src/core/lb/particle_coupling.hpp index bacdd1061ff..d5e7cdf3a5b 100644 --- a/src/core/lb/particle_coupling.hpp +++ b/src/core/lb/particle_coupling.hpp @@ -32,7 +32,6 @@ #include #include -#include #include #include #include @@ -93,17 +92,16 @@ class ParticleCoupling { public: ParticleCoupling(LBThermostat const &thermostat, LB::Solver &lb, BoxGeometry const &box_geo, LocalBox const &local_box, - double time_step, double kT = -1.) + double time_step) : m_thermostat{thermostat}, m_lb{lb}, m_box_geo{box_geo}, m_local_box{local_box}, m_time_step{time_step} { - assert(kT >= 0. or kT == -1.); /* 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.; - kT = (kT >= 0.) ? kT : lb.get_kT() * Utils::sqr(lb.get_lattice_speed()); + 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); } diff --git a/src/core/unit_tests/lb_particle_coupling_test.cpp b/src/core/unit_tests/lb_particle_coupling_test.cpp index 2d4e6893dc5..542587dc89f 100644 --- a/src/core/unit_tests/lb_particle_coupling_test.cpp +++ b/src/core/unit_tests/lb_particle_coupling_test.cpp @@ -175,12 +175,13 @@ BOOST_AUTO_TEST_CASE(rng) { 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, - params.time_step, 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()); @@ -206,8 +207,9 @@ BOOST_AUTO_TEST_CASE(rng) { BOOST_CHECK(step1_random1 != step2_random1); BOOST_CHECK(step1_random1 != step2_random2); - LB::ParticleCoupling coupling_unthermalized{ - thermostat, lb, box_geo, local_box, 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.})); diff --git a/src/script_interface/thermostat/thermostat.hpp b/src/script_interface/thermostat/thermostat.hpp index 1125f04200e..c2494362d8e 100644 --- a/src/script_interface/thermostat/thermostat.hpp +++ b/src/script_interface/thermostat/thermostat.hpp @@ -146,9 +146,9 @@ class Interface : public AutoParameters, System::Leaf> { #ifdef PARTICLE_ANISOTROPY static_assert(std::is_same_v); T gamma{}; - try { + if (is_type(v) or is_type(v)) { gamma = T::broadcast(get_value(v)); - } catch (...) { + } else { gamma = get_value(v); } #else