diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eb0f5c136dd..5de0d50577a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -438,6 +438,8 @@ empty: with_static_analysis: 'true' with_scafacos: 'false' with_stokesian_dynamics: 'false' + with_coverage: 'false' + with_coverage_python: 'true' script: - bash maintainer/CI/build_cmake.sh tags: diff --git a/doc/sphinx/inter_non-bonded.rst b/doc/sphinx/inter_non-bonded.rst index e7c07177d38..f7613713e6c 100644 --- a/doc/sphinx/inter_non-bonded.rst +++ b/doc/sphinx/inter_non-bonded.rst @@ -18,23 +18,41 @@ explicitly set. A bonded interaction between a set of particles has to be specified explicitly by the command, while the command is used to define the interaction parameters. -.. _Isotropic non-bonded interactions: +Non-bonded interaction are configured via the +:class:`espressomd.interactions.NonBondedInteraction` class, +which is a member of :class:`espressomd.system.System`:: -Isotropic non-bonded interactions ---------------------------------- + system.non_bonded_inter[type1, type2] -Non-bonded interaction are configured via the :class:`espressomd.interactions.NonBondedInteraction` class, which is a member of :class:`espressomd.system.System`:: +This command defines an interaction between all particles of type ``type1`` +and ``type2``. All available interaction potentials and their parameters are +listed below. For example, the following adds a WCA potential between +particles of type 0:: - system.non_bonded_inter[type1, type2] + system.non_bonded_inter[0, 0].wca.set_params(epsilon=1., sigma=2.) + +Each type pair can have multiple potentials active at the same time. +To deactivate a specific potential for a given type pair, do:: + + system.non_bonded_inter[0, 0].wca.deactivate() + +To deactivate all potentials for a given type pair, do:: -This command defines an interaction between all particles of type ``type1`` and -``type2``. Possible interaction types and their parameters are -listed below. + system.non_bonded_inter[0, 0].reset() + +To deactivate all potentials between all type pairs, do:: + + system.non_bonded_inter.reset() For many non-bonded interactions, it is possible to artificially cap the forces, which often allows to equilibrate the system much faster. See the subsection :ref:`Capping the force during warmup` for more details. +.. _Isotropic non-bonded interactions: + +Isotropic non-bonded interactions +--------------------------------- + .. _Tabulated interaction: Tabulated interaction diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 9426568be13..852b30d5f4e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -55,7 +55,8 @@ add_library( virtual_sites.cpp exclusions.cpp PartCfg.cpp - EspressoSystemStandAlone.cpp) + EspressoSystemStandAlone.cpp + TabulatedPotential.cpp) add_library(espresso::core ALIAS espresso_core) if(CUDA) diff --git a/src/core/TabulatedPotential.cpp b/src/core/TabulatedPotential.cpp new file mode 100644 index 00000000000..2ba8a6e61a1 --- /dev/null +++ b/src/core/TabulatedPotential.cpp @@ -0,0 +1,55 @@ +/* + * 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 "TabulatedPotential.hpp" + +#include +#include + +TabulatedPotential::TabulatedPotential(double minval, double maxval, + std::vector const &force, + std::vector const &energy) + : minval{minval}, maxval{maxval} { + + if (minval > maxval) { + throw std::domain_error("TabulatedPotential parameter 'max' must be " + "larger than or equal to parameter 'min'"); + } + if (minval != -1.) { + if (minval == maxval and force.size() != 1) { + throw std::domain_error( + "TabulatedPotential parameter 'force' must contain 1 element"); + } + if (force.empty()) { + throw std::domain_error("TabulatedPotential parameter 'force' must " + "contain at least 1 element"); + } + if (force.size() != energy.size()) { + throw std::invalid_argument("TabulatedPotential parameter 'force' must " + "have the same size as parameter 'energy'"); + } + invstepsize = static_cast(force.size() - 1) / (maxval - minval); + } else { + invstepsize = 0.; + } + force_tab = force; + energy_tab = energy; +} diff --git a/src/core/TabulatedPotential.hpp b/src/core/TabulatedPotential.hpp index f05a4db85fe..e63a3bef9d4 100644 --- a/src/core/TabulatedPotential.hpp +++ b/src/core/TabulatedPotential.hpp @@ -25,7 +25,6 @@ #include #include -#include #include /** Evaluate forces and energies using a custom potential profile. @@ -46,6 +45,11 @@ struct TabulatedPotential { /** Tabulated energies. */ std::vector energy_tab; + TabulatedPotential() = default; + TabulatedPotential(double minval, double maxval, + std::vector const &force, + std::vector const &energy); + /** Evaluate the force at position @p x. * @param x Bond length/angle * @return Interpolated force. @@ -67,17 +71,6 @@ struct TabulatedPotential { } double cutoff() const { return maxval; } - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar &minval; - ar &maxval; - ar &invstepsize; - ar &force_tab; - ar &energy_tab; - } }; #endif diff --git a/src/core/bonded_interactions/bonded_interaction_data.hpp b/src/core/bonded_interactions/bonded_interaction_data.hpp index e25f403af06..2f006186cec 100644 --- a/src/core/bonded_interactions/bonded_interaction_data.hpp +++ b/src/core/bonded_interactions/bonded_interaction_data.hpp @@ -133,6 +133,9 @@ class BondedInteractionsMap { bool empty() const { return m_params.empty(); } auto size() const { return m_params.size(); } auto get_next_key() const { return next_key; } + auto get_zero_based_type(int bond_id) const { + return contains(bond_id) ? at(bond_id)->which() : 0; + } private: container_type m_params = {}; diff --git a/src/core/bonded_interactions/bonded_interactions.dox b/src/core/bonded_interactions/bonded_interactions.dox index 5685d6ed817..ab7eb29a285 100644 --- a/src/core/bonded_interactions/bonded_interactions.dox +++ b/src/core/bonded_interactions/bonded_interactions.dox @@ -243,37 +243,27 @@ * * @subsection bondedIA_new_interface Adding the interaction in the Python interface * - * Please note that the following is Cython code (www.cython.org), rather than - * pure Python. - * - * * In file src/python/espressomd/interactions.pxd: - * - Add the bonded interaction to \c enum_bonded_interaction. + * * In file src/python/espressomd/interactions.py: + * - 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} - * cdef enum enum_bonded_interaction: - * BONDED_IA_NONE = 0, - * BONDED_IA_FENE, - * [...] + * class BONDED_IA(enum.IntEnum): + * NONE = 0 + * FENE = enum.auto() + * HARMONIC = enum.auto() + * # ... * @endcode - * * In file src/python/espressomd/interactions.pyx: - * - Implement the Cython class for the bonded interaction, using the one + * - Implement the Python class for the bonded interaction, using the one * for the FENE bond as template. Please use pep8 naming convention. - * - Set the class' member + * - Set the class' members * @code{.py} * _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. - * - Create a new entry in the dictionary \c bonded_interaction_classes to - * register the new class you have written: - * @code{.py} - * bonded_interaction_classes = { - * int(BONDED_IA_FENE): FeneBond, - * int(BONDED_IA_HARMONIC): HarmonicBond, - * [...] - * } - * @endcode + * The type number is the new enum value from \c BONDED_IA. * * In file testsuite/python/interactions_bonded_interface.py: * - Add a test case to verify that parameters set and gotten from the * interaction are consistent. diff --git a/src/core/bonded_interactions/bonded_tab.cpp b/src/core/bonded_interactions/bonded_tab.cpp index ff86e49b0af..74762482f91 100644 --- a/src/core/bonded_interactions/bonded_tab.cpp +++ b/src/core/bonded_interactions/bonded_tab.cpp @@ -30,20 +30,7 @@ TabulatedBond::TabulatedBond(double min, double max, std::vector const &energy, std::vector const &force) { - assert(max >= min); - assert((max == min) || force.size() > 1); - assert(force.size() == energy.size()); - - auto tab_pot = this->pot = std::make_shared(); - - /* set table limits */ - tab_pot->minval = min; - tab_pot->maxval = max; - - tab_pot->invstepsize = static_cast(force.size() - 1) / (max - min); - - tab_pot->force_tab = force; - tab_pot->energy_tab = energy; + pot = std::make_shared(min, max, force, energy); } TabulatedDistanceBond::TabulatedDistanceBond(double min, double max, diff --git a/src/core/dpd.cpp b/src/core/dpd.cpp index 8a22f988d8c..52a427b743e 100644 --- a/src/core/dpd.cpp +++ b/src/core/dpd.cpp @@ -64,28 +64,15 @@ Vector3d dpd_noise(int pid1, int pid2) { (pid1 < pid2) ? pid1 : pid2); } -int dpd_set_params(int part_type_a, int part_type_b, double gamma, double k, - double r_c, int wf, double tgamma, double tr_c, int twf) { - auto &ia_params = *get_ia_param_safe(part_type_a, part_type_b); - - ia_params.dpd_radial = DPDParameters{gamma, k, r_c, wf, -1.}; - ia_params.dpd_trans = DPDParameters{tgamma, k, tr_c, twf, -1.}; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; -} - void dpd_init(double kT, double time_step) { for (int type_a = 0; type_a < max_seen_particle_type; type_a++) { for (int type_b = 0; type_b < max_seen_particle_type; type_b++) { IA_parameters &ia_params = get_ia_param(type_a, type_b); - ia_params.dpd_radial.pref = - sqrt(24.0 * kT * ia_params.dpd_radial.gamma / time_step); - ia_params.dpd_trans.pref = - sqrt(24.0 * kT * ia_params.dpd_trans.gamma / time_step); + ia_params.dpd.radial.pref = + sqrt(24.0 * kT * ia_params.dpd.radial.gamma / time_step); + ia_params.dpd.trans.pref = + sqrt(24.0 * kT * ia_params.dpd.trans.gamma / time_step); } } } @@ -116,19 +103,19 @@ Utils::Vector3d dpd_pair_force(Particle const &p1, Particle const &p2, 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) { + if (ia_params.dpd.radial.cutoff <= 0.0 && ia_params.dpd.trans.cutoff <= 0.0) { return {}; } 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) + (ia_params.dpd.radial.pref > 0.0 || ia_params.dpd.trans.pref > 0.0) ? dpd_noise(p1.id(), p2.id()) : Vector3d{}; - auto const f_r = dpd_pair_force(ia_params.dpd_radial, v21, dist, noise_vec); - auto const f_t = dpd_pair_force(ia_params.dpd_trans, v21, dist, noise_vec); + auto const f_r = dpd_pair_force(ia_params.dpd.radial, v21, dist, noise_vec); + auto const f_t = dpd_pair_force(ia_params.dpd.trans, v21, dist, noise_vec); /* Projection operator to radial direction */ auto const P = tensor_product(d / dist2, d); @@ -150,8 +137,8 @@ static auto dpd_viscous_stress_local() { auto const &ia_params = get_ia_param(p1.type(), p2.type()); auto const dist = std::sqrt(d.dist2); - auto const f_r = dpd_pair_force(ia_params.dpd_radial, v21, dist, {}); - auto const f_t = dpd_pair_force(ia_params.dpd_trans, v21, dist, {}); + auto const f_r = dpd_pair_force(ia_params.dpd.radial, v21, dist, {}); + auto const f_t = dpd_pair_force(ia_params.dpd.trans, v21, dist, {}); /* Projection operator to radial direction */ auto const P = tensor_product(d.vec21 / d.dist2, d.vec21); @@ -188,4 +175,5 @@ Utils::Vector9d dpd_stress() { return Utils::flatten(stress) / volume; } -#endif + +#endif // DPD diff --git a/src/core/dpd.hpp b/src/core/dpd.hpp index ea9e7a7e42c..e921e67da9f 100644 --- a/src/core/dpd.hpp +++ b/src/core/dpd.hpp @@ -36,8 +36,6 @@ struct IA_parameters; -int dpd_set_params(int part_type_a, int part_type_b, double gamma, double k, - double r_c, int wf, double tgamma, double tr_c, int twf); void dpd_init(double kT, double time_step); Utils::Vector3d dpd_pair_force(Particle const &p1, Particle const &p2, diff --git a/src/core/event.cpp b/src/core/event.cpp index 1e1ea80524c..0c5a750158d 100644 --- a/src/core/event.cpp +++ b/src/core/event.cpp @@ -251,6 +251,11 @@ void on_dipoles_change() { on_short_range_ia_change(); } +void on_non_bonded_ia_change() { + maximal_cutoff_nonbonded(); + on_short_range_ia_change(); +} + void on_short_range_ia_change() { cells_re_init(cell_structure.decomposition_type()); recalc_forces = true; diff --git a/src/core/event.hpp b/src/core/event.hpp index 6e39cbff4ab..8f1f56c6af6 100644 --- a/src/core/event.hpp +++ b/src/core/event.hpp @@ -79,6 +79,9 @@ void on_dipoles_change(); /** called every time short ranged interaction parameters are changed. */ void on_short_range_ia_change(); +/** called every time a non-bonded interaction parameters are changed. */ +void on_non_bonded_ia_change(); + /** called every time a constraint is changed. */ void on_constraint_change(); diff --git a/src/core/interactions.cpp b/src/core/interactions.cpp index b050bedab4f..6ba4a77ee67 100644 --- a/src/core/interactions.cpp +++ b/src/core/interactions.cpp @@ -18,16 +18,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "communication.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" #include "collision.hpp" +#include "communication.hpp" #include "electrostatics/coulomb.hpp" #include "errorhandling.hpp" #include "event.hpp" #include "magnetostatics/dipoles.hpp" - -#include "serialization/IA_parameters.hpp" +#include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include @@ -77,14 +76,3 @@ bool long_range_interactions_sanity_checks() { } return false; } - -void mpi_bcast_ia_params_local(int i, int j) { - boost::mpi::broadcast(comm_cart, get_ia_param(i, j), 0); - on_short_range_ia_change(); -} - -REGISTER_CALLBACK(mpi_bcast_ia_params_local) - -void mpi_bcast_ia_params(int i, int j) { - mpi_call_all(mpi_bcast_ia_params_local, i, j); -} diff --git a/src/core/interactions.hpp b/src/core/interactions.hpp index 4399e5017bf..332891dc14a 100644 --- a/src/core/interactions.hpp +++ b/src/core/interactions.hpp @@ -32,17 +32,4 @@ double maximal_cutoff(bool single_node); */ bool long_range_interactions_sanity_checks(); -/** Send new IA params. - * Also calls \ref on_short_range_ia_change. - * - * Used for both bonded and non-bonded interaction parameters. Therefore - * @p i and @p j are used depending on their value: - * - * \param i particle type for non-bonded interaction parameters / - * bonded interaction type number. - * \param j if not negative: particle type for non-bonded interaction - * parameters / if negative: flag for bonded interaction - */ -void mpi_bcast_ia_params(int i, int j); - #endif diff --git a/src/core/nonbonded_interactions/CMakeLists.txt b/src/core/nonbonded_interactions/CMakeLists.txt index 66570208137..1e52b0fe4d0 100644 --- a/src/core/nonbonded_interactions/CMakeLists.txt +++ b/src/core/nonbonded_interactions/CMakeLists.txt @@ -12,8 +12,6 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/ljgen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/morse.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nonbonded_interaction_data.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nonbonded_tab.cpp ${CMAKE_CURRENT_SOURCE_DIR}/soft_sphere.cpp ${CMAKE_CURRENT_SOURCE_DIR}/smooth_step.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/thole.cpp ${CMAKE_CURRENT_SOURCE_DIR}/wca.cpp) diff --git a/src/core/nonbonded_interactions/bmhtf-nacl.cpp b/src/core/nonbonded_interactions/bmhtf-nacl.cpp index 74c2d0df9d0..df7975415d2 100644 --- a/src/core/nonbonded_interactions/bmhtf-nacl.cpp +++ b/src/core/nonbonded_interactions/bmhtf-nacl.cpp @@ -25,35 +25,29 @@ #include "bmhtf-nacl.hpp" #ifdef BMHTF_NACL -#include "interactions.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" -#include - -int BMHTF_set_params(int part_type_a, int part_type_b, double A, double B, - double C, double D, double sig, double cut) { - double shift, dist2, pw6; - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - dist2 = cut * cut; - pw6 = dist2 * dist2 * dist2; - shift = -(A * exp(B * (sig - cut)) - C / pw6 - D / pw6 / dist2); - - data->bmhtf.A = A; - data->bmhtf.B = B; - data->bmhtf.C = C; - data->bmhtf.D = D; - data->bmhtf.sig = sig; - data->bmhtf.cut = cut; - data->bmhtf.computed_shift = shift; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; +#include + +#include + +BMHTF_Parameters::BMHTF_Parameters(double a, double b, double c, double d, + double sig, double cutoff) + : A{a}, B{b}, C{c}, D{d}, sig{sig}, cut{cutoff} { + if (a < 0.) { + throw std::domain_error("BMHTF parameter 'a' has to be >= 0"); + } + if (c < 0.) { + throw std::domain_error("BMHTF parameter 'c' has to be >= 0"); + } + if (d < 0.) { + throw std::domain_error("BMHTF parameter 'd' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("BMHTF parameter 'cutoff' has to be >= 0"); + } + computed_shift = C / Utils::int_pow<6>(cut) + D / Utils::int_pow<8>(cut) - + A * std::exp(B * (sig - cut)); } -#endif +#endif // BMHTF_NACL diff --git a/src/core/nonbonded_interactions/bmhtf-nacl.hpp b/src/core/nonbonded_interactions/bmhtf-nacl.hpp index b5274c751ee..3926beb42ff 100644 --- a/src/core/nonbonded_interactions/bmhtf-nacl.hpp +++ b/src/core/nonbonded_interactions/bmhtf-nacl.hpp @@ -33,14 +33,10 @@ #include "nonbonded_interaction_data.hpp" -#include #include #include -int BMHTF_set_params(int part_type_a, int part_type_b, double A, double B, - double C, double D, double sig, double cut); - /** Calculate BMHTF force factor */ inline double BMHTF_pair_force_factor(IA_parameters const &ia_params, double dist) { @@ -67,5 +63,5 @@ inline double BMHTF_pair_energy(IA_parameters const &ia_params, double dist) { return 0.0; } -#endif +#endif // BMHTF_NACL #endif diff --git a/src/core/nonbonded_interactions/buckingham.cpp b/src/core/nonbonded_interactions/buckingham.cpp index b8b77565d01..7fd90cdad50 100644 --- a/src/core/nonbonded_interactions/buckingham.cpp +++ b/src/core/nonbonded_interactions/buckingham.cpp @@ -25,38 +25,35 @@ #include "buckingham.hpp" #ifdef BUCKINGHAM -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include - -int buckingham_set_params(int part_type_a, int part_type_b, double A, double B, - double C, double D, double cut, double discont, - double shift) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->buckingham.A = A; - data->buckingham.B = B; - data->buckingham.C = C; - data->buckingham.D = D; - data->buckingham.cut = cut; - data->buckingham.discont = discont; - data->buckingham.shift = shift; +#include + +Buckingham_Parameters::Buckingham_Parameters(double a, double b, double c, + double d, double cutoff, + double discont, double shift) + : A{a}, B{b}, C{c}, D{d}, cut{cutoff}, discont{discont}, shift{shift} { + if (a < 0.) { + throw std::domain_error("Buckingham parameter 'a' has to be >= 0"); + } + if (b < 0.) { + throw std::domain_error("Buckingham parameter 'b' has to be >= 0"); + } + if (c < 0.) { + throw std::domain_error("Buckingham parameter 'c' has to be >= 0"); + } + if (d < 0.) { + throw std::domain_error("Buckingham parameter 'd' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("Buckingham parameter 'cutoff' has to be >= 0"); + } /* Replace the Buckingham potential for interatomic distance less than or equal to discontinuity by a straight line (F1+F2*r) */ - auto const F = buck_force_r(A, B, C, D, discont); - data->buckingham.F1 = buck_energy_r(A, B, C, D, shift, discont) + discont * F; - data->buckingham.F2 = -F; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; + F1 = buck_energy_r(A, B, C, D, shift, discont) + discont * F; + F2 = -F; } -#endif +#endif // BUCKINGHAM diff --git a/src/core/nonbonded_interactions/buckingham.hpp b/src/core/nonbonded_interactions/buckingham.hpp index 4bafa45b974..ed9be7130c7 100644 --- a/src/core/nonbonded_interactions/buckingham.hpp +++ b/src/core/nonbonded_interactions/buckingham.hpp @@ -32,14 +32,8 @@ #include "nonbonded_interaction_data.hpp" -#include - #include -int buckingham_set_params(int part_type_a, int part_type_b, double A, double B, - double C, double D, double cut, double discont, - double shift); - /**Resultant Force due to a Buckingham potential between two particles at * interatomic separation r greater than or equal to discont*/ inline double buck_force_r(double A, double B, double C, double D, double r) { @@ -90,5 +84,5 @@ inline double buck_pair_energy(IA_parameters const &ia_params, double dist) { return 0.0; } -#endif +#endif // BUCKINGHAM #endif diff --git a/src/core/nonbonded_interactions/gaussian.cpp b/src/core/nonbonded_interactions/gaussian.cpp index 51643bcfa27..76821cf4c11 100644 --- a/src/core/nonbonded_interactions/gaussian.cpp +++ b/src/core/nonbonded_interactions/gaussian.cpp @@ -25,26 +25,20 @@ #include "gaussian.hpp" #ifdef GAUSSIAN -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include - -int gaussian_set_params(int part_type_a, int part_type_b, double eps, - double sig, double cut) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->gaussian.eps = eps; - data->gaussian.sig = sig; - data->gaussian.cut = cut; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; +#include + +Gaussian_Parameters::Gaussian_Parameters(double eps, double sig, double cutoff) + : eps{eps}, sig{sig}, cut{cutoff} { + if (eps < 0.) { + throw std::domain_error("Gaussian parameter 'eps' has to be >= 0"); + } + if (sig < 0.) { + throw std::domain_error("Gaussian parameter 'sig' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("Gaussian parameter 'cutoff' has to be >= 0"); + } } - -#endif +#endif // GAUSSIAN diff --git a/src/core/nonbonded_interactions/gaussian.hpp b/src/core/nonbonded_interactions/gaussian.hpp index 9b345ab217c..621f7abd359 100644 --- a/src/core/nonbonded_interactions/gaussian.hpp +++ b/src/core/nonbonded_interactions/gaussian.hpp @@ -31,16 +31,12 @@ #include "nonbonded_interaction_data.hpp" -#include #include #include #ifdef GAUSSIAN -int gaussian_set_params(int part_type_a, int part_type_b, double eps, - double sig, double cut); - /** Calculate Gaussian force factor */ inline double gaussian_pair_force_factor(IA_parameters const &ia_params, double dist) { diff --git a/src/core/nonbonded_interactions/gay_berne.cpp b/src/core/nonbonded_interactions/gay_berne.cpp index d5732832415..12a9a78b907 100644 --- a/src/core/nonbonded_interactions/gay_berne.cpp +++ b/src/core/nonbonded_interactions/gay_berne.cpp @@ -25,39 +25,15 @@ #include "gay_berne.hpp" #ifdef GAY_BERNE -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include +#include -int gay_berne_set_params(int part_type_a, int part_type_b, double eps, - double sig, double cut, double k1, double k2, - double mu, double nu) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); +GayBerne_Parameters::GayBerne_Parameters(double eps, double sig, double cut, + double k1, double k2, double mu, + double nu) + : eps{eps}, sig{sig}, cut{cut}, k1{k1}, k2{k2}, mu{mu}, nu{nu}, + chi1{((k1 * k1) - 1.) / ((k1 * k1) + 1.)}, + chi2{(std::pow(k2, 1. / mu) - 1.) / (std::pow(k2, 1. / mu) + 1.)} {} - if (!data) - return ES_ERROR; - - data->gay_berne.eps = eps; - data->gay_berne.sig = sig; - data->gay_berne.cut = cut; - data->gay_berne.k1 = k1; - data->gay_berne.k2 = k2; - data->gay_berne.mu = mu; - data->gay_berne.nu = nu; - - /* Calculate dependent parameters */ - - data->gay_berne.chi1 = ((data->gay_berne.k1 * data->gay_berne.k1) - 1) / - ((data->gay_berne.k1 * data->gay_berne.k1) + 1); - data->gay_berne.chi2 = - (pow(data->gay_berne.k2, (1 / data->gay_berne.mu)) - 1) / - (pow(data->gay_berne.k2, (1 / data->gay_berne.mu)) + 1); - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; -} - -#endif +#endif // GAY_BERNE diff --git a/src/core/nonbonded_interactions/gay_berne.hpp b/src/core/nonbonded_interactions/gay_berne.hpp index 2804df031a0..93e707b0055 100644 --- a/src/core/nonbonded_interactions/gay_berne.hpp +++ b/src/core/nonbonded_interactions/gay_berne.hpp @@ -45,10 +45,6 @@ #include -int gay_berne_set_params(int part_type_a, int part_type_b, double eps, - double sig, double cut, double k1, double k2, - double mu, double nu); - /** Calculate Gay-Berne force and torques */ inline ParticleForce gb_pair_force(Utils::Quaternion const &qi, Utils::Quaternion const &qj, diff --git a/src/core/nonbonded_interactions/hat.cpp b/src/core/nonbonded_interactions/hat.cpp index c861851a6d8..00dd4661ecc 100644 --- a/src/core/nonbonded_interactions/hat.cpp +++ b/src/core/nonbonded_interactions/hat.cpp @@ -25,24 +25,18 @@ #include "hat.hpp" #ifdef HAT -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include +#include -int hat_set_params(int part_type_a, int part_type_b, double Fmax, double r) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->hat.Fmax = Fmax; - data->hat.r = r; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; +Hat_Parameters::Hat_Parameters(double F_max, double cutoff) + : Fmax{F_max}, r{cutoff} { + if (F_max < 0.) { + throw std::domain_error("Hat parameter 'F_max' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("Hat parameter 'cutoff' has to be >= 0"); + } } -#endif +#endif // HAT diff --git a/src/core/nonbonded_interactions/hat.hpp b/src/core/nonbonded_interactions/hat.hpp index bc9785006d2..3128d77ff4b 100644 --- a/src/core/nonbonded_interactions/hat.hpp +++ b/src/core/nonbonded_interactions/hat.hpp @@ -33,14 +33,10 @@ #include "nonbonded_interaction_data.hpp" -#include - -int hat_set_params(int part_type_a, int part_type_b, double Fmax, double r); - /** Calculate hat force factor */ inline double hat_pair_force_factor(IA_parameters const &ia_params, double dist) { - if (dist > 0. && dist < ia_params.hat.r) { + if (dist != 0. and dist < ia_params.hat.r) { return ia_params.hat.Fmax * (1.0 - dist / ia_params.hat.r) / dist; } return 0.0; @@ -55,5 +51,5 @@ inline double hat_pair_energy(IA_parameters const &ia_params, double dist) { return 0.0; } -#endif /* ifdef HAT */ +#endif // HAT #endif diff --git a/src/core/nonbonded_interactions/hertzian.cpp b/src/core/nonbonded_interactions/hertzian.cpp index 025f513b9e5..9ee911d0757 100644 --- a/src/core/nonbonded_interactions/hertzian.cpp +++ b/src/core/nonbonded_interactions/hertzian.cpp @@ -25,25 +25,18 @@ #include "hertzian.hpp" #ifdef HERTZIAN -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include +#include -int hertzian_set_params(int part_type_a, int part_type_b, double eps, - double sig) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->hertzian.eps = eps; - data->hertzian.sig = sig; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; +Hertzian_Parameters::Hertzian_Parameters(double eps, double sig) + : eps{eps}, sig{sig} { + if (eps < 0.) { + throw std::domain_error("Hertzian parameter 'eps' has to be >= 0"); + } + if (sig < 0.) { + throw std::domain_error("Hertzian parameter 'sig' has to be >= 0"); + } } -#endif +#endif // HERTZIAN diff --git a/src/core/nonbonded_interactions/hertzian.hpp b/src/core/nonbonded_interactions/hertzian.hpp index 767c401dc6d..89dc3bebc23 100644 --- a/src/core/nonbonded_interactions/hertzian.hpp +++ b/src/core/nonbonded_interactions/hertzian.hpp @@ -31,15 +31,10 @@ #include "nonbonded_interaction_data.hpp" -#include - #include #ifdef HERTZIAN -int hertzian_set_params(int part_type_a, int part_type_b, double eps, - double sig); - /** Calculate Hertzian force factor */ inline double hertzian_pair_force_factor(IA_parameters const &ia_params, double dist) { diff --git a/src/core/nonbonded_interactions/lj.cpp b/src/core/nonbonded_interactions/lj.cpp index ebc988949be..afb8a63b341 100644 --- a/src/core/nonbonded_interactions/lj.cpp +++ b/src/core/nonbonded_interactions/lj.cpp @@ -25,31 +25,24 @@ #include "lj.hpp" #ifdef LENNARD_JONES -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include +#include +#include -int lennard_jones_set_params(int part_type_a, int part_type_b, double eps, - double sig, double cut, double shift, - double offset, double min) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->lj.eps = eps; - data->lj.sig = sig; - data->lj.cut = cut; - data->lj.shift = shift; - data->lj.offset = offset; - if (min > 0) { - data->lj.min = min; +LJ_Parameters::LJ_Parameters(double epsilon, double sigma, double cutoff, + double offset, double min, double shift) + : eps{epsilon}, sig{sigma}, cut{cutoff}, shift{shift}, offset{offset}, + min{std::max(min, 0.)} { + if (epsilon < 0.) { + throw std::domain_error("LJ parameter 'epsilon' has to be >= 0"); + } + if (sigma < 0.) { + throw std::domain_error("LJ parameter 'sigma' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("LJ parameter 'cutoff' has to be >= 0"); } - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; } #endif /* ifdef LENNARD_JONES */ diff --git a/src/core/nonbonded_interactions/lj.hpp b/src/core/nonbonded_interactions/lj.hpp index 99b5d6f7df5..e0eeb4f54a7 100644 --- a/src/core/nonbonded_interactions/lj.hpp +++ b/src/core/nonbonded_interactions/lj.hpp @@ -37,15 +37,10 @@ #include #include -int lennard_jones_set_params(int part_type_a, int part_type_b, double eps, - double sig, double cut, double shift, - double offset, double min); - /** Calculate Lennard-Jones force factor */ inline double lj_pair_force_factor(IA_parameters const &ia_params, double dist) { - if ((dist < ia_params.lj.cut + ia_params.lj.offset) && - (dist > ia_params.lj.min + ia_params.lj.offset)) { + if (dist < ia_params.lj.max_cutoff() and dist > ia_params.lj.min_cutoff()) { auto const r_off = dist - ia_params.lj.offset; auto const frac6 = Utils::int_pow<6>(ia_params.lj.sig / r_off); return 48.0 * ia_params.lj.eps * frac6 * (frac6 - 0.5) / (r_off * dist); @@ -55,8 +50,7 @@ inline double lj_pair_force_factor(IA_parameters const &ia_params, /** Calculate Lennard-Jones energy */ inline double lj_pair_energy(IA_parameters const &ia_params, double dist) { - if ((dist < ia_params.lj.cut + ia_params.lj.offset) && - (dist > ia_params.lj.min + ia_params.lj.offset)) { + if (dist < ia_params.lj.max_cutoff() and dist > ia_params.lj.min_cutoff()) { auto const r_off = dist - ia_params.lj.offset; auto const frac6 = Utils::int_pow<6>(ia_params.lj.sig / r_off); return 4.0 * ia_params.lj.eps * diff --git a/src/core/nonbonded_interactions/ljcos.cpp b/src/core/nonbonded_interactions/ljcos.cpp index 78c29d28a80..1fa4d75e266 100644 --- a/src/core/nonbonded_interactions/ljcos.cpp +++ b/src/core/nonbonded_interactions/ljcos.cpp @@ -25,34 +25,30 @@ #include "ljcos.hpp" #ifdef LJCOS -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" #include #include -int ljcos_set_params(int part_type_a, int part_type_b, double eps, double sig, - double cut, double offset) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->ljcos.eps = eps; - data->ljcos.sig = sig; - data->ljcos.cut = cut; - data->ljcos.offset = offset; - - /* Calculate dependent parameters */ +#include + +LJcos_Parameters::LJcos_Parameters(double epsilon, double sigma, double cutoff, + double offset) + : eps{epsilon}, sig{sigma}, cut{cutoff}, offset{offset} { + if (epsilon < 0.) { + throw std::domain_error("LJcos parameter 'epsilon' has to be >= 0"); + } + if (sigma < 0.) { + throw std::domain_error("LJcos parameter 'sigma' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("LJcos parameter 'cutoff' has to be >= 0"); + } auto const facsq = Utils::cbrt_2() * Utils::sqr(sig); - data->ljcos.rmin = sqrt(Utils::cbrt_2()) * sig; - data->ljcos.alfa = Utils::pi() / (Utils::sqr(data->ljcos.cut) - facsq); - data->ljcos.beta = - Utils::pi() * (1. - (1. / (Utils::sqr(data->ljcos.cut) / facsq - 1.))); - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; + rmin = sqrt(Utils::cbrt_2()) * sig; + alfa = Utils::pi() / (Utils::sqr(cut) - facsq); + beta = Utils::pi() * (1. - (1. / (Utils::sqr(cut) / facsq - 1.))); } -#endif + +#endif // LJCOS diff --git a/src/core/nonbonded_interactions/ljcos.hpp b/src/core/nonbonded_interactions/ljcos.hpp index 50e9abd824e..0221bc0c22d 100644 --- a/src/core/nonbonded_interactions/ljcos.hpp +++ b/src/core/nonbonded_interactions/ljcos.hpp @@ -33,15 +33,11 @@ #include "nonbonded_interaction_data.hpp" -#include #include #include #include -int ljcos_set_params(int part_type_a, int part_type_b, double eps, double sig, - double cut, double offset); - /** Calculate Lennard-Jones cosine force factor */ inline double ljcos_pair_force_factor(IA_parameters const &ia_params, double dist) { diff --git a/src/core/nonbonded_interactions/ljcos2.cpp b/src/core/nonbonded_interactions/ljcos2.cpp index 56237327934..1f4539e26f0 100644 --- a/src/core/nonbonded_interactions/ljcos2.cpp +++ b/src/core/nonbonded_interactions/ljcos2.cpp @@ -25,33 +25,27 @@ #include "ljcos2.hpp" #ifdef LJCOS2 -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include - #include - -int ljcos2_set_params(int part_type_a, int part_type_b, double eps, double sig, - double offset, double w) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->ljcos2.eps = eps; - data->ljcos2.sig = sig; - data->ljcos2.offset = offset; - data->ljcos2.w = w; - - /* calculate dependent parameters */ - data->ljcos2.rchange = pow(2, 1 / 6.) * sig; - data->ljcos2.cut = w + data->ljcos2.rchange; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; +#include + +LJcos2_Parameters::LJcos2_Parameters(double epsilon, double sigma, + double offset, double width) + : eps{epsilon}, sig{sigma}, offset{offset}, w{width} { + if (epsilon < 0.) { + throw std::domain_error("LJcos2 parameter 'epsilon' has to be >= 0"); + } + if (sigma < 0.) { + throw std::domain_error("LJcos2 parameter 'sigma' has to be >= 0"); + } + if (width < 0.) { + throw std::domain_error("LJcos2 parameter 'width' has to be >= 0"); + } + if (sigma != 0.) { + rchange = std::pow(2., 1. / 6.) * sigma; + cut = width + rchange; + } } #endif /* ifdef LJCOS2 */ diff --git a/src/core/nonbonded_interactions/ljcos2.hpp b/src/core/nonbonded_interactions/ljcos2.hpp index 3d39d94c242..482da3b435a 100644 --- a/src/core/nonbonded_interactions/ljcos2.hpp +++ b/src/core/nonbonded_interactions/ljcos2.hpp @@ -36,16 +36,12 @@ #include "nonbonded_interaction_data.hpp" -#include #include #include #include #include -int ljcos2_set_params(int part_type_a, int part_type_b, double eps, double sig, - double offset, double w); - /** Calculate Lennard-Jones cosine squared force factor */ inline double ljcos2_pair_force_factor(IA_parameters const &ia_params, double dist) { diff --git a/src/core/nonbonded_interactions/ljgen.cpp b/src/core/nonbonded_interactions/ljgen.cpp index 862d0dfef81..d97e80f7258 100644 --- a/src/core/nonbonded_interactions/ljgen.cpp +++ b/src/core/nonbonded_interactions/ljgen.cpp @@ -25,43 +25,39 @@ #include "ljgen.hpp" #ifdef LENNARD_JONES_GENERIC -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include - -int ljgen_set_params(int part_type_a, int part_type_b, double eps, double sig, - double cut, double shift, double offset, double a1, - double a2, double b1, double b2 +#include +LJGen_Parameters::LJGen_Parameters(double epsilon, double sigma, double cutoff, + double shift, double offset, #ifdef LJGEN_SOFTCORE - , - double lambda, double softrad + double lam, double delta, #endif -) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->ljgen.eps = eps; - data->ljgen.sig = sig; - data->ljgen.cut = cut; - data->ljgen.shift = shift; - data->ljgen.offset = offset; - data->ljgen.a1 = a1; - data->ljgen.a2 = a2; - data->ljgen.b1 = b1; - data->ljgen.b2 = b2; + double e1, double e2, double b1, double b2) + : eps{epsilon}, sig{sigma}, cut{cutoff}, shift{shift}, offset{offset}, #ifdef LJGEN_SOFTCORE - data->ljgen.lambda1 = lambda; - data->ljgen.softrad = softrad; + lambda{lam}, softrad{delta}, #endif - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; + a1{e1}, a2{e2}, b1{b1}, b2{b2} { + if (epsilon < 0.) { + throw std::domain_error("Generic LJ parameter 'epsilon' has to be >= 0"); + } + if (sigma < 0.) { + throw std::domain_error("Generic LJ parameter 'sigma' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("Generic LJ parameter 'cutoff' has to be >= 0"); + } +#ifdef LJGEN_SOFTCORE + if (delta < 0.) { + throw std::domain_error("Generic LJ parameter 'delta' has to be >= 0"); + } + if (lam < 0. or lam > 1.) { + throw std::domain_error( + "Generic LJ parameter 'lam' has to be in the range [0, 1]"); + } +#endif // LJGEN_SOFTCORE } -#endif +#endif // LENNARD_JONES_GENERIC diff --git a/src/core/nonbonded_interactions/ljgen.hpp b/src/core/nonbonded_interactions/ljgen.hpp index 23afed47a95..5f370d37909 100644 --- a/src/core/nonbonded_interactions/ljgen.hpp +++ b/src/core/nonbonded_interactions/ljgen.hpp @@ -47,24 +47,15 @@ #include -int ljgen_set_params(int part_type_a, int part_type_b, double eps, double sig, - double cut, double shift, double offset, double a1, - double a2, double b1, double b2 -#ifdef LJGEN_SOFTCORE - , - double lambda, double softrad -#endif -); - /** Calculate Lennard-Jones force factor */ inline double ljgen_pair_force_factor(IA_parameters const &ia_params, double dist) { - if (dist < (ia_params.ljgen.cut + ia_params.ljgen.offset)) { + if (dist < ia_params.ljgen.max_cutoff()) { auto r_off = dist - ia_params.ljgen.offset; #ifdef LJGEN_SOFTCORE r_off *= r_off; - r_off += Utils::sqr(ia_params.ljgen.sig) * (1.0 - ia_params.ljgen.lambda1) * + r_off += Utils::sqr(ia_params.ljgen.sig) * (1.0 - ia_params.ljgen.lambda) * ia_params.ljgen.softrad; r_off = sqrt(r_off); #else @@ -73,7 +64,7 @@ inline double ljgen_pair_force_factor(IA_parameters const &ia_params, auto const frac = ia_params.ljgen.sig / r_off; auto const fac = ia_params.ljgen.eps #ifdef LJGEN_SOFTCORE - * ia_params.ljgen.lambda1 * + * ia_params.ljgen.lambda * (dist - ia_params.ljgen.offset) / r_off #endif * (ia_params.ljgen.b1 * ia_params.ljgen.a1 * @@ -88,11 +79,11 @@ inline double ljgen_pair_force_factor(IA_parameters const &ia_params, /** Calculate Lennard-Jones energy */ inline double ljgen_pair_energy(IA_parameters const &ia_params, double dist) { - if (dist < (ia_params.ljgen.cut + ia_params.ljgen.offset)) { + if (dist < ia_params.ljgen.max_cutoff()) { auto r_off = dist - ia_params.ljgen.offset; #ifdef LJGEN_SOFTCORE r_off *= r_off; - r_off += pow(ia_params.ljgen.sig, 2) * (1.0 - ia_params.ljgen.lambda1) * + r_off += pow(ia_params.ljgen.sig, 2) * (1.0 - ia_params.ljgen.lambda) * ia_params.ljgen.softrad; r_off = sqrt(r_off); #else @@ -101,7 +92,7 @@ inline double ljgen_pair_energy(IA_parameters const &ia_params, double dist) { auto const frac = ia_params.ljgen.sig / r_off; auto const fac = ia_params.ljgen.eps #ifdef LJGEN_SOFTCORE - * ia_params.ljgen.lambda1 + * ia_params.ljgen.lambda #endif * (ia_params.ljgen.b1 * pow(frac, ia_params.ljgen.a1) - ia_params.ljgen.b2 * pow(frac, ia_params.ljgen.a2) + @@ -112,4 +103,4 @@ inline double ljgen_pair_energy(IA_parameters const &ia_params, double dist) { } #endif // LENNARD_JONES_GENERIC -#endif // CORE_NB_IA_LJGEN_HPP +#endif diff --git a/src/core/nonbonded_interactions/morse.cpp b/src/core/nonbonded_interactions/morse.cpp index f31d2335be5..a4e4cfe0212 100644 --- a/src/core/nonbonded_interactions/morse.cpp +++ b/src/core/nonbonded_interactions/morse.cpp @@ -25,33 +25,23 @@ #include "morse.hpp" #ifdef MORSE -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include - -int morse_set_params(int part_type_a, int part_type_b, double eps, double alpha, - double rmin, double cut) { - double add1, add2; - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->morse.eps = eps; - data->morse.alpha = alpha; - data->morse.rmin = rmin; - data->morse.cut = cut; - - /* calculate dependent parameter */ - add1 = exp(-2.0 * data->morse.alpha * (data->morse.cut - data->morse.rmin)); - add2 = 2.0 * exp(-data->morse.alpha * (data->morse.cut - data->morse.rmin)); - data->morse.rest = data->morse.eps * (add1 - add2); - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; +#include +#include + +Morse_Parameters::Morse_Parameters(double eps, double alpha, double rmin, + double cutoff) + : eps{eps}, alpha{alpha}, rmin{rmin}, cut{cutoff} { + if (eps < 0.) { + throw std::domain_error("Morse parameter 'eps' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("Morse parameter 'cutoff' has to be >= 0"); + } + auto const add1 = std::exp(-2.0 * alpha * (cut - rmin)); + auto const add2 = 2.0 * std::exp(-alpha * (cut - rmin)); + rest = eps * (add1 - add2); } -#endif +#endif // MORSE diff --git a/src/core/nonbonded_interactions/morse.hpp b/src/core/nonbonded_interactions/morse.hpp index 4515e2b0837..66b1f3678de 100644 --- a/src/core/nonbonded_interactions/morse.hpp +++ b/src/core/nonbonded_interactions/morse.hpp @@ -33,14 +33,10 @@ #include "nonbonded_interaction_data.hpp" -#include #include #include -int morse_set_params(int part_type_a, int part_type_b, double eps, double alpha, - double rmin, double cut); - /** Calculate Morse force factor */ inline double morse_pair_force_factor(IA_parameters const &ia_params, double dist) { @@ -64,5 +60,5 @@ inline double morse_pair_energy(IA_parameters const &ia_params, double dist) { return 0.0; } -#endif /* ifdef MORSE */ -#endif /* ifdef CORE_NB_IA_MORSE_HPP */ +#endif // MORSE +#endif diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 5250e802c87..8b1925ba306 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -26,21 +26,11 @@ #include "communication.hpp" #include "electrostatics/coulomb.hpp" #include "event.hpp" -#include "serialization/IA_parameters.hpp" - -#include -#include -#include -#include -#include -#include -#include #include #include -#include -#include +#include #include #include @@ -48,7 +38,7 @@ * variables *****************************************/ int max_seen_particle_type = 0; -std::vector nonbonded_ia_params; +std::vector> nonbonded_ia_params; /** Minimal global interaction cutoff. Particles with a distance * smaller than this are guaranteed to be available on the same node @@ -60,132 +50,95 @@ static double min_global_cut = INACTIVE_CUTOFF; * general low-level functions *****************************************/ -static void mpi_realloc_ia_params_local(int new_size) { - if (new_size <= max_seen_particle_type) +void mpi_realloc_ia_params_local(int new_size) { + auto const old_size = ::max_seen_particle_type; + if (new_size <= old_size) return; - auto new_params = std::vector(new_size * (new_size + 1) / 2); + auto const n_pairs = new_size * (new_size + 1) / 2; + auto new_params = std::vector>(n_pairs); /* if there is an old field, move entries */ - for (int i = 0; i < max_seen_particle_type; i++) - for (int j = i; j < max_seen_particle_type; j++) { - new_params.at(Utils::upper_triangular(i, j, new_size)) = - std::move(get_ia_param(i, j)); + for (int i = 0; i < old_size; i++) { + for (int j = i; j < old_size; j++) { + auto const new_key = Utils::upper_triangular(i, j, new_size); + auto const old_key = Utils::upper_triangular(i, j, old_size); + new_params[new_key] = std::move(nonbonded_ia_params[old_key]); + } + } + for (auto &ia_params : new_params) { + if (ia_params == nullptr) { + ia_params = std::make_shared(); } + } - max_seen_particle_type = new_size; - std::swap(nonbonded_ia_params, new_params); + ::max_seen_particle_type = new_size; + ::nonbonded_ia_params = std::move(new_params); } REGISTER_CALLBACK(mpi_realloc_ia_params_local) -/** Increase the size of the @ref nonbonded_ia_params vector. */ -static void mpi_realloc_ia_params(int new_size) { - mpi_call_all(mpi_realloc_ia_params_local, new_size); -} - -static void mpi_bcast_all_ia_params_local() { - boost::mpi::broadcast(comm_cart, nonbonded_ia_params, 0); -} - -REGISTER_CALLBACK(mpi_bcast_all_ia_params_local) - -/** Broadcast @ref nonbonded_ia_params to all nodes. */ -static void mpi_bcast_all_ia_params() { - mpi_call_all(mpi_bcast_all_ia_params_local); -} - -IA_parameters *get_ia_param_safe(int i, int j) { - make_particle_type_exist(std::max(i, j)); - return &get_ia_param(i, j); -} - -std::string ia_params_get_state() { - std::stringstream out; - boost::archive::binary_oarchive oa(out); - oa << nonbonded_ia_params; - oa << max_seen_particle_type; - return out.str(); -} - -void ia_params_set_state(std::string const &state) { - namespace iostreams = boost::iostreams; - iostreams::array_source src(state.data(), state.size()); - iostreams::stream ss(src); - boost::archive::binary_iarchive ia(ss); - nonbonded_ia_params.clear(); - ia >> nonbonded_ia_params; - ia >> max_seen_particle_type; - mpi_realloc_ia_params(max_seen_particle_type); - mpi_bcast_all_ia_params(); -} - static double recalc_maximal_cutoff(const IA_parameters &data) { auto max_cut_current = INACTIVE_CUTOFF; #ifdef LENNARD_JONES - max_cut_current = std::max(max_cut_current, (data.lj.cut + data.lj.offset)); + max_cut_current = std::max(max_cut_current, data.lj.max_cutoff()); #endif #ifdef WCA - max_cut_current = std::max(max_cut_current, data.wca.cut); + max_cut_current = std::max(max_cut_current, data.wca.max_cutoff()); #endif #ifdef DPD - max_cut_current = std::max( - max_cut_current, std::max(data.dpd_radial.cutoff, data.dpd_trans.cutoff)); + max_cut_current = std::max(max_cut_current, data.dpd.max_cutoff()); #endif #ifdef LENNARD_JONES_GENERIC - max_cut_current = - std::max(max_cut_current, (data.ljgen.cut + data.ljgen.offset)); + max_cut_current = std::max(max_cut_current, data.ljgen.max_cutoff()); #endif #ifdef SMOOTH_STEP - max_cut_current = std::max(max_cut_current, data.smooth_step.cut); + max_cut_current = std::max(max_cut_current, data.smooth_step.max_cutoff()); #endif #ifdef HERTZIAN - max_cut_current = std::max(max_cut_current, data.hertzian.sig); + max_cut_current = std::max(max_cut_current, data.hertzian.max_cutoff()); #endif #ifdef GAUSSIAN - max_cut_current = std::max(max_cut_current, data.gaussian.cut); + max_cut_current = std::max(max_cut_current, data.gaussian.max_cutoff()); #endif #ifdef BMHTF_NACL - max_cut_current = std::max(max_cut_current, data.bmhtf.cut); + max_cut_current = std::max(max_cut_current, data.bmhtf.max_cutoff()); #endif #ifdef MORSE - max_cut_current = std::max(max_cut_current, data.morse.cut); + max_cut_current = std::max(max_cut_current, data.morse.max_cutoff()); #endif #ifdef BUCKINGHAM - max_cut_current = std::max(max_cut_current, data.buckingham.cut); + max_cut_current = std::max(max_cut_current, data.buckingham.max_cutoff()); #endif #ifdef SOFT_SPHERE - max_cut_current = std::max(max_cut_current, - (data.soft_sphere.cut + data.soft_sphere.offset)); + max_cut_current = std::max(max_cut_current, data.soft_sphere.max_cutoff()); #endif #ifdef HAT - max_cut_current = std::max(max_cut_current, data.hat.r); + max_cut_current = std::max(max_cut_current, data.hat.max_cutoff()); #endif #ifdef LJCOS - max_cut_current = - std::max(max_cut_current, (data.ljcos.cut + data.ljcos.offset)); + max_cut_current = std::max(max_cut_current, data.ljcos.max_cutoff()); #endif #ifdef LJCOS2 - max_cut_current = - std::max(max_cut_current, (data.ljcos2.cut + data.ljcos2.offset)); + max_cut_current = std::max(max_cut_current, data.ljcos2.max_cutoff()); #endif #ifdef GAY_BERNE - max_cut_current = std::max(max_cut_current, data.gay_berne.cut); + max_cut_current = std::max(max_cut_current, data.gay_berne.max_cutoff()); #endif #ifdef TABULATED @@ -194,7 +147,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #ifdef THOLE // If THOLE is active, use p3m cutoff - if (data.thole.scaling_coeff != 0) + if (data.thole.scaling_coeff != 0.) max_cut_current = std::max(max_cut_current, Coulomb::cutoff()); #endif @@ -205,25 +158,20 @@ double maximal_cutoff_nonbonded() { auto max_cut_nonbonded = INACTIVE_CUTOFF; for (auto &data : nonbonded_ia_params) { - data.max_cut = recalc_maximal_cutoff(data); - max_cut_nonbonded = std::max(max_cut_nonbonded, data.max_cut); + data->max_cut = recalc_maximal_cutoff(*data); + max_cut_nonbonded = std::max(max_cut_nonbonded, data->max_cut); } return max_cut_nonbonded; } -void reset_ia_params() { - boost::fill(nonbonded_ia_params, IA_parameters{}); - mpi_bcast_all_ia_params(); -} - bool is_new_particle_type(int type) { return (type + 1) > max_seen_particle_type; } void make_particle_type_exist(int type) { if (is_new_particle_type(type)) - mpi_realloc_ia_params(type + 1); + mpi_call_all(mpi_realloc_ia_params_local, type + 1); } void make_particle_type_exist_local(int type) { diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index 2926e7ceb75..a6370f456d5 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -28,10 +28,12 @@ #include "config/config.hpp" #include +#include #include #include -#include +#include +#include #include /** Cutoff for deactivated interactions. Must be negative, so that even @@ -47,6 +49,18 @@ struct LJ_Parameters { double shift = 0.0; double offset = 0.0; double min = 0.0; + LJ_Parameters() = default; + LJ_Parameters(double epsilon, double sigma, double cutoff, double offset, + double min, double shift); + double get_auto_shift() const { + auto auto_shift = 0.; + if (cut != 0.) { + auto_shift = Utils::int_pow<6>(sig / cut) - Utils::int_pow<12>(sig / cut); + } + return auto_shift; + } + double max_cutoff() const { return cut + offset; } + double min_cutoff() const { return min + offset; } }; /** WCA potential */ @@ -54,6 +68,9 @@ struct WCA_Parameters { double eps = 0.0; double sig = 0.0; double cut = INACTIVE_CUTOFF; + WCA_Parameters() = default; + WCA_Parameters(double epsilon, double sigma); + double max_cutoff() const { return cut; } }; /** Generic Lennard-Jones with shift */ @@ -63,12 +80,27 @@ struct LJGen_Parameters { double cut = INACTIVE_CUTOFF; double shift = 0.0; double offset = 0.0; + double lambda = 1.0; + double softrad = 0.0; double a1 = 0.0; double a2 = 0.0; double b1 = 0.0; double b2 = 0.0; - double lambda1 = 1.0; - double softrad = 0.0; + LJGen_Parameters() = default; + LJGen_Parameters(double epsilon, double sigma, double cutoff, double shift, + double offset, +#ifdef LJGEN_SOFTCORE + double lam, double delta, +#endif + double e1, double e2, double b1, double b2); + double get_auto_shift() const { + auto auto_shift = 0.; + if (cut != 0.) { + auto_shift = b2 * std::pow(sig / cut, a2) - b1 * std::pow(sig / cut, a1); + } + return auto_shift; + } + double max_cutoff() const { return cut + offset; } }; /** smooth step potential */ @@ -79,12 +111,19 @@ struct SmoothStep_Parameters { double d = 0.0; int n = 0; double k0 = 0.0; + SmoothStep_Parameters() = default; + SmoothStep_Parameters(double eps, double sig, double cutoff, double d, int n, + double k0); + double max_cutoff() const { return cut; } }; /** Hertzian potential */ struct Hertzian_Parameters { double eps = 0.0; double sig = INACTIVE_CUTOFF; + Hertzian_Parameters() = default; + Hertzian_Parameters(double eps, double sig); + double max_cutoff() const { return sig; } }; /** Gaussian potential */ @@ -92,6 +131,9 @@ struct Gaussian_Parameters { double eps = 0.0; double sig = 1.0; double cut = INACTIVE_CUTOFF; + Gaussian_Parameters() = default; + Gaussian_Parameters(double eps, double sig, double cutoff); + double max_cutoff() const { return cut; } }; /** BMHTF NaCl potential */ @@ -103,15 +145,22 @@ struct BMHTF_Parameters { double sig = 0.0; double cut = INACTIVE_CUTOFF; double computed_shift = 0.0; + BMHTF_Parameters() = default; + BMHTF_Parameters(double A, double B, double C, double D, double sig, + double cut); + double max_cutoff() const { return cut; } }; /** Morse potential */ struct Morse_Parameters { - double eps = INACTIVE_CUTOFF; + double eps = 0.; double alpha = INACTIVE_CUTOFF; double rmin = INACTIVE_CUTOFF; double cut = INACTIVE_CUTOFF; double rest = INACTIVE_CUTOFF; + Morse_Parameters() = default; + Morse_Parameters(double eps, double alpha, double rmin, double cutoff); + double max_cutoff() const { return cut; } }; /** Buckingham potential */ @@ -125,6 +174,10 @@ struct Buckingham_Parameters { double shift = 0.0; double F1 = 0.0; double F2 = 0.0; + Buckingham_Parameters() = default; + Buckingham_Parameters(double a, double b, double c, double d, double cutoff, + double discont, double shift); + double max_cutoff() const { return cut; } }; /** soft-sphere potential */ @@ -133,12 +186,18 @@ struct SoftSphere_Parameters { double n = 0.0; double cut = INACTIVE_CUTOFF; double offset = 0.0; + SoftSphere_Parameters() = default; + SoftSphere_Parameters(double a, double n, double cutoff, double offset); + double max_cutoff() const { return cut + offset; } }; /** hat potential */ struct Hat_Parameters { double Fmax = 0.0; double r = INACTIVE_CUTOFF; + Hat_Parameters() = default; + Hat_Parameters(double F_max, double cutoff); + double max_cutoff() const { return r; } }; /** Lennard-Jones+Cos potential */ @@ -150,6 +209,9 @@ struct LJcos_Parameters { double alfa = 0.0; double beta = 0.0; double rmin = 0.0; + LJcos_Parameters() = default; + LJcos_Parameters(double epsilon, double sigma, double cutoff, double offset); + double max_cutoff() const { return cut + offset; } }; /** Lennard-Jones with a different Cos potential */ @@ -160,6 +222,9 @@ struct LJcos2_Parameters { double offset = 0.0; double w = 0.0; double rchange = 0.0; + LJcos2_Parameters() = default; + LJcos2_Parameters(double epsilon, double sigma, double offset, double width); + double max_cutoff() const { return cut + offset; } }; /** Gay-Berne potential */ @@ -173,23 +238,42 @@ struct GayBerne_Parameters { double nu = 0.0; double chi1 = 0.0; double chi2 = 0.0; + GayBerne_Parameters() = default; + GayBerne_Parameters(double eps, double sig, double cut, double k1, double k2, + double mu, double nu); + double max_cutoff() const { return cut; } }; /** Thole potential */ struct Thole_Parameters { - double scaling_coeff; - double q1q2; + double scaling_coeff = 0.; // inactive cutoff is 0 + double q1q2 = 0.; + Thole_Parameters() = default; + Thole_Parameters(double scaling_coeff, double q1q2) + : scaling_coeff{scaling_coeff}, q1q2{q1q2} {} }; /** DPD potential */ struct DPDParameters { double gamma = 0.; double k = 1.; - double cutoff = -1.; + double cutoff = INACTIVE_CUTOFF; int wf = 0; double pref = 0.0; }; +struct DPD_Parameters { + DPDParameters radial; + DPDParameters trans; + DPD_Parameters() = default; + DPD_Parameters(double gamma, double k, double r_c, int wf, double tgamma, + double tr_c, int twf) { + radial = DPDParameters{gamma, k, r_c, wf, -1.}; + trans = DPDParameters{tgamma, k, tr_c, twf, -1.}; + } + double max_cutoff() const { return std::max(radial.cutoff, trans.cutoff); } +}; + /** Data structure containing the interaction parameters for non-bonded * interactions. * Access via get_ia_param(i, j) with @@ -263,11 +347,7 @@ struct IA_parameters { #endif #ifdef DPD - /** \name DPD as interaction */ - /**@{*/ - DPDParameters dpd_radial; - DPDParameters dpd_trans; - /**@}*/ + DPD_Parameters dpd; #endif #ifdef THOLE @@ -275,7 +355,7 @@ struct IA_parameters { #endif }; -extern std::vector nonbonded_ia_params; +extern std::vector> nonbonded_ia_params; /** Maximal particle type seen so far. */ extern int max_seen_particle_type; @@ -285,6 +365,13 @@ extern int max_seen_particle_type; */ double maximal_cutoff_nonbonded(); +inline int get_ia_param_key(int i, int j) { + assert(i >= 0 && i < ::max_seen_particle_type); + assert(j >= 0 && j < ::max_seen_particle_type); + return Utils::upper_triangular(std::min(i, j), std::max(i, j), + ::max_seen_particle_type); +} + /** * @brief Get interaction parameters between particle types i and j * @@ -297,26 +384,10 @@ double maximal_cutoff_nonbonded(); * @return Reference to interaction parameters for the type pair. */ inline IA_parameters &get_ia_param(int i, int j) { - assert(i >= 0 && i < max_seen_particle_type); - assert(j >= 0 && j < max_seen_particle_type); - - return nonbonded_ia_params[Utils::upper_triangular( - std::min(i, j), std::max(i, j), max_seen_particle_type)]; + return *::nonbonded_ia_params[get_ia_param_key(i, j)]; } -/** Get interaction parameters between particle types i and j. - * Slower than @ref get_ia_param, but can also be used on not - * yet present particle types - */ -IA_parameters *get_ia_param_safe(int i, int j); - -/** @brief Get the state of all non-bonded interactions. - */ -std::string ia_params_get_state(); - -/** @brief Set the state of all non-bonded interactions. - */ -void ia_params_set_state(std::string const &); +void mpi_realloc_ia_params_local(int new_size); bool is_new_particle_type(int type); /** Make sure that ia_params is large enough to cover interactions @@ -327,11 +398,6 @@ void make_particle_type_exist(int type); void make_particle_type_exist_local(int type); -/** - * @brief Reset all interaction parameters to their defaults. - */ -void reset_ia_params(); - /** Check if a non-bonded interaction is defined */ inline bool checkIfInteraction(IA_parameters const &data) { return data.max_cut != INACTIVE_CUTOFF; diff --git a/src/core/nonbonded_interactions/nonbonded_tab.cpp b/src/core/nonbonded_interactions/nonbonded_tab.cpp deleted file mode 100644 index d9cbaef5ebe..00000000000 --- a/src/core/nonbonded_interactions/nonbonded_tab.cpp +++ /dev/null @@ -1,58 +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 nonbonded_tab.hpp - */ -#include "nonbonded_interactions/nonbonded_tab.hpp" - -#ifdef TABULATED -#include "interactions.hpp" - -#include - -#include -#include - -int tabulated_set_params(int part_type_a, int part_type_b, double min, - double max, std::vector const &energy, - std::vector const &force) { - auto data = get_ia_param_safe(part_type_a, part_type_b); - assert(max >= min); - assert((max == min) || force.size() > 1); - assert(force.size() == energy.size()); - - data->tab.maxval = max; - data->tab.minval = min; - if (max == min) - data->tab.invstepsize = 0; - else - data->tab.invstepsize = static_cast(force.size() - 1) / (max - min); - - data->tab.force_tab = force; - data->tab.energy_tab = energy; - - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; -} - -#endif diff --git a/src/core/nonbonded_interactions/nonbonded_tab.hpp b/src/core/nonbonded_interactions/nonbonded_tab.hpp index 85385b1ca05..fa4de64d6ea 100644 --- a/src/core/nonbonded_interactions/nonbonded_tab.hpp +++ b/src/core/nonbonded_interactions/nonbonded_tab.hpp @@ -25,7 +25,6 @@ * Routines to calculate the energy and/or force for particle pairs via * interpolation of lookup tables. * - * Implementation in \ref nonbonded_tab.cpp. * Needs feature TABULATED compiled in (see \ref config.hpp). */ @@ -40,21 +39,6 @@ #include -/** Set the parameters of a non-bonded tabulated potential. - * ia_params and force/energy tables are communicated to each node - * - * @param part_type_a particle type for which the interaction is defined - * @param part_type_b particle type for which the interaction is defined - * @param min @copybrief TabulatedPotential::minval - * @param max @copybrief TabulatedPotential::maxval - * @param energy @copybrief TabulatedPotential::energy_tab - * @param force @copybrief TabulatedPotential::force_tab - * @retval ES_OK - */ -int tabulated_set_params(int part_type_a, int part_type_b, double min, - double max, std::vector const &energy, - std::vector const &force); - /** Calculate a non-bonded pair force factor by linear interpolation from a * table. */ @@ -75,6 +59,5 @@ inline double tabulated_pair_energy(IA_parameters const &ia_params, return 0.0; } -#endif - +#endif // TABULATED #endif diff --git a/src/core/nonbonded_interactions/smooth_step.cpp b/src/core/nonbonded_interactions/smooth_step.cpp index 34996d91776..6c1b9ee7f93 100644 --- a/src/core/nonbonded_interactions/smooth_step.cpp +++ b/src/core/nonbonded_interactions/smooth_step.cpp @@ -25,29 +25,23 @@ #include "smooth_step.hpp" #ifdef SMOOTH_STEP -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include - -int smooth_step_set_params(int part_type_a, int part_type_b, double d, int n, - double eps, double k0, double sig, double cut) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->smooth_step.eps = eps; - data->smooth_step.sig = sig; - data->smooth_step.cut = cut; - data->smooth_step.d = d; - data->smooth_step.n = n; - data->smooth_step.k0 = k0; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; +#include + +SmoothStep_Parameters::SmoothStep_Parameters(double eps, double sig, + double cutoff, double d, int n, + double k0) + : eps{eps}, sig{sig}, cut{cutoff}, d{d}, n{n}, k0{k0} { + if (eps < 0.) { + throw std::domain_error("SmoothStep parameter 'eps' has to be >= 0"); + } + if (sig < 0.) { + throw std::domain_error("SmoothStep parameter 'sig' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("SmoothStep parameter 'cutoff' has to be >= 0"); + } } -#endif +#endif // SMOOTH_STEP diff --git a/src/core/nonbonded_interactions/smooth_step.hpp b/src/core/nonbonded_interactions/smooth_step.hpp index 926be66665f..7789ba48bb4 100644 --- a/src/core/nonbonded_interactions/smooth_step.hpp +++ b/src/core/nonbonded_interactions/smooth_step.hpp @@ -38,9 +38,6 @@ #include -int smooth_step_set_params(int part_type_a, int part_type_b, double d, int n, - double eps, double k0, double sig, double cut); - /** Calculate smooth step force factor */ inline double SmSt_pair_force_factor(IA_parameters const &ia_params, double dist) { diff --git a/src/core/nonbonded_interactions/soft_sphere.cpp b/src/core/nonbonded_interactions/soft_sphere.cpp index ffc13dc9ad6..84812c1f4f0 100644 --- a/src/core/nonbonded_interactions/soft_sphere.cpp +++ b/src/core/nonbonded_interactions/soft_sphere.cpp @@ -26,27 +26,22 @@ #include "soft_sphere.hpp" #ifdef SOFT_SPHERE -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include - -int soft_sphere_set_params(int part_type_a, int part_type_b, double a, double n, - double cut, double offset) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->soft_sphere.a = a; - data->soft_sphere.n = n; - data->soft_sphere.cut = cut; - data->soft_sphere.offset = offset; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; +#include + +SoftSphere_Parameters::SoftSphere_Parameters(double a, double n, double cutoff, + double offset) + : a{a}, n{n}, cut{cutoff}, offset{offset} { + if (a < 0.) { + throw std::domain_error("SoftSphere parameter 'a' has to be >= 0"); + } + if (cutoff < 0.) { + throw std::domain_error("SoftSphere parameter 'cutoff' has to be >= 0"); + } + if (offset < 0.) { + throw std::domain_error("SoftSphere parameter 'offset' has to be >= 0"); + } } -#endif +#endif // SOFT_SPHERE diff --git a/src/core/nonbonded_interactions/soft_sphere.hpp b/src/core/nonbonded_interactions/soft_sphere.hpp index 5a6d8a9291c..09f00aba466 100644 --- a/src/core/nonbonded_interactions/soft_sphere.hpp +++ b/src/core/nonbonded_interactions/soft_sphere.hpp @@ -33,17 +33,12 @@ #include "nonbonded_interaction_data.hpp" -#include - #include -int soft_sphere_set_params(int part_type_a, int part_type_b, double a, double n, - double cut, double offset); - /** Calculate soft-sphere force factor */ inline double soft_pair_force_factor(IA_parameters const &ia_params, double dist) { - if (dist < (ia_params.soft_sphere.cut + ia_params.soft_sphere.offset)) { + if (dist < ia_params.soft_sphere.max_cutoff()) { /* normal case: resulting force/energy smaller than zero. */ auto const r_off = dist - ia_params.soft_sphere.offset; if (r_off > 0.0) { @@ -56,7 +51,7 @@ inline double soft_pair_force_factor(IA_parameters const &ia_params, /** Calculate soft-sphere energy */ inline double soft_pair_energy(IA_parameters const &ia_params, double dist) { - if (dist < (ia_params.soft_sphere.cut + ia_params.soft_sphere.offset)) { + if (dist < ia_params.soft_sphere.max_cutoff()) { auto const r_off = dist - ia_params.soft_sphere.offset; /* normal case: resulting force/energy smaller than zero. */ return ia_params.soft_sphere.a / std::pow(r_off, ia_params.soft_sphere.n); @@ -64,5 +59,5 @@ inline double soft_pair_energy(IA_parameters const &ia_params, double dist) { return 0.0; } -#endif /* ifdef SOFT_SPHERE */ +#endif // SOFT_SPHERE #endif diff --git a/src/core/nonbonded_interactions/thole.cpp b/src/core/nonbonded_interactions/thole.cpp deleted file mode 100644 index fb750a9ac51..00000000000 --- a/src/core/nonbonded_interactions/thole.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 thole.hpp - */ -#include "thole.hpp" - -#ifdef THOLE -#include "interactions.hpp" - -#include - -int thole_set_params(int part_type_a, int part_type_b, double scaling_coeff, - double q1q2) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - if (!data) - return ES_ERROR; - - data->thole.scaling_coeff = scaling_coeff; - data->thole.q1q2 = q1q2; - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; -} -#endif diff --git a/src/core/nonbonded_interactions/thole.hpp b/src/core/nonbonded_interactions/thole.hpp index 4865850aeb4..2d41c57c298 100644 --- a/src/core/nonbonded_interactions/thole.hpp +++ b/src/core/nonbonded_interactions/thole.hpp @@ -23,8 +23,6 @@ /** \file * Routines to calculate the Thole damping potential between particle pairs. * See @cite thole81a. - * - * Implementation in \ref thole.cpp. */ #include "config/config.hpp" @@ -41,9 +39,6 @@ #include -int thole_set_params(int part_type_a, int part_type_b, double scaling_coeff, - double q1q2); - /** Calculate Thole force */ inline Utils::Vector3d thole_pair_force(Particle const &p1, Particle const &p2, diff --git a/src/core/nonbonded_interactions/wca.cpp b/src/core/nonbonded_interactions/wca.cpp index 29a4a2d14dd..4de3020b2ed 100644 --- a/src/core/nonbonded_interactions/wca.cpp +++ b/src/core/nonbonded_interactions/wca.cpp @@ -23,24 +23,22 @@ #include "wca.hpp" #ifdef WCA -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include - #include - -int wca_set_params(int part_type_a, int part_type_b, double eps, double sig) { - IA_parameters *data = get_ia_param_safe(part_type_a, part_type_b); - - data->wca.eps = eps; - data->wca.sig = sig; - data->wca.cut = sig * std::pow(2., 1. / 6.); - - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); - - return ES_OK; +#include + +WCA_Parameters::WCA_Parameters(double epsilon, double sigma) + : eps{epsilon}, sig{sigma} { + if (epsilon < 0.) { + throw std::domain_error("WCA parameter 'epsilon' has to be >= 0"); + } + if (sigma < 0.) { + throw std::domain_error("WCA parameter 'sigma' has to be >= 0"); + } + if (sigma != 0.) { + cut = sigma * std::pow(2., 1. / 6.); + } } #endif /* ifdef WCA */ diff --git a/src/core/nonbonded_interactions/wca.hpp b/src/core/nonbonded_interactions/wca.hpp index 55ff743e0bd..d1209dfda01 100644 --- a/src/core/nonbonded_interactions/wca.hpp +++ b/src/core/nonbonded_interactions/wca.hpp @@ -35,8 +35,6 @@ #include #include -int wca_set_params(int part_type_a, int part_type_b, double eps, double sig); - /** Calculate WCA force factor */ inline double wca_pair_force_factor(IA_parameters const &ia_params, double dist) { diff --git a/src/core/serialization/IA_parameters.hpp b/src/core/serialization/IA_parameters.hpp deleted file mode 100644 index e56e8c1c03a..00000000000 --- a/src/core/serialization/IA_parameters.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2010-2022 The ESPResSo project - * - * This file is part of ESPResSo. - * - * ESPResSo is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ESPResSo is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef SERIALIZATION_IA_PARAMETERS_HPP -#define SERIALIZATION_IA_PARAMETERS_HPP - -#include "nonbonded_interactions/nonbonded_interaction_data.hpp" - -namespace boost { -namespace serialization { -template -void load(Archive &ar, IA_parameters &p, - const unsigned int /* file_version */) { - ar >> make_array(reinterpret_cast(&p), sizeof(IA_parameters)); - -#ifdef TABULATED - TabulatedPotential tab; - ar >> tab; - - new (&(p.tab)) TabulatedPotential(std::move(tab)); -#endif -} - -template -void save(Archive &ar, IA_parameters const &p, - const unsigned int /* file_version */) { - ar << make_array(reinterpret_cast(&p), sizeof(IA_parameters)); - -#ifdef TABULATED - ar << p.tab; -#endif -} - -template -void serialize(Archive &ar, IA_parameters &p, const unsigned int file_version) { - split_free(ar, p, file_version); -} -} // namespace serialization -} // namespace boost - -#endif diff --git a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp index 0366ac86be9..617918717a3 100644 --- a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp +++ b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp @@ -37,6 +37,7 @@ namespace utf = boost::unit_test; #include "electrostatics/p3m.hpp" #include "electrostatics/registration.hpp" #include "energy.hpp" +#include "event.hpp" #include "galilei/Galilei.hpp" #include "integrate.hpp" #include "nonbonded_interactions/lj.hpp" @@ -130,6 +131,17 @@ static void mpi_set_tuned_p3m(double prefactor) { } #endif // P3M +#ifdef LENNARD_JONES +void mpi_set_lj_local(int key, double eps, double sig, double cut, + double offset, double min, double shift) { + LJ_Parameters lj{eps, sig, cut, offset, min, shift}; + ::nonbonded_ia_params[key]->lj = lj; + on_non_bonded_ia_change(); +} + +REGISTER_CALLBACK(mpi_set_lj_local) +#endif // LENNARD_JONES + BOOST_FIXTURE_TEST_CASE(espresso_system_stand_alone, ParticleFactory, *utf::precondition(if_head_node())) { constexpr auto tol = 100. * std::numeric_limits::epsilon(); @@ -223,9 +235,12 @@ BOOST_FIXTURE_TEST_CASE(espresso_system_stand_alone, ParticleFactory, auto const offset = 0.1; auto const min = 0.0; auto const r_off = dist - offset; - auto const cut = r_off + 1e-3; // LJ for only 2 pairs: AA BB - lennard_jones_set_params(type_a, type_b, eps, sig, cut, shift, offset, min); - lennard_jones_set_params(type_b, type_b, eps, sig, cut, shift, offset, min); + auto const cut = r_off + 1e-3; // LJ for only 2 pairs: AB BB + make_particle_type_exist(std::max(type_a, type_b)); + auto const key_ab = get_ia_param_key(type_a, type_b); + auto const key_bb = get_ia_param_key(type_b, type_b); + mpi_call_all(mpi_set_lj_local, key_ab, eps, sig, cut, offset, min, shift); + mpi_call_all(mpi_set_lj_local, key_bb, eps, sig, cut, offset, min, shift); // matrix indices and reference energy value auto const max_type = type_b + 1; diff --git a/src/core/unit_tests/Verlet_list_test.cpp b/src/core/unit_tests/Verlet_list_test.cpp index 3edecd75d15..6005db3c1f0 100644 --- a/src/core/unit_tests/Verlet_list_test.cpp +++ b/src/core/unit_tests/Verlet_list_test.cpp @@ -37,6 +37,7 @@ namespace bdata = boost::unit_test::data; #include "Particle.hpp" #include "ParticleFactory.hpp" #include "communication.hpp" +#include "event.hpp" #include "integrate.hpp" #include "integrators/steepest_descent.hpp" #include "nonbonded_interactions/lj.hpp" @@ -145,8 +146,18 @@ struct : public IntegratorHelper { char const *name() const override { return "VelocityVerletNpT"; } } velocity_verlet_npt; #endif // NPT + } // namespace Testing +void mpi_set_lj_local(int key, double eps, double sig, double cut, + double offset, double min, double shift) { + LJ_Parameters lj{eps, sig, cut, offset, min, shift}; + ::nonbonded_ia_params[key]->lj = lj; + on_non_bonded_ia_change(); +} + +REGISTER_CALLBACK(mpi_set_lj_local) + inline double get_dist_from_last_verlet_update(Particle const &p) { return (p.pos() - p.pos_at_last_verlet_update()).norm(); } @@ -193,7 +204,9 @@ BOOST_DATA_TEST_CASE_F(ParticleFactory, verlet_list_update, auto const min = 0.0; auto const r_off = dist - offset; auto const cut = r_off + 1e-3; - lennard_jones_set_params(0, 1, eps, sig, cut, shift, offset, min); + make_particle_type_exist(1); + auto const key = get_ia_param_key(0, 1); + mpi_call_all(mpi_set_lj_local, key, eps, sig, cut, offset, min, shift); // set up velocity-Verlet integrator auto const time_step = 0.01; diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd deleted file mode 100644 index 009dc267158..00000000000 --- a/src/python/espressomd/interactions.pxd +++ /dev/null @@ -1,343 +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 . -# - -# Handling of interactions - -from libcpp.string cimport string -from libcpp.vector cimport vector -from libc cimport stdint - -from .thermostat cimport thermalized_bond - -include "myconfig.pxi" - -# force include of config.hpp -cdef extern from "config/config.hpp": - pass - -cdef extern from "TabulatedPotential.hpp": - struct TabulatedPotential: - double maxval - double minval - vector[double] energy_tab - vector[double] force_tab - -cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": - cdef struct LJ_Parameters: - double eps - double sig - double cut - double shift - double offset - double min - - cdef struct WCA_Parameters: - double eps - double sig - double cut - - cdef struct LJGen_Parameters: - double eps - double sig - double cut - double shift - double offset - double a1 - double a2 - double b1 - double b2 - double lambda1 - double softrad - - cdef struct SmoothStep_Parameters: - double eps - double sig - double cut - double d - int n - double k0 - - cdef struct Hertzian_Parameters: - double eps - double sig - - cdef struct Gaussian_Parameters: - double eps - double sig - double cut - - cdef struct BMHTF_Parameters: - double A - double B - double C - double D - double sig - double cut - double computed_shift - - cdef struct Morse_Parameters: - double eps - double alpha - double rmin - double cut - double rest - - cdef struct Buckingham_Parameters: - double A - double B - double C - double D - double cut - double discont - double shift - double F1 - double F2 - - cdef struct SoftSphere_Parameters: - double a - double n - double cut - double offset - - cdef struct Hat_Parameters: - double Fmax - double r - - cdef struct LJcos_Parameters: - double eps - double sig - double cut - double offset - - cdef struct LJcos2_Parameters: - double eps - double sig - double offset - double w - - cdef struct GayBerne_Parameters: - double eps - double sig - double cut - double k1 - double k2 - double mu - double nu - - cdef struct Thole_Parameters: - double scaling_coeff - double q1q2 - - cdef struct DPDParameters: - double gamma - double k - double cutoff - int wf - double pref - - cdef struct IA_parameters: - LJ_Parameters lj - - WCA_Parameters wca - - LJcos_Parameters ljcos - - LJcos2_Parameters ljcos2 - - LJGen_Parameters ljgen - - SoftSphere_Parameters soft_sphere - - TabulatedPotential tab - - GayBerne_Parameters gay_berne - - SmoothStep_Parameters smooth_step - - BMHTF_Parameters bmhtf - - Morse_Parameters morse - - Buckingham_Parameters buckingham - - Hertzian_Parameters hertzian - - Gaussian_Parameters gaussian - - DPDParameters dpd_radial - DPDParameters dpd_trans - - Hat_Parameters hat - - Thole_Parameters thole - - cdef IA_parameters * get_ia_param_safe(int i, int j) - cdef string ia_params_get_state() - cdef void ia_params_set_state(string) - cdef void reset_ia_params() - -IF LENNARD_JONES: - cdef extern from "nonbonded_interactions/lj.hpp": - cdef int lennard_jones_set_params(int part_type_a, int part_type_b, - double eps, double sig, double cut, - double shift, double offset, - double min) - -IF WCA: - cdef extern from "nonbonded_interactions/wca.hpp": - cdef int wca_set_params(int part_type_a, int part_type_b, - double eps, double sig) - -IF LJCOS: - cdef extern from "nonbonded_interactions/ljcos.hpp": - cdef int ljcos_set_params(int part_type_a, int part_type_b, - double eps, double sig, - double cut, double offset) - -IF LJCOS2: - cdef extern from "nonbonded_interactions/ljcos2.hpp": - cdef int ljcos2_set_params(int part_type_a, int part_type_b, - double eps, double sig, double offset, - double w) - -IF GAY_BERNE: - cdef extern from "nonbonded_interactions/gay_berne.hpp": - int gay_berne_set_params(int part_type_a, int part_type_b, - double eps, double sig, double cut, - double k1, double k2, - double mu, double nu) - -IF THOLE: - cdef extern from "nonbonded_interactions/thole.hpp": - int thole_set_params(int part_type_a, int part_type_b, double scaling_coeff, double q1q2) - -IF LENNARD_JONES_GENERIC: - cdef extern from "nonbonded_interactions/ljgen.hpp": - IF LJGEN_SOFTCORE: - cdef int ljgen_set_params(int part_type_a, int part_type_b, - double eps, double sig, double cut, - double shift, double offset, - double a1, double a2, double b1, double b2, - double genlj_lambda, double softrad) - ELSE: - cdef int ljgen_set_params(int part_type_a, int part_type_b, - double eps, double sig, double cut, - double shift, double offset, - double a1, double a2, double b1, double b2) - -IF SMOOTH_STEP: - cdef extern from "nonbonded_interactions/smooth_step.hpp": - int smooth_step_set_params(int part_type_a, int part_type_b, - double d, int n, double eps, - double k0, double sig, - double cut) -IF BMHTF_NACL: - cdef extern from "nonbonded_interactions/bmhtf-nacl.hpp": - int BMHTF_set_params(int part_type_a, int part_type_b, - double A, double B, double C, - double D, double sig, double cut) - -IF MORSE: - cdef extern from "nonbonded_interactions/morse.hpp": - int morse_set_params(int part_type_a, int part_type_b, - double eps, double alpha, - double rmin, double cut) - -IF BUCKINGHAM: - cdef extern from "nonbonded_interactions/buckingham.hpp": - int buckingham_set_params(int part_type_a, int part_type_b, - double A, double B, double C, double D, double cut, - double discont, double shift) - -IF SOFT_SPHERE: - cdef extern from "nonbonded_interactions/soft_sphere.hpp": - int soft_sphere_set_params(int part_type_a, int part_type_b, - double a, double n, double cut, double offset) - -IF HERTZIAN: - cdef extern from "nonbonded_interactions/hertzian.hpp": - int hertzian_set_params(int part_type_a, int part_type_b, - double eps, double sig) - -IF GAUSSIAN: - cdef extern from "nonbonded_interactions/gaussian.hpp": - int gaussian_set_params(int part_type_a, int part_type_b, - double eps, double sig, double cut) - -IF DPD: - cdef extern from "dpd.hpp": - int dpd_set_params(int part_type_a, int part_type_b, - double gamma, double k, double r_c, int wf, - double tgamma, double tr_c, int twf) - -IF HAT: - cdef extern from "nonbonded_interactions/hat.hpp": - int hat_set_params(int part_type_a, int part_type_b, - double Fmax, double r) - -IF SOFT_SPHERE: - cdef extern from "nonbonded_interactions/soft_sphere.hpp": - cdef int soft_sphere_set_params(int part_type_a, int part_type_b, - double a, double n, - double cut, double offset) - -IF TABULATED: - cdef extern from "nonbonded_interactions/nonbonded_tab.hpp": - int tabulated_set_params(int part_type_a, int part_type_b, - double min, double max, - vector[double] energy, - vector[double] force) - -cdef extern from "script_interface/interactions/bonded.hpp": - int bonded_ia_params_zero_based_type(int bond_id) except + - -# Map the boost::variant type indices to python type identifiers. These enum -# values must be in the same order as in the definition of the boost::variant. -cdef enum enum_bonded_interaction: - BONDED_IA_NONE = 0, - BONDED_IA_FENE, - BONDED_IA_HARMONIC, - BONDED_IA_QUARTIC, - BONDED_IA_BONDED_COULOMB, - BONDED_IA_BONDED_COULOMB_SR, - BONDED_IA_ANGLE_HARMONIC, - BONDED_IA_ANGLE_COSINE, - BONDED_IA_ANGLE_COSSQUARE, - BONDED_IA_DIHEDRAL, - BONDED_IA_TABULATED_DISTANCE, - BONDED_IA_TABULATED_ANGLE, - BONDED_IA_TABULATED_DIHEDRAL, - BONDED_IA_THERMALIZED_DIST, - BONDED_IA_RIGID_BOND, - BONDED_IA_IBM_TRIEL, - BONDED_IA_IBM_VOLUME_CONSERVATION, - BONDED_IA_IBM_TRIBEND, - BONDED_IA_OIF_GLOBAL_FORCES, - BONDED_IA_OIF_LOCAL_FORCES, - BONDED_IA_VIRTUAL_BOND - -cdef extern from "thermostat.hpp": - void thermalized_bond_set_rng_counter(stdint.uint64_t counter) - -cdef extern from "immersed_boundary/ImmersedBoundaries.hpp": - cppclass ImmersedBoundaries: - double get_current_volume(int softID) - -cdef extern from "immersed_boundaries.hpp": - extern ImmersedBoundaries immersed_boundaries diff --git a/src/python/espressomd/interactions.py b/src/python/espressomd/interactions.py new file mode 100644 index 00000000000..7ffe575bd62 --- /dev/null +++ b/src/python/espressomd/interactions.py @@ -0,0 +1,1627 @@ +# +# 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 abc +import enum +from . import utils +from . import code_features +from .script_interface import ScriptObjectMap, ScriptInterfaceHelper, script_interface_register + + +class NonBondedInteraction(ScriptInterfaceHelper, metaclass=abc.ABCMeta): + """ + Represents an instance of a non-bonded interaction, such as Lennard-Jones. + + Methods + ------- + deactivate() + Reset parameters for the interaction. + + """ + _so_bind_methods = ("deactivate",) + + def __init__(self, **kwargs): + code_features.assert_features(self.__class__.__dict__["_so_feature"]) + if "sip" in kwargs: + super().__init__(**kwargs) + else: + params = self.default_params() + params.update(kwargs) + super().__init__(**params) + + def __str__(self): + return f'{self.__class__.__name__}({self.get_params()})' + + def set_params(self, **kwargs): + """Set new parameters. + + """ + params = self.default_params() + params.update(kwargs) + + err_msg = f"setting {self.__class__.__name__} raised an error" + self.call_method("set_params", handle_errors_message=err_msg, **params) + + def __reduce__(self): + return (NonBondedInteraction._restore_object, + (self.__class__, self.get_params())) + + @classmethod + def _restore_object(cls, derived_class, kwargs): + return derived_class(**kwargs) + + @abc.abstractmethod + def default_params(self): + pass + + +@script_interface_register +class LennardJonesInteraction(NonBondedInteraction): + """ + Standard 6-12 Lennard-Jones potential. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + epsilon : :obj:`float` + Magnitude of the interaction. + sigma : :obj:`float` + Interaction length scale. + cutoff : :obj:`float` + Cutoff distance of the interaction. + shift : :obj:`float` or :obj:`str` \{'auto'\} + Constant shift of the potential. If ``'auto'``, a default value + is computed from ``sigma`` and ``cutoff``. The LJ potential + will be shifted by :math:`4\\epsilon\\cdot\\text{shift}`. + offset : :obj:`float`, optional + Offset distance of the interaction. + min : :obj:`float`, optional + Restricts the interaction to a minimal distance. + + """ + + _so_name = "Interactions::InteractionLJ" + _so_feature = "LENNARD_JONES" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {"offset": 0., "min": 0.} + + +@script_interface_register +class WCAInteraction(NonBondedInteraction): + """ + Standard 6-12 Weeks-Chandler-Andersen potential. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + epsilon : :obj:`float` + Magnitude of the interaction. + sigma : :obj:`float` + Interaction length scale. + + """ + + _so_name = "Interactions::InteractionWCA" + _so_feature = "WCA" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {} + + @property + def cutoff(self): + return self.call_method("get_cutoff") + + +@script_interface_register +class GenericLennardJonesInteraction(NonBondedInteraction): + """ + Generalized Lennard-Jones potential. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + epsilon : :obj:`float` + Magnitude of the interaction. + sigma : :obj:`float` + Interaction length scale. + cutoff : :obj:`float` + Cutoff distance of the interaction. + shift : :obj:`float` or :obj:`str` \{'auto'\} + Constant shift of the potential. If ``'auto'``, a default value + is computed from the other parameters. The LJ potential + will be shifted by :math:`\\epsilon\\cdot\\text{shift}`. + offset : :obj:`float` + Offset distance of the interaction. + e1 : :obj:`int` + Exponent of the repulsion term. + e2 : :obj:`int` + Exponent of the attraction term. + b1 : :obj:`float` + Prefactor of the repulsion term. + b2 : :obj:`float` + Prefactor of the attraction term. + delta : :obj:`float`, optional + ``LJGEN_SOFTCORE`` parameter delta. Allows control over how + smoothly the potential drops to zero as lambda approaches zero. + lam : :obj:`float`, optional + ``LJGEN_SOFTCORE`` parameter lambda. Tune the strength of the + interaction. + + """ + + _so_name = "Interactions::InteractionLJGen" + _so_feature = "LENNARD_JONES_GENERIC" + + def default_params(self): + """Python dictionary of default parameters. + + """ + if code_features.has_features("LJGEN_SOFTCORE"): + return {"delta": 0., "lam": 1.} + return {} + + +@script_interface_register +class LennardJonesCosInteraction(NonBondedInteraction): + """Lennard-Jones cosine interaction. + + Methods + ------- + set_params() + Set or update parameters for the interaction. + Parameters marked as required become optional once the + interaction has been activated for the first time; + subsequent calls to this method update the existing values. + + Parameters + ---------- + epsilon : :obj:`float` + Magnitude of the interaction. + sigma : :obj:`float` + Interaction length scale. + cutoff : :obj:`float` + Cutoff distance of the interaction. + offset : :obj:`float`, optional + Offset distance of the interaction. + """ + + _so_name = "Interactions::InteractionLJcos" + _so_feature = "LJCOS" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {"offset": 0.} + + +@script_interface_register +class LennardJonesCos2Interaction(NonBondedInteraction): + """Second variant of the Lennard-Jones cosine interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + epsilon : :obj:`float` + Magnitude of the interaction. + sigma : :obj:`float` + Interaction length scale. + offset : :obj:`float`, optional + Offset distance of the interaction. + width : :obj:`float` + Width of interaction. + """ + + _so_name = "Interactions::InteractionLJcos2" + _so_feature = "LJCOS2" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {"offset": 0.} + + @property + def cutoff(self): + return self.call_method("get_cutoff") + + +@script_interface_register +class HatInteraction(NonBondedInteraction): + """Hat interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + F_max : :obj:`float` + Magnitude of the interaction. + cutoff : :obj:`float` + Cutoff distance of the interaction. + + """ + + _so_name = "Interactions::InteractionHat" + _so_feature = "HAT" + + def default_params(self): + return {} + + +@script_interface_register +class GayBerneInteraction(NonBondedInteraction): + """Gay--Berne interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + eps : :obj:`float` + Potential well depth. + sig : :obj:`float` + Interaction range. + cut : :obj:`float` + Cutoff distance of the interaction. + k1 : :obj:`float` or :obj:`str` + Molecular elongation. + k2 : :obj:`float`, optional + Ratio of the potential well depths for the side-by-side + and end-to-end configurations. + mu : :obj:`float`, optional + Adjustable exponent. + nu : :obj:`float`, optional + Adjustable exponent. + + """ + + _so_name = "Interactions::InteractionGayBerne" + _so_feature = "GAY_BERNE" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {} + + +@script_interface_register +class TabulatedNonBonded(NonBondedInteraction): + """Tabulated interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + min : :obj:`float`, + The minimal interaction distance. + max : :obj:`float`, + The maximal interaction distance. + energy: array_like of :obj:`float` + The energy table. + force: array_like of :obj:`float` + The force table. + + """ + + _so_name = "Interactions::InteractionTabulated" + _so_feature = "TABULATED" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {} + + @property + def cutoff(self): + return self.call_method("get_cutoff") + + +@script_interface_register +class DPDInteraction(NonBondedInteraction): + """DPD interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + weight_function : :obj:`int`, \{0, 1\} + The distance dependence of the parallel part, + either 0 (constant) or 1 (linear) + gamma : :obj:`float` + Friction coefficient of the parallel part + k : :obj:`float` + Exponent in the modified weight function + r_cut : :obj:`float` + Cutoff of the parallel part + trans_weight_function : :obj:`int`, \{0, 1\} + The distance dependence of the orthogonal part, + either 0 (constant) or 1 (linear) + trans_gamma : :obj:`float` + Friction coefficient of the orthogonal part + trans_r_cut : :obj:`float` + Cutoff of the orthogonal part + + """ + + _so_name = "Interactions::InteractionDPD" + _so_feature = "DPD" + + def default_params(self): + return { + "weight_function": 0, + "gamma": 0.0, + "k": 1.0, + "r_cut": -1.0, + "trans_weight_function": 0, + "trans_gamma": 0.0, + "trans_r_cut": -1.0} + + +@script_interface_register +class SmoothStepInteraction(NonBondedInteraction): + """Smooth step interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + d : :obj:`float` + Short range repulsion parameter. + n : :obj:`int`, optional + Exponent of short range repulsion. + eps : :obj:`float` + Magnitude of the second (soft) repulsion. + k0 : :obj:`float`, optional + Exponential factor in second (soft) repulsion. + sig : :obj:`float`, optional + Length scale of second (soft) repulsion. + cutoff : :obj:`float` + Cutoff distance of the interaction. + + """ + + _so_name = "Interactions::InteractionSmoothStep" + _so_feature = "SMOOTH_STEP" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {"n": 10, "k0": 0., "sig": 0.} + + +@script_interface_register +class BMHTFInteraction(NonBondedInteraction): + """BMHTF interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + a : :obj:`float` + Magnitude of exponential part of the interaction. + b : :obj:`float` + Exponential factor of the interaction. + c : :obj:`float` + Magnitude of the term decaying with the sixth power of r. + d : :obj:`float` + Magnitude of the term decaying with the eighth power of r. + sig : :obj:`float` + Shift in the exponent. + cutoff : :obj:`float` + Cutoff distance of the interaction. + + """ + + _so_name = "Interactions::InteractionBMHTF" + _so_feature = "BMHTF_NACL" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {} + + +@script_interface_register +class MorseInteraction(NonBondedInteraction): + """Morse interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + eps : :obj:`float` + The magnitude of the interaction. + alpha : :obj:`float` + Stiffness of the Morse interaction. + rmin : :obj:`float` + Distance of potential minimum + cutoff : :obj:`float`, optional + Cutoff distance of the interaction. + + """ + + _so_name = "Interactions::InteractionMorse" + _so_feature = "MORSE" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {"cutoff": 0.} + + +@script_interface_register +class BuckinghamInteraction(NonBondedInteraction): + """Buckingham interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + a : :obj:`float` + Magnitude of the exponential part of the interaction. + b : :obj:`float`, optional + Exponent of the exponential part of the interaction. + c : :obj:`float` + Prefactor of term decaying with the sixth power of distance. + d : :obj:`float` + Prefactor of term decaying with the fourth power of distance. + discont : :obj:`float` + Distance below which the potential is linearly continued. + cutoff : :obj:`float` + Cutoff distance of the interaction. + shift: :obj:`float`, optional + Constant potential shift. + + """ + + _so_name = "Interactions::InteractionBuckingham" + _so_feature = "BUCKINGHAM" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {"b": 0., "shift": 0.} + + +@script_interface_register +class SoftSphereInteraction(NonBondedInteraction): + """Soft sphere interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + a : :obj:`float` + Magnitude of the interaction. + n : :obj:`float` + Exponent of the power law. + cutoff : :obj:`float` + Cutoff distance of the interaction. + offset : :obj:`float`, optional + Offset distance of the interaction. + + """ + + _so_name = "Interactions::InteractionSoftSphere" + _so_feature = "SOFT_SPHERE" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {"offset": 0.} + + +@script_interface_register +class HertzianInteraction(NonBondedInteraction): + """Hertzian interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + eps : :obj:`float` + Magnitude of the interaction. + sig : :obj:`float` + Interaction length scale. + + """ + + _so_name = "Interactions::InteractionHertzian" + _so_feature = "HERTZIAN" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {} + + +@script_interface_register +class GaussianInteraction(NonBondedInteraction): + """Gaussian interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + eps : :obj:`float` + Overlap energy epsilon. + sig : :obj:`float` + Variance sigma of the Gaussian interaction. + cutoff : :obj:`float` + Cutoff distance of the interaction. + + """ + + _so_name = "Interactions::InteractionGaussian" + _so_feature = "GAUSSIAN" + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {} + + +@script_interface_register +class TholeInteraction(NonBondedInteraction): + """Thole interaction. + + Methods + ------- + set_params() + Set new parameters for the interaction. + + Parameters + ---------- + scaling_coeff : :obj:`float` + The factor used in the Thole damping function between + polarizable particles i and j. Usually calculated by + the polarizabilities :math:`\\alpha_i`, :math:`\\alpha_j` + and damping parameters :math:`a_i`, :math:`a_j` via + :math:`s_{ij} = \\frac{(a_i+a_j)/2}{((\\alpha_i\\cdot\\alpha_j)^{1/2})^{1/3}}` + q1q2: :obj:`float` + Charge factor of the involved charges. Has to be set because + it acts only on the portion of the Drude core charge that is + associated to the dipole of the atom. For charged, polarizable + atoms that charge is not equal to the particle charge property. + + """ + + _so_name = "Interactions::InteractionThole" + _so_feature = "THOLE" + + def default_params(self): + return {} + + +@script_interface_register +class NonBondedInteractionHandle(ScriptInterfaceHelper): + + """ + Provides access to all non-bonded interactions between two particle types. + + """ + _so_name = "Interactions::NonBondedInteractionHandle" + + def __getattr__(self, key): + obj = super().__getattr__(key) + return globals()[obj.__class__.__name__]( + _types=self.call_method("get_types"), **obj.get_params()) + + def _serialize(self): + serialized = [] + for name, obj in self.get_params().items(): + serialized.append((name, obj.__reduce__()[1])) + return serialized + + def reset(self): + for key in self._valid_parameters(): + getattr(self, key).deactivate() + + @classmethod + def _restore_object(cls, types, kwargs): + objects = {} + for name, (obj_class, obj_params) in kwargs: + objects[name] = obj_class(**obj_params) + return NonBondedInteractionHandle(_types=types, **objects) + + def __reduce__(self): + return (NonBondedInteractionHandle._restore_object, + (self.call_method("get_types"), self._serialize())) + + +@script_interface_register +class NonBondedInteractions(ScriptInterfaceHelper): + """ + Access to non-bonded interaction parameters via ``[i,j]``, where ``i, j`` + are particle types. Returns a :class:`NonBondedInteractionHandle` object. + + Methods + ------- + reset() + Reset all interaction parameters to their default values. + + """ + _so_name = "Interactions::NonBondedInteractions" + _so_creation_policy = "GLOBAL" + _so_bind_methods = ("reset",) + + def keys(self): + return [tuple(x) for x in self.call_method("keys")] + + def _assert_key_type(self, key): + if not isinstance(key, tuple) or len(key) != 2 or \ + not utils.is_valid_type(key[0], int) or not utils.is_valid_type(key[1], int): + raise TypeError( + "NonBondedInteractions[] expects two particle types as indices.") + + def __getitem__(self, key): + self._assert_key_type(key) + return NonBondedInteractionHandle(_types=key) + + def __setitem__(self, key, value): + self._assert_key_type(key) + self.call_method("insert", key=key, object=value) + + def __getstate__(self): + n_types = self.call_method("get_n_types") + state = [] + for i in range(n_types): + for j in range(i, n_types): + handle = NonBondedInteractionHandle(_types=(i, j)) + state.append(((i, j), handle._serialize())) + return {"state": state} + + def __setstate__(self, params): + for types, kwargs in params["state"]: + obj = NonBondedInteractionHandle._restore_object(types, kwargs) + self.call_method("insert", key=types, object=obj) + + @classmethod + def _restore_object(cls, so_callback, so_callback_args, state): + so = so_callback(*so_callback_args) + so.__setstate__(state) + return so + + def __reduce__(self): + so_callback, (so_name, so_bytestring) = super().__reduce__() + return (NonBondedInteractions._restore_object, + (so_callback, (so_name, so_bytestring), self.__getstate__())) + + +class BONDED_IA(enum.IntEnum): + NONE = 0 + FENE = enum.auto() + HARMONIC = enum.auto() + QUARTIC = enum.auto() + BONDED_COULOMB = enum.auto() + BONDED_COULOMB_SR = enum.auto() + ANGLE_HARMONIC = enum.auto() + ANGLE_COSINE = enum.auto() + ANGLE_COSSQUARE = enum.auto() + DIHEDRAL = enum.auto() + TABULATED_DISTANCE = enum.auto() + TABULATED_ANGLE = enum.auto() + TABULATED_DIHEDRAL = enum.auto() + THERMALIZED_DIST = enum.auto() + RIGID_BOND = enum.auto() + IBM_TRIEL = enum.auto() + IBM_VOLUME_CONSERVATION = enum.auto() + IBM_TRIBEND = enum.auto() + OIF_GLOBAL_FORCES = enum.auto() + OIF_LOCAL_FORCES = enum.auto() + VIRTUAL_BOND = enum.auto() + + +class BondedInteraction(ScriptInterfaceHelper, metaclass=abc.ABCMeta): + + """ + Base class for bonded interactions. + + Either called with an interaction id, in which case the interaction + will represent the bonded interaction as it is defined in ESPResSo core, + or called with keyword arguments describing a new interaction. + + """ + + _so_name = "Interactions::BondedInteraction" + _so_creation_policy = "GLOBAL" + + def __init__(self, **kwargs): + feature = self.__class__.__dict__.get("_so_feature") + if feature is not None: + code_features.assert_features(feature) + + if "sip" not in kwargs: + if "bond_id" in kwargs: + # create a new script interface object for a bond that already + # exists in the core via its id (BondedInteractions getter and + # checkpointing constructor #1) + bond_id = kwargs["bond_id"] + super().__init__(bond_id=bond_id) + # Check if the bond type in ESPResSo core matches this class + if self.call_method("get_zero_based_type", + bond_id=bond_id) != self._type_number: + raise RuntimeError( + f"The bond with id {bond_id} is not defined as a " + f"{self._type_number.name} bond in the ESPResSo core.") + self._bond_id = bond_id + self._ctor_params = self.get_params() + else: + # create a new script interface object from bond parameters + # (normal bond creation and checkpointing constructor #2) + params = self.get_default_params() + params.update(kwargs) + super().__init__(**params) + self._ctor_params = params + self._bond_id = -1 + else: + # create a new bond based on a bond in the script interface + # (checkpointing constructor #3) + super().__init__(**kwargs) + self._bond_id = -1 + self._ctor_params = self.get_params() + + def __reduce__(self): + if self._bond_id != -1: + # checkpointing constructor #1 + return (BondedInteraction._restore_object, + (self.__class__, {"bond_id": self._bond_id})) + else: + # checkpointing constructor #2 + return (BondedInteraction._restore_object, + (self.__class__, self._serialize())) + + def _serialize(self): + return self._ctor_params.copy() + + @classmethod + def _restore_object(cls, derived_class, kwargs): + return derived_class(**kwargs) + + def __setattr__(self, attr, value): + super().__setattr__(attr, value) + + @property + def params(self): + return self.get_params() + + @params.setter + def params(self, p): + raise RuntimeError("Bond parameters are immutable.") + + def __str__(self): + return f'{self.__class__.__name__}({self._ctor_params})' + + @abc.abstractmethod + def get_default_params(self): + """Gets default values of optional parameters. + + """ + pass + + def __repr__(self): + return f'<{self}>' + + def __eq__(self, other): + return self.__class__ == other.__class__ and self.call_method( + "is_same_bond", bond=other) + + def __ne__(self, other): + return not self.__eq__(other) + + +@script_interface_register +class FeneBond(BondedInteraction): + + """ + FENE bond. + + Parameters + ---------- + k : :obj:`float` + Magnitude of the bond interaction. + d_r_max : :obj:`float` + Maximum stretch and compression length of the bond. + r_0 : :obj:`float`, optional + Equilibrium bond length. + + """ + + _so_name = "Interactions::FeneBond" + _type_number = BONDED_IA.FENE + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {"r_0": 0.} + + +@script_interface_register +class HarmonicBond(BondedInteraction): + + """ + Harmonic bond. + + Parameters + ---------- + k : :obj:`float` + Magnitude of the bond interaction. + r_0 : :obj:`float` + Equilibrium bond length. + r_cut : :obj:`float`, optional + Maximum distance beyond which the bond is considered broken. + + """ + + _so_name = "Interactions::HarmonicBond" + _type_number = BONDED_IA.HARMONIC + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {"r_cut": 0.} + + +@script_interface_register +class BondedCoulomb(BondedInteraction): + + """ + Bonded Coulomb bond. + + Parameters + ---------- + + prefactor : :obj:`float` + Coulomb prefactor of the bonded Coulomb interaction. + """ + + _so_name = "Interactions::BondedCoulomb" + _so_feature = "ELECTROSTATICS" + _type_number = BONDED_IA.BONDED_COULOMB + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class BondedCoulombSRBond(BondedInteraction): + + """ + Bonded Coulomb short range bond. Calculates the short range part of + Coulomb interactions. + + Parameters + ---------- + + q1q2 : :obj:`float` + Charge factor of the involved particle pair. Note the + particle charges are used to allow e.g. only partial subtraction + of the involved charges. + """ + + _so_name = "Interactions::BondedCoulombSR" + _so_feature = "ELECTROSTATICS" + _type_number = BONDED_IA.BONDED_COULOMB_SR + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class ThermalizedBond(BondedInteraction): + + """ + Thermalized bond. + + Parameters + ---------- + + temp_com : :obj:`float` + Temperature of the Langevin thermostat for the center of mass of the + particle pair. + gamma_com: :obj:`float` + Friction coefficient of the Langevin thermostat for the center of mass + of the particle pair. + temp_distance: :obj:`float` + Temperature of the Langevin thermostat for the distance vector + of the particle pair. + gamma_distance: :obj:`float` + Friction coefficient of the Langevin thermostat for the + 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} + + +@script_interface_register +class RigidBond(BondedInteraction): + + """ + Rigid bond. + + Parameters + ---------- + r : :obj:`float` + Bond length. + ptol : :obj:`float`, optional + Tolerance for positional deviations. + vtop : :obj:`float`, optional + Tolerance for velocity deviations. + + """ + + _so_name = "Interactions::RigidBond" + _so_feature = "BOND_CONSTRAINT" + _type_number = BONDED_IA.RIGID_BOND + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + # TODO rationality of Default Parameters has to be checked + return {"ptol": 0.001, "vtol": 0.001} + + +@script_interface_register +class Dihedral(BondedInteraction): + + """ + Dihedral potential with phase shift. + + Parameters + ---------- + mult : :obj:`int` + Multiplicity of the potential (number of minima). + bend : :obj:`float` + Bending constant. + phase : :obj:`float` + Angle of the first local minimum in radians. + + """ + + _so_name = "Interactions::DihedralBond" + _type_number = BONDED_IA.DIHEDRAL + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class TabulatedDistance(BondedInteraction): + + """ + Tabulated bond length. + + Parameters + ---------- + + min : :obj:`float` + The minimal interaction distance. + max : :obj:`float` + The maximal interaction distance. + energy: array_like of :obj:`float` + The energy table. + force: array_like of :obj:`float` + The force table. + + """ + + _so_name = "Interactions::TabulatedDistanceBond" + _so_feature = "TABULATED" + _type_number = BONDED_IA.TABULATED_DISTANCE + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class TabulatedAngle(BondedInteraction): + + """ + Tabulated bond angle. + + Parameters + ---------- + + energy: array_like of :obj:`float` + The energy table for the range :math:`0-\\pi`. + force: array_like of :obj:`float` + The force table for the range :math:`0-\\pi`. + + """ + + _so_name = "Interactions::TabulatedAngleBond" + _so_feature = "TABULATED" + _type_number = BONDED_IA.TABULATED_ANGLE + + pi = 3.14159265358979 + + def __init__(self, *args, **kwargs): + if len(args) == 0 and "sip" not in kwargs: + kwargs.update({"min": 0., "max": self.pi}) + super().__init__(*args, **kwargs) + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class TabulatedDihedral(BondedInteraction): + + """ + Tabulated bond dihedral. + + Parameters + ---------- + + energy: array_like of :obj:`float` + The energy table for the range :math:`0-2\\pi`. + force: array_like of :obj:`float` + The force table for the range :math:`0-2\\pi`. + + """ + + _so_name = "Interactions::TabulatedDihedralBond" + _so_feature = "TABULATED" + _type_number = BONDED_IA.TABULATED_DIHEDRAL + + pi = 3.14159265358979 + + def __init__(self, *args, **kwargs): + if len(args) == 0 and "sip" not in kwargs: + kwargs.update({"min": 0., "max": 2. * self.pi}) + super().__init__(*args, **kwargs) + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class Virtual(BondedInteraction): + + """ + Virtual bond. + """ + + _so_name = "Interactions::VirtualBond" + _type_number = BONDED_IA.VIRTUAL_BOND + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class AngleHarmonic(BondedInteraction): + + """ + Bond-angle-dependent harmonic potential. + + Parameters + ---------- + phi0 : :obj:`float` + Equilibrium bond angle in radians. + bend : :obj:`float` + Magnitude of the bond interaction. + + """ + + _so_name = "Interactions::AngleHarmonicBond" + _type_number = BONDED_IA.ANGLE_HARMONIC + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class AngleCosine(BondedInteraction): + + """ + Bond-angle-dependent cosine potential. + + Parameters + ---------- + phi0 : :obj:`float` + Equilibrium bond angle in radians. + bend : :obj:`float` + Magnitude of the bond interaction. + + """ + + _so_name = "Interactions::AngleCosineBond" + _type_number = BONDED_IA.ANGLE_COSINE + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class AngleCossquare(BondedInteraction): + + """ + Bond-angle-dependent cosine squared potential. + + Parameters + ---------- + phi0 : :obj:`float` + Equilibrium bond angle in radians. + bend : :obj:`float` + Magnitude of the bond interaction. + + """ + + _so_name = "Interactions::AngleCossquareBond" + _type_number = BONDED_IA.ANGLE_COSSQUARE + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class IBM_Triel(BondedInteraction): + + """ + IBM Triel bond. + + See Figure C.1 in :cite:`kruger12a`. + + Parameters + ---------- + ind1, ind2, ind3 : :obj:`int` + First, second and third bonding partner. Used for + initializing reference state + k1 : :obj:`float` + Shear elasticity for Skalak and Neo-Hookean + k2 : :obj:`float`, optional + Area resistance for Skalak + maxDist : :obj:`float` + Gives an error if an edge becomes longer than maxDist + elasticLaw : :obj:`str`, \{'NeoHookean', 'Skalak'\} + Type of elastic bond + + """ + + _so_name = "Interactions::IBMTriel" + _type_number = BONDED_IA.IBM_TRIEL + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {"k2": 0} + + +@script_interface_register +class IBM_Tribend(BondedInteraction): + + """ + IBM Tribend bond. + + See Figure C.2 in :cite:`kruger12a`. + + Parameters + ---------- + ind1, ind2, ind3, ind4 : :obj:`int` + First, second, third and fourth bonding partner. Used for + initializing reference state + kb : :obj:`float` + Bending modulus + refShape : :obj:`str`, optional, \{'Flat', 'Initial'\} + Reference shape, default is ``'Flat'`` + + """ + + _so_name = "Interactions::IBMTribend" + _type_number = BONDED_IA.IBM_TRIBEND + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {"refShape": "Flat"} + + +@script_interface_register +class IBM_VolCons(BondedInteraction): + + """ + IBM volume conservation bond. + + See Figure C.3 in :cite:`kruger12a`. + + Parameters + ---------- + softID : :obj:`int` + Used to identify the object to which this bond belongs. Each object + (cell) needs its own ID. For performance reasons, it is best to + start from ``softID=0`` and increment by 1 for each subsequent bond. + kappaV : :obj:`float` + Modulus for volume force + + Methods + ------- + current_volume() + Query the current volume of the soft object associated to this bond. + The volume is initialized once all :class:`IBM_Triel` bonds have + been added and the forces have been recalculated. + + """ + + _so_name = "Interactions::IBMVolCons" + _so_bind_methods = ("current_volume",) + _type_number = BONDED_IA.IBM_VOLUME_CONSERVATION + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class OifGlobalForces(BondedInteraction): + + """ + Characterize the distribution of the force of the global mesh deformation + onto individual vertices of the mesh. + + Part of the :ref:`Object-in-fluid` method. + + Parameters + ---------- + A0_g : :obj:`float` + Relaxed area of the mesh + ka_g : :obj:`float` + Area coefficient + V0 : :obj:`float` + Relaxed volume of the mesh + kv : :obj:`float` + Volume coefficient + + """ + + _so_name = "Interactions::OifGlobalForcesBond" + _type_number = BONDED_IA.OIF_GLOBAL_FORCES + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class OifLocalForces(BondedInteraction): + + """ + Characterize the deformation of two triangles sharing an edge. + + Part of the :ref:`Object-in-fluid` method. + + Parameters + ---------- + r0 : :obj:`float` + Equilibrium bond length of triangle edges + ks : :obj:`float` + Non-linear stretching coefficient of triangle edges + kslin : :obj:`float` + Linear stretching coefficient of triangle edges + phi0 : :obj:`float` + Equilibrium angle between the two triangles + kb : :obj:`float` + Bending coefficient for the angle between the two triangles + A01 : :obj:`float` + Equilibrium surface of the first triangle + A02 : :obj:`float` + Equilibrium surface of the second triangle + kal : :obj:`float` + Stretching coefficient of a triangle surface + kvisc : :obj:`float` + Viscous coefficient of the triangle vertices + + """ + + _so_name = "Interactions::OifLocalForcesBond" + _type_number = BONDED_IA.OIF_LOCAL_FORCES + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class QuarticBond(BondedInteraction): + + """ + Quartic bond. + + Parameters + ---------- + k0 : :obj:`float` + Magnitude of the square term. + k1 : :obj:`float` + Magnitude of the fourth order term. + r : :obj:`float` + Equilibrium bond length. + r_cut : :obj:`float` + Maximum interaction length. + """ + + _so_name = "Interactions::QuarticBond" + _type_number = BONDED_IA.QUARTIC + + def get_default_params(self): + """Gets default values of optional parameters. + + """ + return {} + + +@script_interface_register +class BondedInteractions(ScriptObjectMap): + + """ + Represents the bonded interactions list. + + Individual interactions can be accessed using ``BondedInteractions[i]``, + where ``i`` is the bond id. + + Methods + ------- + remove() + Remove a bond from the list. + This is a no-op if the bond does not exist. + + Parameters + ---------- + bond_id : :obj:`int` + + clear() + Remove all bonds. + + """ + + _so_name = "Interactions::BondedInteractions" + _so_creation_policy = "GLOBAL" + _bond_classes = { + cls._type_number: cls for cls in globals().values() + if isinstance(cls, type) and issubclass(cls, BondedInteraction) and cls != BondedInteraction + } + + def add(self, *args): + """ + Add a bond to the list. + + Parameters + ---------- + bond: :class:`espressomd.interactions.BondedInteraction` + Either a bond object... + + """ + + if len(args) == 1 and isinstance(args[0], BondedInteraction): + bonded_ia = args[0] + else: + raise TypeError("A BondedInteraction object needs to be passed.") + bond_id = self._insert_bond(None, bonded_ia) + return bond_id + + def __getitem__(self, bond_id): + self._assert_key_type(bond_id) + + if self.call_method('has_bond', bond_id=bond_id): + bond_obj = self.call_method('get_bond', bond_id=bond_id) + bond_obj._bond_id = bond_id + return bond_obj + + # Find out the type of the interaction from ESPResSo + bond_type = self.call_method("get_zero_based_type", bond_id=bond_id) + + # Check if the bonded interaction exists in ESPResSo core + if bond_type == BONDED_IA.NONE: + raise ValueError(f"The bond with id {bond_id} is not yet defined.") + + # Find the appropriate class representing such a bond + bond_class = self._bond_classes[bond_type] + + # Create a new script interface object (i.e. a copy of the shared_ptr) + # which links to the bonded interaction object + return bond_class(bond_id=bond_id) + + def __setitem__(self, bond_id, bond_obj): + self._insert_bond(bond_id, bond_obj) + + def _insert_bond(self, bond_id, bond_obj): + """ + Inserts a new bond. If a ``bond_id`` is given, the bond is inserted at + that id. If no id is given, a new id is generated. + + Bonds can only be overwritten if the new bond is of the same type as the + old one, e.g. a :class:`~espressomd.interactions.FeneBond` bond can only + be overwritten by another :class:`~espressomd.interactions.FeneBond` bond. + """ + + # Validate arguments + if not isinstance(bond_obj, BondedInteraction): + raise ValueError( + "Only subclasses of BondedInteraction can be assigned.") + + # Send the script interface object pointer to the core + if bond_id is None: + bond_id = self.call_method("insert", object=bond_obj) + else: + # Throw error if attempting to overwrite a bond of different type + self._assert_key_type(bond_id) + if self.call_method("contains", key=bond_id): + old_type = self._bond_classes[ + self.call_method("get_zero_based_type", bond_id=bond_id)] + if not isinstance(bond_obj, old_type): + raise ValueError( + "Bonds can only be overwritten by bonds of equal type.") + self.call_method("insert", key=bond_id, object=bond_obj) + + # Save the bond id in the BondedInteraction instance + bond_obj._bond_id = bond_id + + return bond_id + + def __len__(self): + return self.call_method('get_size') + + # Support iteration over active bonded interactions + def __iter__(self): + for bond_id in self.call_method('get_bond_ids'): + if self.call_method("get_zero_based_type", bond_id=bond_id): + yield self[bond_id] + + def __getstate__(self): + params = {} + for bond_id in self.call_method('get_bond_ids'): + if self.call_method("get_zero_based_type", bond_id=bond_id): + obj = self[bond_id] + if hasattr(obj, "params"): + params[bond_id] = (obj._type_number, obj._serialize()) + return params + + def __setstate__(self, params): + for bond_id, (type_number, bond_params) in params.items(): + self[bond_id] = self._bond_classes[type_number](**bond_params) diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx deleted file mode 100644 index 2344513c14b..00000000000 --- a/src/python/espressomd/interactions.pyx +++ /dev/null @@ -1,2948 +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.string cimport string -cimport cpython.object -import collections - -include "myconfig.pxi" -from . import utils -from .script_interface import ScriptObjectMap, ScriptInterfaceHelper, script_interface_register - - -cdef class NonBondedInteraction: - """ - Represents an instance of a non-bonded interaction, such as Lennard-Jones. - Either called with two particle type id, in which case, the interaction - will represent the bonded interaction as it is defined in ESPResSo core, - or called with keyword arguments describing a new interaction. - - """ - - cdef public object _part_types - cdef public object _params - - # init dict to access all user defined nonbonded-inters via - # user_interactions[type1][type2][parameter] - cdef public object user_interactions - - def __init__(self, *args, **kwargs): - if self.user_interactions is None: - self.user_interactions = {} - # Interaction id as argument - if len(args) == 2 and utils.is_valid_type( - args[0], int) and utils.is_valid_type(args[1], int): - self._part_types = args - - # Load the parameters currently set in the ESPResSo core - self._params = self._get_params_from_es_core() - - # Or have we been called with keyword args describing the interaction - elif len(args) == 0: - utils.check_required_keys(self.required_keys(), kwargs.keys()) - utils.check_valid_keys(self.valid_keys(), kwargs.keys()) - # Initialize default values - self._params = self.default_params() - self._part_types = [-1, -1] - self._params.update(kwargs) - self.validate_params() - else: - raise Exception( - "The constructor has to be called either with two particle type ids (as integer), or with a set of keyword arguments describing a new interaction") - - def is_valid(self): - """Check, if the data stored in the instance still matches what is in ESPResSo. - - """ - temp_params = self._get_params_from_es_core() - return self._params == temp_params - - def get_params(self): - """Get interaction parameters. - - """ - # If this instance refers to an actual interaction defined in - # the es core, load current parameters from there - if self._part_types[0] >= 0 and self._part_types[1] >= 0: - self._params = self._get_params_from_es_core() - return self._params - - def __str__(self): - return f'{self.__class__.__name__}({self.get_params()})' - - def __getstate__(self): - odict = collections.OrderedDict() - odict['user_interactions'] = self.user_interactions - odict['_part_types'] = self._part_types - odict['params'] = self.get_params() - return odict - - def __setstate__(self, state): - self.user_interactions = state['user_interactions'] - self._part_types = state['_part_types'] - self._params = state['params'] - - def set_params(self, **p): - """Update the given parameters. - - """ - # Check, if any key was passed, which is not known - utils.check_valid_keys(self.valid_keys(), p.keys()) - - # When an interaction is newly activated, all required keys must be - # given - if not self.is_active(): - utils.check_required_keys(self.required_keys(), p.keys()) - - # If this instance refers to an interaction defined in the ESPResSo core, - # load the parameters from there - is_valid_ia = self._part_types[0] >= 0 and self._part_types[1] >= 0 - - if is_valid_ia: - self._params = self._get_params_from_es_core() - - # Put in values given by the user - self._params.update(p) - - if is_valid_ia: - self._set_params_in_es_core() - - # update interaction dict when user sets interaction - if self._part_types[0] not in self.user_interactions: - self.user_interactions[self._part_types[0]] = {} - self.user_interactions[self._part_types[0]][self._part_types[1]] = {} - new_params = self.get_params() - for p_key in new_params: - self.user_interactions[self._part_types[0]][ - self._part_types[1]][p_key] = new_params[p_key] - self.user_interactions[self._part_types[0]][ - self._part_types[1]]['type_name'] = self.type_name() - - # defer exception (core and interface must always agree on parameters) - if is_valid_ia: - utils.handle_errors(f'setting {self.type_name()} raised an error') - - def validate_params(self): - """Check that parameters are valid. - - """ - pass - - def __getattribute__(self, name): - """Every time _set_params_in_es_core is called, the parameter dict is also updated. - - """ - attr = object.__getattribute__(self, name) - if hasattr( - attr, '__call__') and attr.__name__ == "_set_params_in_es_core": - def sync_params(*args, **kwargs): - result = attr(*args, **kwargs) - self._params.update(self._get_params_from_es_core()) - return result - return sync_params - else: - return attr - - def _get_params_from_es_core(self): - raise Exception( - "Subclasses of NonBondedInteraction must define the _get_params_from_es_core() method.") - - def _set_params_in_es_core(self): - raise Exception( - "Subclasses of NonBondedInteraction must define the _set_params_in_es_core() method.") - - def default_params(self): - """Virtual method. - - """ - raise Exception( - "Subclasses of NonBondedInteraction must define the default_params() method.") - - def is_active(self): - """Virtual method. - - """ - # If this instance refers to an actual interaction defined in - # the es core, load current parameters from there - if self._part_types[0] >= 0 and self._part_types[1] >= 0: - self._params = self._get_params_from_es_core() - raise Exception( - "Subclasses of NonBondedInteraction must define the is_active() method.") - - def type_name(self): - """Virtual method. - - """ - raise Exception( - "Subclasses of NonBondedInteraction must define the type_name() method.") - - def valid_keys(self): - """Virtual method. - - """ - raise Exception( - "Subclasses of NonBondedInteraction must define the valid_keys() method.") - - def required_keys(self): - """Virtual method. - - """ - raise Exception( - "Subclasses of NonBondedInteraction must define the required_keys() method.") - -IF LENNARD_JONES == 1: - - cdef class LennardJonesInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - Raises - ------ - ValueError - If not true. - """ - if self._params["epsilon"] < 0: - raise ValueError("Lennard-Jones epsilon has to be >=0") - if self._params["sigma"] < 0: - raise ValueError("Lennard-Jones sigma has to be >=0") - if self._params["cutoff"] < 0: - raise ValueError("Lennard-Jones cutoff has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "epsilon": ia_params.lj.eps, - "sigma": ia_params.lj.sig, - "cutoff": ia_params.lj.cut, - "shift": ia_params.lj.shift, - "offset": ia_params.lj.offset, - "min": ia_params.lj.min} - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["epsilon"] > 0) - - def set_params(self, **kwargs): - """Set parameters for the Lennard-Jones interaction. - - Parameters - ---------- - - epsilon : :obj:`float` - Magnitude of the interaction. - sigma : :obj:`float` - Interaction length scale. - cutoff : :obj:`float` - Cutoff distance of the interaction. - shift : :obj:`float` or :obj:`str` \{'auto'\} - Constant shift of the potential. If ``'auto'``, a default value - is computed from ``sigma`` and ``cutoff``. The LJ potential - will be shifted by :math:`4\\epsilon\\cdot\\text{shift}`. - offset : :obj:`float`, optional - Offset distance of the interaction. - min : :obj:`float`, optional - Restricts the interaction to a minimal distance. - - """ - super().set_params(**kwargs) - - def _set_params_in_es_core(self): - # Handle the case of shift="auto" - if self._params["shift"] == "auto": - self._params["shift"] = -(( - self._params["sigma"] / self._params["cutoff"])**12 - ( - self._params["sigma"] / self._params["cutoff"])**6) - - if lennard_jones_set_params( - self._part_types[0], self._part_types[1], - self._params["epsilon"], - self._params["sigma"], - self._params["cutoff"], - self._params["shift"], - self._params["offset"], - self._params["min"]): - raise Exception("Could not set Lennard-Jones parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {"offset": 0., "min": 0.} - - def type_name(self): - """Name of interaction type. - - """ - return "LennardJones" - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"epsilon", "sigma", "cutoff", "shift", "offset", "min"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"epsilon", "sigma", "cutoff", "shift"} - -IF WCA == 1: - - cdef class WCAInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - Raises - ------ - ValueError - If not true. - """ - if self._params["epsilon"] < 0: - raise ValueError("WCA eps has to be >=0") - if self._params["sigma"] < 0: - raise ValueError("WCA sigma has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "epsilon": ia_params.wca.eps, - "sigma": ia_params.wca.sig, - "cutoff": ia_params.wca.cut} - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["epsilon"] > 0) - - def set_params(self, **kwargs): - """Set parameters for the WCA interaction. - - Parameters - ---------- - - epsilon : :obj:`float` - Magnitude of the interaction. - sigma : :obj:`float` - Interaction length scale. - - """ - super().set_params(**kwargs) - - def _set_params_in_es_core(self): - if wca_set_params( - self._part_types[0], self._part_types[1], - self._params["epsilon"], - self._params["sigma"]): - raise Exception("Could not set WCA parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {} - - def type_name(self): - """Name of interaction type. - - """ - return "WCA" - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"epsilon", "sigma"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"epsilon", "sigma"} - -IF LENNARD_JONES_GENERIC == 1: - - cdef class GenericLennardJonesInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - Raises - ------ - ValueError - If not true. - """ - if self._params["epsilon"] < 0: - raise ValueError("Generic Lennard-Jones epsilon has to be >=0") - if self._params["sigma"] < 0: - raise ValueError("Generic Lennard-Jones sigma has to be >=0") - if self._params["cutoff"] < 0: - raise ValueError("Generic Lennard-Jones cutoff has to be >=0") - IF LJGEN_SOFTCORE: - if self._params["delta"] < 0: - raise ValueError( - "Generic Lennard-Jones delta has to be >=0") - if self._params["lam"] < 0 or self._params["lam"] > 1: - raise ValueError( - "Generic Lennard-Jones lam has to be in the range [0,1]") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "epsilon": ia_params.ljgen.eps, - "sigma": ia_params.ljgen.sig, - "cutoff": ia_params.ljgen.cut, - "shift": ia_params.ljgen.shift, - "offset": ia_params.ljgen.offset, - "e1": ia_params.ljgen.a1, - "e2": ia_params.ljgen.a2, - "b1": ia_params.ljgen.b1, - "b2": ia_params.ljgen.b2, - "lam": ia_params.ljgen.lambda1, - "delta": ia_params.ljgen.softrad - } - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["epsilon"] > 0) - - def _set_params_in_es_core(self): - # Handle the case of shift="auto" - if self._params["shift"] == "auto": - self._params["shift"] = -( - self._params["b1"] * (self._params["sigma"] / self._params["cutoff"])**self._params["e1"] - - self._params["b2"] * (self._params["sigma"] / self._params["cutoff"])**self._params["e2"]) - IF LJGEN_SOFTCORE: - if ljgen_set_params(self._part_types[0], self._part_types[1], - self._params["epsilon"], - self._params["sigma"], - self._params["cutoff"], - self._params["shift"], - self._params["offset"], - self._params["e1"], - self._params["e2"], - self._params["b1"], - self._params["b2"], - self._params["lam"], - self._params["delta"]): - raise Exception( - "Could not set Generic Lennard-Jones parameters") - ELSE: - if ljgen_set_params(self._part_types[0], self._part_types[1], - self._params["epsilon"], - self._params["sigma"], - self._params["cutoff"], - self._params["shift"], - self._params["offset"], - self._params["e1"], - self._params["e2"], - self._params["b1"], - self._params["b2"], - ): - raise Exception( - "Could not set Generic Lennard-Jones parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - IF LJGEN_SOFTCORE: - return {"delta": 0., "lam": 1.} - ELSE: - return {} - - def type_name(self): - """Name of interaction type. - - """ - return "GenericLennardJones" - - def set_params(self, **kwargs): - """ - Set parameters for the generic Lennard-Jones interaction. - - Parameters - ---------- - epsilon : :obj:`float` - Magnitude of the interaction. - sigma : :obj:`float` - Interaction length scale. - cutoff : :obj:`float` - Cutoff distance of the interaction. - shift : :obj:`float` or :obj:`str` \{'auto'\} - Constant shift of the potential. If ``'auto'``, a default value - is computed from the other parameters. The LJ potential - will be shifted by :math:`\\epsilon\\cdot\\text{shift}`. - offset : :obj:`float` - Offset distance of the interaction. - e1 : :obj:`int` - Exponent of the repulsion term. - e2 : :obj:`int` - Exponent of the attraction term. - b1 : :obj:`float` - Prefactor of the repulsion term. - b2 : :obj:`float` - Prefactor of the attraction term. - delta : :obj:`float`, optional - ``LJGEN_SOFTCORE`` parameter delta. Allows control over how - smoothly the potential drops to zero as lambda approaches zero. - lam : :obj:`float`, optional - ``LJGEN_SOFTCORE`` parameter lambda. Tune the strength of the - interaction. - - """ - super().set_params(**kwargs) - - def valid_keys(self): - """All parameters that can be set. - - """ - IF LJGEN_SOFTCORE: - return {"epsilon", "sigma", "cutoff", "shift", - "offset", "e1", "e2", "b1", "b2", "delta", "lam"} - ELSE: - return {"epsilon", "sigma", "cutoff", - "shift", "offset", "e1", "e2", "b1", "b2"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"epsilon", "sigma", "cutoff", - "shift", "offset", "e1", "e2", "b1", "b2"} - -IF LJCOS: - - cdef class LennardJonesCosInteraction(NonBondedInteraction): - - def validate_params(self): - if self._params["epsilon"] < 0: - raise ValueError("Lennard-Jones epsilon has to be >=0") - if self._params["sigma"] < 0: - raise ValueError("Lennard-Jones sigma has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "epsilon": ia_params.ljcos.eps, - "sigma": ia_params.ljcos.sig, - "cutoff": ia_params.ljcos.cut, - "offset": ia_params.ljcos.offset, - } - - def is_active(self): - return(self._params["epsilon"] > 0) - - def set_params(self, **kwargs): - """Set parameters for the Lennard-Jones cosine interaction. - - Parameters - ---------- - - epsilon : :obj:`float` - Magnitude of the interaction. - sigma : :obj:`float` - Interaction length scale. - cutoff : :obj:`float` - Cutoff distance of the interaction. - offset : :obj:`float`, optional - Offset distance of the interaction. - """ - super().set_params(**kwargs) - - def _set_params_in_es_core(self): - if ljcos_set_params(self._part_types[0], - self._part_types[1], - self._params["epsilon"], - self._params["sigma"], - self._params["cutoff"], - self._params["offset"]): - raise Exception( - "Could not set Lennard-Jones Cosine parameters") - - def default_params(self): - return {"offset": 0.} - - def type_name(self): - """Name of interaction type. - - """ - return "LennardJonesCos" - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"epsilon", "sigma", "cutoff", "offset"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"epsilon", "sigma", "cutoff"} - -IF LJCOS2: - - cdef class LennardJonesCos2Interaction(NonBondedInteraction): - - def validate_params(self): - if self._params["epsilon"] < 0: - raise ValueError("Lennard-Jones epsilon has to be >=0") - if self._params["sigma"] < 0: - raise ValueError("Lennard-Jones sigma has to be >=0") - if self._params["width"] < 0: - raise ValueError("Parameter width has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return{ - "epsilon": ia_params.ljcos2.eps, - "sigma": ia_params.ljcos2.sig, - "offset": ia_params.ljcos2.offset, - "width": ia_params.ljcos2.w} - - def is_active(self): - return (self._params["epsilon"] > 0) - - def set_params(self, **kwargs): - """Set parameters for the Lennard-Jones cosine squared interaction. - - Parameters - ---------- - - epsilon : :obj:`float` - Magnitude of the interaction. - sigma : :obj:`float` - Interaction length scale. - offset : :obj:`float`, optional - Offset distance of the interaction. - width : :obj:`float` - Width of interaction. - """ - super().set_params(**kwargs) - - def _set_params_in_es_core(self): - if ljcos2_set_params(self._part_types[0], - self._part_types[1], - self._params["epsilon"], - self._params["sigma"], - self._params["offset"], - self._params["width"]): - raise Exception( - "Could not set Lennard-Jones Cosine2 parameters") - - def default_params(self): - return {"offset": 0.} - - def type_name(self): - """Name of interaction type. - - """ - return "LennardJonesCos2" - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"epsilon", "sigma", "offset", "width"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"epsilon", "sigma", "width"} - -IF HAT == 1: - - cdef class HatInteraction(NonBondedInteraction): - - def validate_params(self): - if self._params["F_max"] < 0: - raise ValueError("Hat max force has to be >=0") - if self._params["cutoff"] < 0: - raise ValueError("Hat cutoff has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], self._part_types[1]) - return { - "F_max": ia_params.hat.Fmax, - "cutoff": ia_params.hat.r, - } - - def is_active(self): - return (self._params["F_max"] > 0) - - def set_params(self, **kwargs): - """Set parameters for the Hat interaction. - - Parameters - ---------- - F_max : :obj:`float` - Magnitude of the interaction. - cutoff : :obj:`float` - Cutoff distance of the interaction. - - """ - super().set_params(**kwargs) - - def _set_params_in_es_core(self): - if hat_set_params(self._part_types[0], self._part_types[1], - self._params["F_max"], - self._params["cutoff"]): - raise Exception("Could not set Hat parameters") - - def default_params(self): - return {} - - def type_name(self): - return "Hat" - - def valid_keys(self): - return {"F_max", "cutoff"} - - def required_keys(self): - return {"F_max", "cutoff"} - -IF GAY_BERNE: - - cdef class GayBerneInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - """ - pass - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "eps": ia_params.gay_berne.eps, - "sig": ia_params.gay_berne.sig, - "cut": ia_params.gay_berne.cut, - "k1": ia_params.gay_berne.k1, - "k2": ia_params.gay_berne.k2, - "mu": ia_params.gay_berne.mu, - "nu": ia_params.gay_berne.nu} - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["eps"] > 0) - - def set_params(self, **kwargs): - """Set parameters for the Gay-Berne interaction. - - Parameters - ---------- - eps : :obj:`float` - Potential well depth. - sig : :obj:`float` - Interaction range. - cut : :obj:`float` - Cutoff distance of the interaction. - k1 : :obj:`float` or :obj:`str` - Molecular elongation. - k2 : :obj:`float`, optional - Ratio of the potential well depths for the side-by-side - and end-to-end configurations. - mu : :obj:`float`, optional - Adjustable exponent. - nu : :obj:`float`, optional - Adjustable exponent. - - """ - super().set_params(**kwargs) - - def _set_params_in_es_core(self): - if gay_berne_set_params(self._part_types[0], - self._part_types[1], - self._params["eps"], - self._params["sig"], - self._params["cut"], - self._params["k1"], - self._params["k2"], - self._params["mu"], - self._params["nu"]): - raise Exception("Could not set Gay-Berne parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {} - - def type_name(self): - """Name of interaction type. - - """ - return "GayBerne" - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"eps", "sig", "cut", "k1", "k2", "mu", "nu"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"eps", "sig", "cut", "k1", "k2", "mu", "nu"} - -IF DPD: - - cdef class DPDInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - """ - pass - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], self._part_types[1]) - return { - "weight_function": ia_params.dpd_radial.wf, - "gamma": ia_params.dpd_radial.gamma, - "k": ia_params.dpd_radial.k, - "r_cut": ia_params.dpd_radial.cutoff, - "trans_weight_function": ia_params.dpd_trans.wf, - "trans_gamma": ia_params.dpd_trans.gamma, - "trans_r_cut": ia_params.dpd_trans.cutoff - } - - def is_active(self): - return (self._params["r_cut"] > 0) or ( - self._params["trans_r_cut"] > 0) - - def set_params(self, **kwargs): - """Set parameters for the DPD interaction. - - Parameters - ---------- - weight_function : :obj:`int`, \{0, 1\} - The distance dependence of the parallel part, - either 0 (constant) or 1 (linear) - gamma : :obj:`float` - Friction coefficient of the parallel part - k : :obj:`float` - Exponent in the modified weight function - r_cut : :obj:`float` - Cutoff of the parallel part - trans_weight_function : :obj:`int`, \{0, 1\} - The distance dependence of the orthogonal part, - either 0 (constant) or 1 (linear) - trans_gamma : :obj:`float` - Friction coefficient of the orthogonal part - trans_r_cut : :obj:`float` - Cutoff of the orthogonal part - - """ - super().set_params(**kwargs) - - def _set_params_in_es_core(self): - if dpd_set_params(self._part_types[0], - self._part_types[1], - self._params["gamma"], - self._params["k"], - self._params["r_cut"], - self._params["weight_function"], - self._params["trans_gamma"], - self._params["trans_r_cut"], - self._params["trans_weight_function"]): - raise Exception("Could not set DPD parameters") - - def default_params(self): - return { - "weight_function": 0, - "gamma": 0.0, - "k": 1.0, - "r_cut": -1.0, - "trans_weight_function": 0, - "trans_gamma": 0.0, - "trans_r_cut": -1.0} - - def type_name(self): - return "DPD" - - def valid_keys(self): - return {"weight_function", "gamma", "k", "r_cut", - "trans_weight_function", "trans_gamma", "trans_r_cut"} - - def required_keys(self): - return set() - -IF SMOOTH_STEP == 1: - - cdef class SmoothStepInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - """ - if self._params["eps"] < 0: - raise ValueError("Smooth-step eps has to be >=0") - if self._params["offset"] < 0: - raise ValueError("Smooth-step offset has to be >=0") - if self._params["cutoff"] < 0: - raise ValueError("Smooth-step cutoff has to be >=0") - if self._params["cap"] < 0: - raise ValueError("Smooth-step cap has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "d": ia_params.smooth_step.d, - "n": ia_params.smooth_step.n, - "eps": ia_params.smooth_step.eps, - "k0": ia_params.smooth_step.k0, - "sig": ia_params.smooth_step.sig, - "cutoff": ia_params.smooth_step.cut - } - - def is_active(self): - """Check if interaction is active. - - """ - return ((self._params["eps"] > 0) and (self._params["sig"] > 0)) - - def _set_params_in_es_core(self): - if smooth_step_set_params(self._part_types[0], - self._part_types[1], - self._params["d"], - self._params["n"], - self._params["eps"], - self._params["k0"], - self._params["sig"], - self._params["cutoff"]): - raise Exception("Could not set smooth-step parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {"n": 10, "k0": 0., "sig": 0.} - - def type_name(self): - """Name of interaction type. - - """ - return "SmoothStep" - - def set_params(self, **kwargs): - """ - Set parameters for the smooth-step interaction. - - Parameters - ---------- - d : :obj:`float` - Short range repulsion parameter. - n : :obj:`int`, optional - Exponent of short range repulsion. - eps : :obj:`float` - Magnitude of the second (soft) repulsion. - k0 : :obj:`float`, optional - Exponential factor in second (soft) repulsion. - sig : :obj:`float`, optional - Length scale of second (soft) repulsion. - cutoff : :obj:`float` - Cutoff distance of the interaction. - - """ - super().set_params(**kwargs) - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"d", "n", "eps", "k0", "sig", "cutoff"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"d", "eps", "cutoff"} - -IF BMHTF_NACL == 1: - - cdef class BMHTFInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - """ - if self._params["a"] < 0: - raise ValueError("BMHTF a has to be >=0") - if self._params["c"] < 0: - raise ValueError("BMHTF c has to be >=0") - if self._params["d"] < 0: - raise ValueError("BMHTF d has to be >=0") - if self._params["cutoff"] < 0: - raise ValueError("BMHTF cutoff has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe(self._part_types[0], - self._part_types[1]) - return { - "a": ia_params.bmhtf.A, - "b": ia_params.bmhtf.B, - "c": ia_params.bmhtf.C, - "d": ia_params.bmhtf.D, - "sig": ia_params.bmhtf.sig, - "cutoff": ia_params.bmhtf.cut, - } - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["a"] > 0) and ( - self._params["c"] > 0) and (self._params["d"] > 0) - - def _set_params_in_es_core(self): - if BMHTF_set_params(self._part_types[0], - self._part_types[1], - self._params["a"], - self._params["b"], - self._params["c"], - self._params["d"], - self._params["sig"], - self._params["cutoff"]): - raise Exception("Could not set BMHTF parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {} - - def type_name(self): - """Name of interaction type. - - """ - return "BMHTF" - - def set_params(self, **kwargs): - """ - Set parameters for the BMHTF interaction. - - Parameters - ---------- - a : :obj:`float` - Magnitude of exponential part of the interaction. - b : :obj:`float` - Exponential factor of the interaction. - c : :obj:`float` - Magnitude of the term decaying with the sixth power of r. - d : :obj:`float` - Magnitude of the term decaying with the eighth power of r. - sig : :obj:`float` - Shift in the exponent. - cutoff : :obj:`float` - Cutoff distance of the interaction. - - """ - super().set_params(**kwargs) - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"a", "b", "c", "d", "sig", "cutoff"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"a", "b", "c", "d", "sig", "cutoff"} - -IF MORSE == 1: - - cdef class MorseInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - """ - if self._params["eps"] < 0: - raise ValueError("Morse eps has to be >=0") - if self._params["offset"] < 0: - raise ValueError("Morse offset has to be >=0") - if self._params["cutoff"] < 0: - raise ValueError("Morse cutoff has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "eps": ia_params.morse.eps, - "alpha": ia_params.morse.alpha, - "rmin": ia_params.morse.rmin, - "cutoff": ia_params.morse.cut - } - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["eps"] > 0) - - def _set_params_in_es_core(self): - if morse_set_params(self._part_types[0], - self._part_types[1], - self._params["eps"], - self._params["alpha"], - self._params["rmin"], - self._params["cutoff"]): - raise Exception("Could not set Morse parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {"cutoff": 0.} - - def type_name(self): - """Name of interaction type. - - """ - return "Morse" - - def set_params(self, **kwargs): - """ - Set parameters for the Morse interaction. - - Parameters - ---------- - eps : :obj:`float` - The magnitude of the interaction. - alpha : :obj:`float` - Stiffness of the Morse interaction. - rmin : :obj:`float` - Distance of potential minimum - cutoff : :obj:`float`, optional - Cutoff distance of the interaction. - - """ - super().set_params(**kwargs) - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"eps", "alpha", "rmin", "cutoff"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"eps", "alpha", "rmin"} - -IF BUCKINGHAM == 1: - - cdef class BuckinghamInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - """ - if self._params["a"] < 0: - raise ValueError("Buckingham a has to be >=0") - if self._params["b"] < 0: - raise ValueError("Buckingham b has to be >=0") - if self._params["c"] < 0: - raise ValueError("Buckingham c has to be >=0") - if self._params["d"] < 0: - raise ValueError("Buckingham d has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "a": ia_params.buckingham.A, - "b": ia_params.buckingham.B, - "c": ia_params.buckingham.C, - "d": ia_params.buckingham.D, - "cutoff": ia_params.buckingham.cut, - "discont": ia_params.buckingham.discont, - "shift": ia_params.buckingham.shift - } - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["a"] > 0) or (self._params["b"] > 0) or ( - self._params["d"] > 0) or (self._params["shift"] > 0) - - def _set_params_in_es_core(self): - if buckingham_set_params(self._part_types[0], self._part_types[1], - self._params["a"], - self._params["b"], - self._params["c"], - self._params["d"], - self._params["cutoff"], - self._params["discont"], - self._params["shift"]): - raise Exception("Could not set Buckingham parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {"b": 0., "shift": 0.} - - def type_name(self): - """Name of interaction type. - - """ - return "Buckingham" - - def set_params(self, **kwargs): - """ - Set parameters for the Buckingham interaction. - - Parameters - ---------- - a : :obj:`float` - Magnitude of the exponential part of the interaction. - b : :obj:`float`, optional - Exponent of the exponential part of the interaction. - c : :obj:`float` - Prefactor of term decaying with the sixth power of distance. - d : :obj:`float` - Prefactor of term decaying with the fourth power of distance. - discont : :obj:`float` - Distance below which the potential is linearly continued. - cutoff : :obj:`float` - Cutoff distance of the interaction. - shift: :obj:`float`, optional - Constant potential shift. - - """ - super().set_params(**kwargs) - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"a", "b", "c", "d", "discont", "cutoff", "shift"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"a", "c", "d", "discont", "cutoff"} - -IF SOFT_SPHERE == 1: - - cdef class SoftSphereInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - """ - if self._params["a"] < 0: - raise ValueError("Soft-sphere a has to be >=0") - if self._params["offset"] < 0: - raise ValueError("Soft-sphere offset has to be >=0") - if self._params["cutoff"] < 0: - raise ValueError("Soft-sphere cutoff has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe(self._part_types[0], - self._part_types[1]) - return { - "a": ia_params.soft_sphere.a, - "n": ia_params.soft_sphere.n, - "cutoff": ia_params.soft_sphere.cut, - "offset": ia_params.soft_sphere.offset - } - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["a"] > 0) - - def _set_params_in_es_core(self): - if soft_sphere_set_params(self._part_types[0], - self._part_types[1], - self._params["a"], - self._params["n"], - self._params["cutoff"], - self._params["offset"]): - raise Exception("Could not set Soft-sphere parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {"offset": 0.} - - def type_name(self): - """Name of interaction type. - - """ - return "SoftSphere" - - def set_params(self, **kwargs): - """ - Set parameters for the Soft-sphere interaction. - - Parameters - ---------- - a : :obj:`float` - Magnitude of the interaction. - n : :obj:`float` - Exponent of the power law. - cutoff : :obj:`float` - Cutoff distance of the interaction. - offset : :obj:`float`, optional - Offset distance of the interaction. - - """ - super().set_params(**kwargs) - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"a", "n", "cutoff", "offset"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"a", "n", "cutoff"} - - -IF HERTZIAN == 1: - - cdef class HertzianInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - """ - if self._params["eps"] < 0: - raise ValueError("Hertzian eps a has to be >=0") - if self._params["sig"] < 0: - raise ValueError("Hertzian sig has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "eps": ia_params.hertzian.eps, - "sig": ia_params.hertzian.sig - } - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["eps"] > 0) - - def _set_params_in_es_core(self): - if hertzian_set_params(self._part_types[0], - self._part_types[1], - self._params["eps"], - self._params["sig"]): - raise Exception("Could not set Hertzian parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {} - - def type_name(self): - """Name of interaction type. - - """ - return "Hertzian" - - def set_params(self, **kwargs): - """ - Set parameters for the Hertzian interaction. - - Parameters - ---------- - eps : :obj:`float` - Magnitude of the interaction. - sig : :obj:`float` - Parameter sigma. Determines the length over which the potential - decays. - - """ - super().set_params(**kwargs) - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"eps", "sig"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"eps", "sig"} - -IF GAUSSIAN == 1: - - cdef class GaussianInteraction(NonBondedInteraction): - - def validate_params(self): - """Check that parameters are valid. - - """ - if self._params["eps"] < 0: - raise ValueError("Gaussian eps a has to be >=0") - if self._params["sig"] < 0: - raise ValueError("Gaussian sig has to be >=0") - if self._params["offset"] < 0: - raise ValueError("Gaussian offset has to be >=0") - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - return { - "eps": ia_params.gaussian.eps, - "sig": ia_params.gaussian.sig, - "cutoff": ia_params.gaussian.cut - } - - def is_active(self): - """Check if interaction is active. - - """ - return (self._params["eps"] > 0) - - def _set_params_in_es_core(self): - if gaussian_set_params(self._part_types[0], - self._part_types[1], - self._params["eps"], - self._params["sig"], - self._params["cutoff"]): - raise Exception( - "Could not set Gaussian interaction parameters") - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {} - - def type_name(self): - """Name of interaction type. - - """ - return "Gaussian" - - def set_params(self, **kwargs): - """ - Set parameters for the Gaussian interaction. - - Parameters - ---------- - eps : :obj:`float` - Overlap energy epsilon. - sig : :obj:`float` - Variance sigma of the Gaussian interaction. - cutoff : :obj:`float` - Cutoff distance of the interaction. - - """ - super().set_params(**kwargs) - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"eps", "sig", "cutoff"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"eps", "sig", "cutoff"} - - -class NonBondedInteractionHandle: - - """ - Provides access to all non-bonded interactions between two particle types. - - """ - - def __init__(self, _type1, _type2): - if not (utils.is_valid_type(_type1, int) - and utils.is_valid_type(_type2, int)): - raise TypeError("The particle types have to be of type integer.") - - # Here, add one line for each nonbonded ia - IF LENNARD_JONES: - self.lennard_jones = LennardJonesInteraction(_type1, _type2) - IF WCA: - self.wca = WCAInteraction(_type1, _type2) - IF SOFT_SPHERE: - self.soft_sphere = SoftSphereInteraction(_type1, _type2) - IF LENNARD_JONES_GENERIC: - self.generic_lennard_jones = GenericLennardJonesInteraction( - _type1, _type2) - IF LJCOS: - self.lennard_jones_cos = LennardJonesCosInteraction(_type1, _type2) - IF LJCOS2: - self.lennard_jones_cos2 = LennardJonesCos2Interaction( - _type1, _type2) - IF SMOOTH_STEP: - self.smooth_step = SmoothStepInteraction(_type1, _type2) - IF BMHTF_NACL: - self.bmhtf = BMHTFInteraction(_type1, _type2) - IF MORSE: - self.morse = MorseInteraction(_type1, _type2) - IF BUCKINGHAM: - self.buckingham = BuckinghamInteraction(_type1, _type2) - IF HERTZIAN: - self.hertzian = HertzianInteraction(_type1, _type2) - IF GAUSSIAN: - self.gaussian = GaussianInteraction(_type1, _type2) - IF TABULATED: - self.tabulated = TabulatedNonBonded(_type1, _type2) - IF GAY_BERNE: - self.gay_berne = GayBerneInteraction(_type1, _type2) - IF DPD: - self.dpd = DPDInteraction(_type1, _type2) - IF HAT: - self.hat = HatInteraction(_type1, _type2) - IF THOLE: - self.thole = TholeInteraction(_type1, _type2) - - -cdef class NonBondedInteractions: - """ - Access to non-bonded interaction parameters via ``[i,j]``, where ``i, j`` - are particle types. Returns a :class:`NonBondedInteractionHandle` object. - Also: access to force capping. - - """ - - def __getitem__(self, key): - if not isinstance(key, tuple): - raise ValueError( - "NonBondedInteractions[] expects two particle types as indices.") - if len(key) != 2 or (not utils.is_valid_type(key[0], int)) or ( - not utils.is_valid_type(key[1], int)): - raise ValueError( - "NonBondedInteractions[] expects two particle types as indices.") - return NonBondedInteractionHandle(key[0], key[1]) - - def __getstate__(self): - cdef string core_state - core_state = ia_params_get_state() - return core_state - - def __setstate__(self, core_state): - cdef string state = core_state - ia_params_set_state(state) - - def reset(self): - """ - Reset all interaction parameters to their default values. - """ - - reset_ia_params() - - -class BondedInteraction(ScriptInterfaceHelper): - - """ - Base class for bonded interactions. - - Either called with an interaction id, in which case the interaction - will represent the bonded interaction as it is defined in ESPResSo core, - or called with keyword arguments describing a new interaction. - - """ - - _so_name = "Interactions::BondedInteraction" - _so_creation_policy = "GLOBAL" - - def __init__(self, *args, **kwargs): - if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], dict): - # this branch is only visited by checkpointing constructor #2 - kwargs = args[0] - args = [] - - if not 'sip' in kwargs: - if len(args) == 1 and utils.is_valid_type(args[0], int): - # create a new script interface object for a bond that already - # exists in the core via bond_id (checkpointing constructor #1) - bond_id = args[0] - # Check if the bond type in ESPResSo core matches this class - if get_bonded_interaction_type_from_es_core( - bond_id) != self.type_number(): - raise Exception( - f"The bond with id {bond_id} is not defined as a " - f"{self.type_name()} bond in the ESPResSo core.") - super().__init__(bond_id=bond_id) - self._bond_id = bond_id - self._ctor_params = self._get_params_from_es_core() - else: - # create a bond from bond parameters - params = self.get_default_params() - params.update(kwargs) - self.validate_params(params) - super().__init__(*args, **params) - utils.check_valid_keys(self.valid_keys(), kwargs.keys()) - self._ctor_params = params - self._bond_id = -1 - else: - # create a new bond based on a bond in the script interface - # (checkpointing constructor #2 or BondedInteractions getter) - super().__init__(**kwargs) - self._bond_id = -1 - self._ctor_params = self._get_params_from_es_core() - - def __reduce__(self): - if self._bond_id != -1: - # checkpointing constructor #1 - return (self.__class__, (self._bond_id,)) - else: - # checkpointing constructor #2 - return (self.__class__, (self._ctor_params,)) - - def __setattr__(self, attr, value): - super().__setattr__(attr, value) - - @property - def params(self): - return self._get_params_from_es_core() - - @params.setter - def params(self, p): - raise RuntimeError("Bond parameters are immutable.") - - def validate_params(self, params): - """Check that parameters are valid. - - """ - pass - - def _get_params_from_es_core(self): - return {key: getattr(self, key) for key in self.valid_keys()} - - def __str__(self): - return f'{self.__class__.__name__}({self._ctor_params})' - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - raise Exception( - "Subclasses of BondedInteraction must define the get_default_params() method.") - - def type_number(self): - raise Exception( - "Subclasses of BondedInteraction must define the type_number() method.") - - def type_name(self): - """Name of interaction type. - - """ - raise Exception( - "Subclasses of BondedInteraction must define the type_name() method.") - - def valid_keys(self): - """All parameters that can be set. - - """ - return set(self._valid_parameters()) - - def required_keys(self): - """Parameters that have to be set. - - """ - return self.valid_keys().difference(self.get_default_params().keys()) - - def __repr__(self): - return f'<{self}>' - - def __eq__(self, other): - return self.__richcmp__(other, cpython.object.Py_EQ) - - def __ne__(self, other): - return self.__richcmp__(other, cpython.object.Py_NE) - - def __richcmp__(self, other, op): - are_equal = self.__class__ == other.__class__ and self.call_method( - "get_address") == other.call_method("get_address") - if op == cpython.object.Py_EQ: - return are_equal - elif op == cpython.object.Py_NE: - return not are_equal - else: - raise NotImplementedError("only equality comparison is supported") - - -class BondedInteractionNotDefined: - - def __init__(self, *args, **kwargs): - raise Exception( - self.__class__.__name__ + " not compiled into ESPResSo core") - - def type_number(self): - raise Exception(f"{self.name} has to be defined in myconfig.hpp.") - - def type_name(self): - """Name of interaction type. - - """ - raise Exception(f"{self.name} has to be defined in myconfig.hpp.") - - def valid_keys(self): - """All parameters that can be set. - - """ - raise Exception(f"{self.name} has to be defined in myconfig.hpp.") - - def required_keys(self): - """Parameters that have to be set. - - """ - raise Exception(f"{self.name} has to be defined in myconfig.hpp.") - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - raise Exception(f"{self.name} has to be defined in myconfig.hpp.") - - def _get_params_from_es_core(self): - raise Exception(f"{self.name} has to be defined in myconfig.hpp.") - - def _set_params_in_es_core(self): - raise Exception(f"{self.name} has to be defined in myconfig.hpp.") - - -@script_interface_register -class FeneBond(BondedInteraction): - - """ - FENE bond. - - Parameters - ---------- - k : :obj:`float` - Magnitude of the bond interaction. - d_r_max : :obj:`float` - Maximum stretch and compression length of the bond. - r_0 : :obj:`float`, optional - Equilibrium bond length. - - """ - - _so_name = "Interactions::FeneBond" - - def type_number(self): - return BONDED_IA_FENE - - def type_name(self): - """Name of interaction type. - - """ - return "FENE" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {"r_0": 0.} - - -@script_interface_register -class HarmonicBond(BondedInteraction): - - """ - Harmonic bond. - - Parameters - ---------- - k : :obj:`float` - Magnitude of the bond interaction. - r_0 : :obj:`float` - Equilibrium bond length. - r_cut : :obj:`float`, optional - Maximum distance beyond which the bond is considered broken. - - """ - - _so_name = "Interactions::HarmonicBond" - - def type_number(self): - return BONDED_IA_HARMONIC - - def type_name(self): - """Name of interaction type. - - """ - return "HARMONIC" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {"r_cut": 0.} - - -if ELECTROSTATICS: - - @script_interface_register - class BondedCoulomb(BondedInteraction): - - """ - Bonded Coulomb bond. - - Parameters - ---------- - - prefactor : :obj:`float` - Coulomb prefactor of the bonded Coulomb interaction. - """ - - _so_name = "Interactions::BondedCoulomb" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_BONDED_COULOMB - - def type_name(self): - return "BONDED_COULOMB" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - -if ELECTROSTATICS: - - @script_interface_register - class BondedCoulombSRBond(BondedInteraction): - - """ - Bonded Coulomb short range bond. Calculates the short range part of - Coulomb interactions. - - Parameters - ---------- - - q1q2 : :obj:`float` - Charge factor of the involved particle pair. Note the - particle charges are used to allow e.g. only partial subtraction - of the involved charges. - """ - - _so_name = "Interactions::BondedCoulombSR" - - def type_number(self): - return BONDED_IA_BONDED_COULOMB_SR - - def type_name(self): - return "BONDED_COULOMB_SR" - - def get_default_params(self): - return {} - - -@script_interface_register -class ThermalizedBond(BondedInteraction): - - """ - Thermalized bond. - - Parameters - ---------- - - temp_com : :obj:`float` - Temperature of the Langevin thermostat for the center of mass of the - particle pair. - gamma_com: :obj:`float` - Friction coefficient of the Langevin thermostat for the center of mass - of the particle pair. - temp_distance: :obj:`float` - Temperature of the Langevin thermostat for the distance vector - of the particle pair. - gamma_distance: :obj:`float` - Friction coefficient of the Langevin thermostat for the - distance vector of the particle pair. - r_cut: :obj:`float`, optional - Maximum distance beyond which the bond is considered broken. - seed : :obj:`int` - Initial counter value (or seed) of the philox RNG. - Required on the first thermalized bond in the system. Must be positive. - If prompted, it does not return the initially set counter value - (the seed) but the current state of the RNG. - - """ - - _so_name = "Interactions::ThermalizedBond" - - def __init__(self, *args, **kwargs): - counter = None - # Interaction id as argument - if len(args) == 2 and isinstance(args[0], (dict, int)): - counter = args[1] - args = (args[0],) - super().__init__(*args, **kwargs) - if counter is not None: - thermalized_bond_set_rng_counter(counter) - if self.params["seed"] is None and thermalized_bond.is_seed_required(): - raise ValueError( - "A seed has to be given as keyword argument on first activation of the thermalized bond") - - def __reduce__(self): - counter = thermalized_bond.rng_counter() - if self._bond_id != -1: - return (self.__class__, (self._bond_id, counter)) - else: - return (self.__class__, (self._ctor_params, counter)) - - def type_number(self): - return BONDED_IA_THERMALIZED_DIST - - def type_name(self): - return "THERMALIZED_DIST" - - def validate_params(self, params): - if params["seed"] is not None: - utils.check_type_or_throw_except( - params["seed"], 1, int, "seed must be a positive integer") - if params["seed"] < 0: - raise ValueError("seed must be a positive integer") - - def get_default_params(self): - return {"r_cut": 0., "seed": None} - - -IF THOLE: - - cdef class TholeInteraction(NonBondedInteraction): - - def validate_params(self): - pass - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params - ia_params = get_ia_param_safe(self._part_types[0], - self._part_types[1]) - return { - "scaling_coeff": ia_params.thole.scaling_coeff, - "q1q2": ia_params.thole.q1q2 - } - - def is_active(self): - return (self._params["scaling_coeff"] != 0) - - def set_params(self, **kwargs): - """Set parameters for the Thole interaction. - - Parameters - ---------- - scaling_coeff : :obj:`float` - The factor used in the Thole damping function between - polarizable particles i and j. Usually calculated by - the polarizabilities :math:`\\alpha_i`, :math:`\\alpha_j` - and damping parameters :math:`a_i`, :math:`a_j` via - :math:`s_{ij} = \\frac{(a_i+a_j)/2}{((\\alpha_i\\cdot\\alpha_j)^{1/2})^{1/3}}` - q1q2: :obj:`float` - Charge factor of the involved charges. Has to be set because - it acts only on the portion of the Drude core charge that is - associated to the dipole of the atom. For charged, polarizable - atoms that charge is not equal to the particle charge property. - - """ - super().set_params(**kwargs) - - def _set_params_in_es_core(self): - if thole_set_params(self._part_types[0], self._part_types[1], - self._params["scaling_coeff"], - self._params["q1q2"]): - raise Exception("Could not set Thole parameters") - - def default_params(self): - return {} - - def type_name(self): - return "Thole" - - def valid_keys(self): - return {"scaling_coeff", "q1q2"} - - def required_keys(self): - return {"scaling_coeff", "q1q2"} - - -IF BOND_CONSTRAINT == 1: - - @script_interface_register - class RigidBond(BondedInteraction): - - """ - Rigid bond. - - Parameters - ---------- - r : :obj:`float` - Bond length. - ptol : :obj:`float`, optional - Tolerance for positional deviations. - vtop : :obj:`float`, optional - Tolerance for velocity deviations. - - """ - - _so_name = "Interactions::RigidBond" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_RIGID_BOND - - def type_name(self): - """Name of interaction type. - - """ - return "RIGID" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - # TODO rationality of Default Parameters has to be checked - return {"ptol": 0.001, "vtol": 0.001} - -ELSE: - class RigidBond(BondedInteractionNotDefined): - name = "RIGID" - - -@script_interface_register -class Dihedral(BondedInteraction): - - """ - Dihedral potential with phase shift. - - Parameters - ---------- - mult : :obj:`int` - Multiplicity of the potential (number of minima). - bend : :obj:`float` - Bending constant. - phase : :obj:`float` - Angle of the first local minimum in radians. - - """ - - _so_name = "Interactions::DihedralBond" - - def type_number(self): - return BONDED_IA_DIHEDRAL - - def type_name(self): - """Name of interaction type. - - """ - return "DIHEDRAL" - - def validate_params(self, params): - """Check that parameters are valid. - - """ - if params["mult"] is not None: - utils.check_type_or_throw_except( - params["mult"], 1, int, "mult must be a positive integer") - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - -IF TABULATED: - class _TabulatedBase(BondedInteraction): - - """ - Parent class for tabulated bonds. - - Parameters - ---------- - - min : :obj:`float` - The minimal interaction distance. Has to be 0 for angles and dihedrals. - max : :obj:`float` - The maximal interaction distance. Has to be pi for angles and 2pi for - dihedrals. - energy: array_like of :obj:`float` - The energy table. - force: array_like of :obj:`float` - The force table. - - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def type_number(self): - return "BONDED_IA_TABULATED" - - def type_name(self): - """Name of interaction type. - - """ - return "TABULATED_BOND" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - @script_interface_register - class TabulatedDistance(_TabulatedBase): - - """ - Tabulated bond length. - - Parameters - ---------- - - min : :obj:`float` - The minimal interaction distance. - max : :obj:`float` - The maximal interaction distance. - energy: array_like of :obj:`float` - The energy table. - force: array_like of :obj:`float` - The force table. - - """ - - _so_name = "Interactions::TabulatedDistanceBond" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_TABULATED_DISTANCE - - def type_name(self): - """Name of interaction type. - - """ - return "TABULATED_DISTANCE" - - @script_interface_register - class TabulatedAngle(_TabulatedBase): - - """ - Tabulated bond angle. - - Parameters - ---------- - - energy: array_like of :obj:`float` - The energy table for the range :math:`0-\\pi`. - force: array_like of :obj:`float` - The force table for the range :math:`0-\\pi`. - - """ - - _so_name = "Interactions::TabulatedAngleBond" - - pi = 3.14159265358979 - - def __init__(self, *args, **kwargs): - if len(args) == 0: - kwargs.update({"min": 0., "max": self.pi}) - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_TABULATED_ANGLE - - def type_name(self): - """Name of interaction type. - - """ - return "TABULATED_ANGLE" - - def validate_params(self, params): - """Check that parameters are valid. - - """ - phi = [params["min"], params["max"]] - if abs(phi[0] - 0.) > 1e-5 or abs(phi[1] - self.pi) > 1e-5: - raise ValueError(f"Tabulated angle expects forces/energies " - f"within the range [0, pi], got {phi}") - - @script_interface_register - class TabulatedDihedral(_TabulatedBase): - - """ - Tabulated bond dihedral. - - Parameters - ---------- - - energy: array_like of :obj:`float` - The energy table for the range :math:`0-2\\pi`. - force: array_like of :obj:`float` - The force table for the range :math:`0-2\\pi`. - - """ - - _so_name = "Interactions::TabulatedDihedralBond" - - pi = 3.14159265358979 - - def __init__(self, *args, **kwargs): - if len(args) == 0: - kwargs.update({"min": 0., "max": 2. * self.pi}) - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_TABULATED_DIHEDRAL - - def type_name(self): - """Name of interaction type. - - """ - return "TABULATED_DIHEDRAL" - - def validate_params(self, params): - """Check that parameters are valid. - - """ - phi = [params["min"], params["max"]] - if abs(phi[0] - 0.) > 1e-5 or abs(phi[1] - 2 * self.pi) > 1e-5: - raise ValueError(f"Tabulated dihedral expects forces/energies " - f"within the range [0, 2*pi], got {phi}") - - cdef class TabulatedNonBonded(NonBondedInteraction): - - cdef int state - - def __init__(self, *args, **kwargs): - self.state = -1 - super().__init__(*args, **kwargs) - - def type_number(self): - return "TABULATED_NONBONDED" - - def type_name(self): - """Name of the potential. - - """ - return "TABULATED" - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"min", "max", "energy", "force"} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"min", "max", "energy", "force"} - - def set_params(self, **kwargs): - """Set parameters for the TabulatedNonBonded interaction. - - Parameters - ---------- - - min : :obj:`float`, - The minimal interaction distance. - max : :obj:`float`, - The maximal interaction distance. - energy: array_like of :obj:`float` - The energy table. - force: array_like of :obj:`float` - The force table. - - """ - super().set_params(**kwargs) - - def set_default_params(self): - """Set parameters that are not required to their default value. - - """ - self._params = {} - - def _get_params_from_es_core(self): - cdef IA_parameters * ia_params = get_ia_param_safe( - self._part_types[0], - self._part_types[1]) - - return {'min': ia_params.tab.minval, - 'max': ia_params.tab.maxval, - 'energy': ia_params.tab.energy_tab, - 'force': ia_params.tab.force_tab} - - def _set_params_in_es_core(self): - self.state = tabulated_set_params(self._part_types[0], - self._part_types[1], - self._params["min"], - self._params["max"], - self._params["energy"], - self._params["force"]) - - def is_active(self): - """Check if interaction is active. - - """ - return (self.state == 0) - - -@script_interface_register -class Virtual(BondedInteraction): - - """ - Virtual bond. - """ - - _so_name = "Interactions::VirtualBond" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_VIRTUAL_BOND - - def type_name(self): - """Name of interaction type. - - """ - return "VIRTUAL" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - -@script_interface_register -class AngleHarmonic(BondedInteraction): - - """ - Bond-angle-dependent harmonic potential. - - Parameters - ---------- - phi0 : :obj:`float` - Equilibrium bond angle in radians. - bend : :obj:`float` - Magnitude of the bond interaction. - - """ - - _so_name = "Interactions::AngleHarmonicBond" - - def type_number(self): - return BONDED_IA_ANGLE_HARMONIC - - def type_name(self): - """Name of interaction type. - - """ - return "ANGLE_HARMONIC" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - -@script_interface_register -class AngleCosine(BondedInteraction): - - """ - Bond-angle-dependent cosine potential. - - Parameters - ---------- - phi0 : :obj:`float` - Equilibrium bond angle in radians. - bend : :obj:`float` - Magnitude of the bond interaction. - - """ - - _so_name = "Interactions::AngleCosineBond" - - def type_number(self): - return BONDED_IA_ANGLE_COSINE - - def type_name(self): - """Name of interaction type. - - """ - return "ANGLE_COSINE" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - -@script_interface_register -class AngleCossquare(BondedInteraction): - - """ - Bond-angle-dependent cosine squared potential. - - Parameters - ---------- - phi0 : :obj:`float` - Equilibrium bond angle in radians. - bend : :obj:`float` - Magnitude of the bond interaction. - - """ - - _so_name = "Interactions::AngleCossquareBond" - - def type_number(self): - return BONDED_IA_ANGLE_COSSQUARE - - def type_name(self): - """Name of interaction type. - - """ - return "ANGLE_COSSQUARE" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - -@script_interface_register -class IBM_Triel(BondedInteraction): - - """ - IBM Triel bond. - - See Figure C.1 in :cite:`kruger12a`. - - Parameters - ---------- - ind1, ind2, ind3 : :obj:`int` - First, second and third bonding partner. Used for - initializing reference state - k1 : :obj:`float` - Shear elasticity for Skalak and Neo-Hookean - k2 : :obj:`float`, optional - Area resistance for Skalak - maxDist : :obj:`float` - Gives an error if an edge becomes longer than maxDist - elasticLaw : :obj:`str`, \{'NeoHookean', 'Skalak'\} - Type of elastic bond - - """ - - _so_name = "Interactions::IBMTriel" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_IBM_TRIEL - - def type_name(self): - return "IBM_Triel" - - def valid_keys(self): - return {"ind1", "ind2", "ind3", "k1", "k2", "maxDist", "elasticLaw"} - - def validate_params(self, params): - """Check that parameters are valid. - - """ - if params['elasticLaw'] not in {'NeoHookean', 'Skalak'}: - raise ValueError(f"Unknown elasticLaw: '{params['elasticLaw']}'") - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {"k2": 0} - - def _get_params_from_es_core(self): - return \ - {"maxDist": self.maxDist, - "k1": self.k1, - "k2": self.k2, - "elasticLaw": self.elasticLaw} - - -@script_interface_register -class IBM_Tribend(BondedInteraction): - - """ - IBM Tribend bond. - - See Figure C.2 in :cite:`kruger12a`. - - Parameters - ---------- - ind1, ind2, ind3, ind4 : :obj:`int` - First, second, third and fourth bonding partner. Used for - initializing reference state - kb : :obj:`float` - Bending modulus - refShape : :obj:`str`, optional, \{'Flat', 'Initial'\} - Reference shape, default is ``'Flat'`` - - """ - - _so_name = "Interactions::IBMTribend" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_IBM_TRIBEND - - def type_name(self): - return "IBM_Tribend" - - def valid_keys(self): - return {"ind1", "ind2", "ind3", "ind4", "kb", "refShape"} - - def validate_params(self, params): - """Check that parameters are valid. - - """ - if params['refShape'] not in {'Flat', 'Initial'}: - raise ValueError(f"Unknown refShape: '{params['refShape']}'") - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {"refShape": "Flat"} - - def _get_params_from_es_core(self): - return {"kb": self.kb, "theta0": self.theta0, - "refShape": self.refShape} - - -@script_interface_register -class IBM_VolCons(BondedInteraction): - - """ - IBM volume conservation bond. - - See Figure C.3 in :cite:`kruger12a`. - - Parameters - ---------- - softID : :obj:`int` - Used to identify the object to which this bond belongs. Each object - (cell) needs its own ID. For performance reasons, it is best to - start from ``softID=0`` and increment by 1 for each subsequent bond. - kappaV : :obj:`float` - Modulus for volume force - - """ - - _so_name = "Interactions::IBMVolCons" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_IBM_VOLUME_CONSERVATION - - def type_name(self): - return "IBM_VolCons" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - def current_volume(self): - """ - Query the current volume of the soft object associated to this bond. - The volume is initialized once all :class:`IBM_Triel` bonds have - been added and the forces have been recalculated. - """ - return immersed_boundaries.get_current_volume(self.softID) - - -@script_interface_register -class OifGlobalForces(BondedInteraction): - - """ - Characterize the distribution of the force of the global mesh deformation - onto individual vertices of the mesh. - - Part of the :ref:`Object-in-fluid` method. - - Parameters - ---------- - A0_g : :obj:`float` - Relaxed area of the mesh - ka_g : :obj:`float` - Area coefficient - V0 : :obj:`float` - Relaxed volume of the mesh - kv : :obj:`float` - Volume coefficient - - """ - - _so_name = "Interactions::OifGlobalForcesBond" - - def type_number(self): - return BONDED_IA_OIF_GLOBAL_FORCES - - def type_name(self): - """Name of interaction type. - - """ - return "OIF_GLOBAL_FORCES" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - -@script_interface_register -class OifLocalForces(BondedInteraction): - - """ - Characterize the deformation of two triangles sharing an edge. - - Part of the :ref:`Object-in-fluid` method. - - Parameters - ---------- - r0 : :obj:`float` - Equilibrium bond length of triangle edges - ks : :obj:`float` - Non-linear stretching coefficient of triangle edges - kslin : :obj:`float` - Linear stretching coefficient of triangle edges - phi0 : :obj:`float` - Equilibrium angle between the two triangles - kb : :obj:`float` - Bending coefficient for the angle between the two triangles - A01 : :obj:`float` - Equilibrium surface of the first triangle - A02 : :obj:`float` - Equilibrium surface of the second triangle - kal : :obj:`float` - Stretching coefficient of a triangle surface - kvisc : :obj:`float` - Viscous coefficient of the triangle vertices - - """ - - _so_name = "Interactions::OifLocalForcesBond" - - def type_number(self): - return BONDED_IA_OIF_LOCAL_FORCES - - def type_name(self): - """Name of interaction type. - - """ - return "OIF_LOCAL_FORCES" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - -@script_interface_register -class QuarticBond(BondedInteraction): - - """ - Quartic bond. - - Parameters - ---------- - k0 : :obj:`float` - Magnitude of the square term. - k1 : :obj:`float` - Magnitude of the fourth order term. - r : :obj:`float` - Equilibrium bond length. - r_cut : :obj:`float` - Maximum interaction length. - """ - - _so_name = "Interactions::QuarticBond" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def type_number(self): - return BONDED_IA_QUARTIC - - def type_name(self): - """Name of interaction type. - - """ - return "QUARTIC" - - def get_default_params(self): - """Gets default values of optional parameters. - - """ - return {} - - -bonded_interaction_classes = { - int(BONDED_IA_FENE): FeneBond, - int(BONDED_IA_HARMONIC): HarmonicBond, - int(BONDED_IA_RIGID_BOND): RigidBond, - int(BONDED_IA_DIHEDRAL): Dihedral, - int(BONDED_IA_VIRTUAL_BOND): Virtual, - int(BONDED_IA_ANGLE_HARMONIC): AngleHarmonic, - int(BONDED_IA_ANGLE_COSINE): AngleCosine, - int(BONDED_IA_ANGLE_COSSQUARE): AngleCossquare, - int(BONDED_IA_OIF_GLOBAL_FORCES): OifGlobalForces, - int(BONDED_IA_OIF_LOCAL_FORCES): OifLocalForces, - int(BONDED_IA_IBM_TRIEL): IBM_Triel, - int(BONDED_IA_IBM_TRIBEND): IBM_Tribend, - int(BONDED_IA_IBM_VOLUME_CONSERVATION): IBM_VolCons, - int(BONDED_IA_THERMALIZED_DIST): ThermalizedBond, - int(BONDED_IA_QUARTIC): QuarticBond, -} -IF ELECTROSTATICS: - bonded_interaction_classes[int(BONDED_IA_BONDED_COULOMB)] = BondedCoulomb - bonded_interaction_classes[ - int(BONDED_IA_BONDED_COULOMB_SR)] = BondedCoulombSRBond -IF TABULATED: - bonded_interaction_classes[ - int(BONDED_IA_TABULATED_DISTANCE)] = TabulatedDistance - bonded_interaction_classes[ - int(BONDED_IA_TABULATED_ANGLE)] = TabulatedAngle - bonded_interaction_classes[ - int(BONDED_IA_TABULATED_DIHEDRAL)] = TabulatedDihedral - - -def get_bonded_interaction_type_from_es_core(bond_id): - return < enum_bonded_interaction > bonded_ia_params_zero_based_type(bond_id) - - -@script_interface_register -class BondedInteractions(ScriptObjectMap): - - """ - Represents the bonded interactions list. - - Individual interactions can be accessed using ``BondedInteractions[i]``, - where ``i`` is the bond id. - - Methods - ------- - remove() - Remove a bond from the list. - This is a no-op if the bond does not exist. - - Parameters - ---------- - bond_id : :obj:`int` - - clear() - Remove all bonds. - - """ - - _so_name = "Interactions::BondedInteractions" - _so_creation_policy = "GLOBAL" - - def add(self, *args, **kwargs): - """ - Add a bond to the list. - - Parameters - ---------- - bond: :class:`espressomd.interactions.BondedInteraction` - Either a bond object... - \*\*kwargs : any - ... or parameters to construct a - :class:`~espressomd.interactions.BondedInteraction` object - - """ - - if len(args) == 1 and isinstance(args[0], BondedInteraction): - bonded_ia = args[0] - else: - raise TypeError("A BondedInteraction object needs to be passed.") - bond_id = self._insert_bond(None, bonded_ia) - return bond_id - - def __getitem__(self, bond_id): - self._assert_key_type(bond_id) - - if self.call_method('has_bond', bond_id=bond_id): - bond_obj = self.call_method('get_bond', bond_id=bond_id) - bond_obj._bond_id = bond_id - return bond_obj - - # Find out the type of the interaction from ESPResSo - bond_type = get_bonded_interaction_type_from_es_core(bond_id) - - # Check if the bonded interaction exists in ESPResSo core - if bond_type == BONDED_IA_NONE: - raise ValueError(f"The bond with id {bond_id} is not yet defined.") - - # Find the appropriate class representing such a bond - bond_class = bonded_interaction_classes[bond_type] - - # Create a new script interface object (i.e. a copy of the shared_ptr) - # which links to the bonded interaction object - return bond_class(bond_id) - - def __setitem__(self, bond_id, bond_obj): - self._insert_bond(bond_id, bond_obj) - - def _insert_bond(self, bond_id, bond_obj): - """ - Inserts a new bond. If a ``bond_id`` is given, the bond is inserted at - that id. If no id is given, a new id is generated. - - Bonds can only be overwritten if the new bond is of the same type as the - old one, e.g. a :class:`~espressomd.interactions.FeneBond` bond can only - be overwritten by another :class:`~espressomd.interactions.FeneBond` bond. - """ - - # Validate arguments - if not isinstance(bond_obj, BondedInteraction): - raise ValueError( - "Only subclasses of BondedInteraction can be assigned.") - - # Send the script interface object pointer to the core - if bond_id is None: - bond_id = self.call_method("insert", object=bond_obj) - else: - # Throw error if attempting to overwrite a bond of different type - self._assert_key_type(bond_id) - if self.call_method("contains", key=bond_id): - old_type = bonded_interaction_classes[ - get_bonded_interaction_type_from_es_core(bond_id)] - if not type(bond_obj) is old_type: - raise ValueError( - "Bonds can only be overwritten by bonds of equal type.") - self.call_method("insert", key=bond_id, object=bond_obj) - - # Save the bond id in the BondedInteraction instance - bond_obj._bond_id = bond_id - - return bond_id - - def __len__(self): - return self.call_method('get_size') - - # Support iteration over active bonded interactions - def __iter__(self): - for bond_id in self.call_method('get_bond_ids'): - if get_bonded_interaction_type_from_es_core(bond_id): - yield self[bond_id] - - def __getstate__(self): - params = {} - for bond_id in self.call_method('get_bond_ids'): - if get_bonded_interaction_type_from_es_core(bond_id): - bond_obj = self[bond_id] - if hasattr(bond_obj, 'params'): - params[bond_id] = ( - bond_obj._ctor_params, bond_obj.type_number()) - return params - - def __setstate__(self, params): - for bond_id, (bond_params, bond_type) in params.items(): - self[bond_id] = bonded_interaction_classes[bond_type]( - **bond_params) diff --git a/src/python/espressomd/script_interface.pyx b/src/python/espressomd/script_interface.pyx index 67a9151e658..176beea6185 100644 --- a/src/python/espressomd/script_interface.pyx +++ b/src/python/espressomd/script_interface.pyx @@ -136,7 +136,7 @@ cdef class PScriptInterface: self.sip = sip - def call_method(self, method, **kwargs): + def call_method(self, method, handle_errors_message=None, **kwargs): """ Call a method of the core class. @@ -144,6 +144,8 @@ cdef class PScriptInterface: ---------- method : Creation policy. Name of the core method. + handle_errors_message : :obj:`str`, optional + Custom error message for runtime errors raised in a MPI context. \*\*kwargs Arguments for the method. """ @@ -156,7 +158,9 @@ cdef class PScriptInterface: value = self.sip.get().call_method(utils.to_char_pointer(method), parameters) res = variant_to_python_object(value) - utils.handle_errors(f'while calling method {method}()') + if handle_errors_message is None: + handle_errors_message = f"while calling method {method}()" + utils.handle_errors(handle_errors_message) return res def name(self): diff --git a/src/script_interface/Context.hpp b/src/script_interface/Context.hpp index 6a979517c1c..a4640439e71 100644 --- a/src/script_interface/Context.hpp +++ b/src/script_interface/Context.hpp @@ -87,6 +87,12 @@ class Context : public std::enable_shared_from_this { virtual std::shared_ptr make_shared(std::string const &name, const VariantMap ¶meters) = 0; + /** + * @copydoc Context::make_shared + */ + virtual std::shared_ptr + make_shared_local(std::string const &name, VariantMap const ¶meters) = 0; + protected: /** * @brief Set the context of an object to this. diff --git a/src/script_interface/GlobalContext.cpp b/src/script_interface/GlobalContext.cpp index ed22422e782..c4859b71be9 100644 --- a/src/script_interface/GlobalContext.cpp +++ b/src/script_interface/GlobalContext.cpp @@ -88,10 +88,22 @@ void GlobalContext::notify_call_method(const ObjectHandle *o, cb_call_method(object_id(o), name, pack(arguments)); } +std::shared_ptr +GlobalContext::make_shared_local(std::string const &name, + VariantMap const ¶meters) { + auto sp = m_node_local_context->factory().make(name); + set_context(sp.get()); + + sp->construct(parameters); + + return sp; +} + std::shared_ptr GlobalContext::make_shared(std::string const &name, const VariantMap ¶meters) { - std::unique_ptr sp = m_node_local_context->factory().make(name); + assert(is_head_node()); + auto sp = m_node_local_context->factory().make(name); set_context(sp.get()); auto const id = object_id(sp.get()); diff --git a/src/script_interface/GlobalContext.hpp b/src/script_interface/GlobalContext.hpp index 03f2d0dc329..090c811ef5e 100644 --- a/src/script_interface/GlobalContext.hpp +++ b/src/script_interface/GlobalContext.hpp @@ -165,6 +165,9 @@ class GlobalContext : public Context { */ std::shared_ptr make_shared(std::string const &name, const VariantMap ¶meters) override; + std::shared_ptr + make_shared_local(std::string const &name, + VariantMap const ¶meters) override; boost::string_ref name(const ObjectHandle *o) const override; diff --git a/src/script_interface/LocalContext.hpp b/src/script_interface/LocalContext.hpp index 11a79a7a2fb..9f528812533 100644 --- a/src/script_interface/LocalContext.hpp +++ b/src/script_interface/LocalContext.hpp @@ -71,6 +71,12 @@ class LocalContext : public Context { return sp; } + std::shared_ptr + make_shared_local(std::string const &name, + VariantMap const ¶meters) override { + return make_shared(name, parameters); + } + boost::string_ref name(const ObjectHandle *o) const override { assert(o); diff --git a/src/script_interface/analysis/ObservableStat.cpp b/src/script_interface/analysis/ObservableStat.cpp index c5a0d61ae64..130185f6376 100644 --- a/src/script_interface/analysis/ObservableStat.cpp +++ b/src/script_interface/analysis/ObservableStat.cpp @@ -21,8 +21,7 @@ #include "ObservableStat.hpp" -#include "script_interface/interactions/bonded.hpp" - +#include "core/bonded_interactions/bonded_interaction_data.hpp" #include "core/nonbonded_interactions/nonbonded_interaction_data.hpp" #include "core/energy.hpp" @@ -86,7 +85,7 @@ static auto get_summary(Observable_stat const &obs, bool const calc_sp) { auto const n_bonds = ::bonded_ia_params.get_next_key(); for (std::size_t bond_id = 0; bond_id < n_bonds; ++bond_id) { - if (bonded_ia_params_zero_based_type(bond_id) != 0) { + if (::bonded_ia_params.get_zero_based_type(bond_id) != 0) { dict["bonded," + std::to_string(bond_id)] = get_obs_contrib(obs.bonded_contribution(bond_id)); } diff --git a/src/script_interface/interactions/BondedInteraction.hpp b/src/script_interface/interactions/BondedInteraction.hpp index 787a8b57a17..46c8b752d6c 100644 --- a/src/script_interface/interactions/BondedInteraction.hpp +++ b/src/script_interface/interactions/BondedInteraction.hpp @@ -27,7 +27,9 @@ #define SCRIPT_INTERFACE_INTERACTIONS_BONDED_INTERACTION_HPP #include "core/bonded_interactions/bonded_interaction_data.hpp" +#include "core/immersed_boundaries.hpp" #include "core/thermostat.hpp" + #include "script_interface/ScriptInterface.hpp" #include "script_interface/auto_parameters/AutoParameters.hpp" #include "script_interface/get_value.hpp" @@ -35,9 +37,14 @@ #include #include +#include +#include #include #include +#include +#include #include +#include #include #include #include @@ -45,7 +52,7 @@ namespace ScriptInterface { namespace Interactions { -template class BondedInteractionInterface { +class BondedInteraction : public AutoParameters { protected: std::shared_ptr<::Bonded_IA_Parameters> m_bonded_ia; @@ -54,45 +61,93 @@ template class BondedInteractionInterface { std::shared_ptr bonded_ia() const { return m_bonded_ia; } -}; -class BondedInteraction : public AutoParameters, - public BondedInteractionInterface { +protected: + using AutoParameters::context; + using AutoParameters::valid_parameters; + + virtual std::set get_valid_parameters() const { + auto const vec = valid_parameters(); + auto valid_keys = std::set(); + std::transform(vec.begin(), vec.end(), + std::inserter(valid_keys, valid_keys.begin()), + [](auto const &key) { return std::string{key}; }); + return valid_keys; + } + +private: + void check_valid_parameters(VariantMap const ¶ms) const { + auto const valid_keys = get_valid_parameters(); + for (auto const &key : valid_keys) { + if (params.count(std::string(key)) == 0) { + throw std::runtime_error("Parameter '" + key + "' is missing"); + } + } + for (auto const &kv : params) { + if (valid_keys.count(kv.first) == 0) { + throw std::runtime_error("Parameter '" + kv.first + + "' is not recognized"); + } + } + } + void do_construct(VariantMap const ¶ms) override { // Check if initialization "by id" or "by parameters" if (params.find("bond_id") != params.end()) { - m_bonded_ia = ::bonded_ia_params.at(get_value(params, "bond_id")); + auto const bond_id = get_value(params, "bond_id"); + context()->parallel_try_catch([&]() { + if (not::bonded_ia_params.contains(bond_id)) { + throw std::runtime_error("No bond with id " + + std::to_string(bond_id) + + " exists in the ESPResSo core"); + } + }); + m_bonded_ia = ::bonded_ia_params.at(bond_id); } else { - construct_bond(params); + context()->parallel_try_catch([&]() { + check_valid_parameters(params); + construct_bond(params); + }); } } -private: - virtual void construct_bond(VariantMap const ¶ms) {} + virtual void construct_bond(VariantMap const ¶ms) = 0; public: - template - bool operator==(const BondedInteractionInterface &other) { + bool operator==(BondedInteraction const &other) { return m_bonded_ia == other.m_bonded_ia; } Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { // this feature is needed to compare bonds - if (name == "get_address") { - return reinterpret_cast(bonded_ia().get()); + if (name == "is_same_bond") { + auto const bond_so = + get_value>(params, "bond"); + return *this == *bond_so; } if (name == "get_num_partners") { return number_of_partners(*bonded_ia()); } + if (name == "get_zero_based_type") { + auto const bond_id = get_value(params, "bond_id"); + return ::bonded_ia_params.get_zero_based_type(bond_id); + } return {}; } }; -class FeneBond : public BondedInteraction { - using CoreBondedInteraction = ::FeneBond; +template class BondedInteractionImpl : public BondedInteraction { +public: + using CoreBondedInteraction = CoreIA; + CoreBondedInteraction &get_struct() { + return boost::get(*bonded_ia()); + } +}; + +class FeneBond : public BondedInteractionImpl<::FeneBond> { public: FeneBond() { add_parameters({ @@ -110,15 +165,9 @@ class FeneBond : public BondedInteraction { get_value(params, "d_r_max"), get_value(params, "r_0"))); } - - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } }; -class HarmonicBond : public BondedInteraction { - using CoreBondedInteraction = ::HarmonicBond; - +class HarmonicBond : public BondedInteractionImpl<::HarmonicBond> { public: HarmonicBond() { add_parameters({ @@ -136,15 +185,9 @@ class HarmonicBond : public BondedInteraction { get_value(params, "k"), get_value(params, "r_0"), get_value(params, "r_cut"))); } - - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } }; -class QuarticBond : public BondedInteraction { - using CoreBondedInteraction = ::QuarticBond; - +class QuarticBond : public BondedInteractionImpl<::QuarticBond> { public: QuarticBond() { add_parameters({ @@ -164,15 +207,9 @@ class QuarticBond : public BondedInteraction { get_value(params, "r"), get_value(params, "r_cut"))); } - - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } }; -class BondedCoulomb : public BondedInteraction { - using CoreBondedInteraction = ::BondedCoulomb; - +class BondedCoulomb : public BondedInteractionImpl<::BondedCoulomb> { public: BondedCoulomb() { add_parameters({ @@ -181,10 +218,6 @@ class BondedCoulomb : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>( @@ -192,9 +225,7 @@ class BondedCoulomb : public BondedInteraction { } }; -class BondedCoulombSR : public BondedInteraction { - using CoreBondedInteraction = ::BondedCoulombSR; - +class BondedCoulombSR : public BondedInteractionImpl<::BondedCoulombSR> { public: BondedCoulombSR() { add_parameters({ @@ -203,10 +234,6 @@ class BondedCoulombSR : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>( @@ -214,9 +241,7 @@ class BondedCoulombSR : public BondedInteraction { } }; -class AngleHarmonicBond : public BondedInteraction { - using CoreBondedInteraction = ::AngleHarmonicBond; - +class AngleHarmonicBond : public BondedInteractionImpl<::AngleHarmonicBond> { public: AngleHarmonicBond() { add_parameters({ @@ -227,10 +252,6 @@ class AngleHarmonicBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>( @@ -239,9 +260,7 @@ class AngleHarmonicBond : public BondedInteraction { } }; -class AngleCosineBond : public BondedInteraction { - using CoreBondedInteraction = ::AngleCosineBond; - +class AngleCosineBond : public BondedInteractionImpl<::AngleCosineBond> { public: AngleCosineBond() { add_parameters({ @@ -252,10 +271,6 @@ class AngleCosineBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>( @@ -264,9 +279,7 @@ class AngleCosineBond : public BondedInteraction { } }; -class AngleCossquareBond : public BondedInteraction { - using CoreBondedInteraction = ::AngleCossquareBond; - +class AngleCossquareBond : public BondedInteractionImpl<::AngleCossquareBond> { public: AngleCossquareBond() { add_parameters({ @@ -277,10 +290,6 @@ class AngleCossquareBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>( @@ -289,9 +298,7 @@ class AngleCossquareBond : public BondedInteraction { } }; -class DihedralBond : public BondedInteraction { - using CoreBondedInteraction = ::DihedralBond; - +class DihedralBond : public BondedInteractionImpl<::DihedralBond> { public: DihedralBond() { add_parameters({ @@ -304,10 +311,6 @@ class DihedralBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = @@ -317,9 +320,8 @@ class DihedralBond : public BondedInteraction { } }; -class TabulatedDistanceBond : public BondedInteraction { - using CoreBondedInteraction = ::TabulatedDistanceBond; - +class TabulatedDistanceBond + : public BondedInteractionImpl<::TabulatedDistanceBond> { public: TabulatedDistanceBond() { add_parameters({ @@ -334,10 +336,6 @@ class TabulatedDistanceBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = @@ -348,9 +346,7 @@ class TabulatedDistanceBond : public BondedInteraction { } }; -class TabulatedAngleBond : public BondedInteraction { - using CoreBondedInteraction = ::TabulatedAngleBond; - +class TabulatedAngleBond : public BondedInteractionImpl<::TabulatedAngleBond> { public: TabulatedAngleBond() { add_parameters({ @@ -365,10 +361,6 @@ class TabulatedAngleBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = @@ -379,9 +371,8 @@ class TabulatedAngleBond : public BondedInteraction { } }; -class TabulatedDihedralBond : public BondedInteraction { - using CoreBondedInteraction = ::TabulatedDihedralBond; - +class TabulatedDihedralBond + : public BondedInteractionImpl<::TabulatedDihedralBond> { public: TabulatedDihedralBond() { add_parameters({ @@ -396,10 +387,6 @@ class TabulatedDihedralBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = @@ -410,9 +397,7 @@ class TabulatedDihedralBond : public BondedInteraction { } }; -class ThermalizedBond : public BondedInteraction { - using CoreBondedInteraction = ::ThermalizedBond; - +class ThermalizedBond : public BondedInteractionImpl<::ThermalizedBond> { public: ThermalizedBond() { add_parameters({ @@ -431,8 +416,15 @@ class ThermalizedBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); + 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: @@ -443,14 +435,37 @@ class ThermalizedBond : public BondedInteraction { get_value(params, "temp_distance"), get_value(params, "gamma_distance"), get_value(params, "r_cut"))); - thermalized_bond.rng_initialize( - static_cast(get_value(params, "seed"))); + + 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)); + } } -}; -class RigidBond : public BondedInteraction { - using CoreBondedInteraction = ::RigidBond; + std::set get_valid_parameters() const override { + auto names = + BondedInteractionImpl::get_valid_parameters(); + names.insert("rng_state"); + return names; + } +}; +class RigidBond : public BondedInteractionImpl<::RigidBond> { public: RigidBond() { add_parameters({ @@ -463,10 +478,6 @@ class RigidBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = @@ -476,17 +487,7 @@ class RigidBond : public BondedInteraction { } }; -class IBMTriel : public BondedInteraction { - using CoreBondedInteraction = ::IBMTriel; - -private: - tElasticLaw str2elastic_law(std::string el) { - if (boost::iequals(el, "NeoHookean")) { - return tElasticLaw::NeoHookean; - } - return tElasticLaw::Skalak; - } - +class IBMTriel : public BondedInteractionImpl<::IBMTriel> { public: IBMTriel() { add_parameters({ @@ -504,25 +505,37 @@ class IBMTriel : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { + auto const law_name = get_value(params, "elasticLaw"); + tElasticLaw elastic_law; + if (law_name == "NeoHookean") { + elastic_law = tElasticLaw::NeoHookean; + } else if (law_name == "Skalak") { + elastic_law = tElasticLaw::Skalak; + } else { + throw std::invalid_argument( + "Invalid value for parameter 'elasticLaw': '" + law_name + "'"); + } m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>(CoreBondedInteraction( get_value(params, "ind1"), get_value(params, "ind2"), get_value(params, "ind3"), - get_value(params, "maxDist"), - str2elastic_law(get_value(params, "elasticLaw")), + get_value(params, "maxDist"), elastic_law, get_value(params, "k1"), get_value(params, "k2"))); } -}; -class IBMVolCons : public BondedInteraction { - using CoreBondedInteraction = ::IBMVolCons; + std::set get_valid_parameters() const override { + auto names = + BondedInteractionImpl::get_valid_parameters(); + names.insert("ind1"); + names.insert("ind2"); + names.insert("ind3"); + return names; + } +}; +class IBMVolCons : public BondedInteractionImpl<::IBMVolCons> { public: IBMVolCons() { add_parameters({ @@ -533,8 +546,12 @@ class IBMVolCons : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "current_volume") { + return ::immersed_boundaries.get_current_volume(get_struct().softID); + } + return BondedInteraction::do_call_method(name, params); } private: @@ -545,9 +562,7 @@ class IBMVolCons : public BondedInteraction { } }; -class IBMTribend : public BondedInteraction { - using CoreBondedInteraction = ::IBMTribend; - +class IBMTribend : public BondedInteractionImpl<::IBMTribend> { public: IBMTribend() { add_parameters({ @@ -561,26 +576,39 @@ class IBMTribend : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: bool m_flat; void construct_bond(VariantMap const ¶ms) override { - auto const &refShape = get_value(params, "refShape"); - m_flat = boost::iequals(refShape, "Flat"); + auto const shape_name = get_value(params, "refShape"); + if (shape_name == "Flat") { + m_flat = true; + } else if (shape_name == "Initial") { + m_flat = false; + } else { + throw std::invalid_argument("Invalid value for parameter 'refShape': '" + + shape_name + "'"); + } m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>(CoreBondedInteraction( get_value(params, "ind1"), get_value(params, "ind2"), get_value(params, "ind3"), get_value(params, "ind4"), get_value(params, "kb"), m_flat)); } -}; -class OifGlobalForcesBond : public BondedInteraction { - using CoreBondedInteraction = ::OifGlobalForcesBond; + std::set get_valid_parameters() const override { + auto names = + BondedInteractionImpl::get_valid_parameters(); + names.erase("theta0"); + names.insert("ind1"); + names.insert("ind2"); + names.insert("ind3"); + names.insert("ind4"); + return names; + } +}; +class OifGlobalForcesBond + : public BondedInteractionImpl<::OifGlobalForcesBond> { public: OifGlobalForcesBond() { add_parameters({ @@ -593,10 +621,6 @@ class OifGlobalForcesBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = @@ -607,9 +631,7 @@ class OifGlobalForcesBond : public BondedInteraction { } }; -class OifLocalForcesBond : public BondedInteraction { - using CoreBondedInteraction = ::OifLocalForcesBond; - +class OifLocalForcesBond : public BondedInteractionImpl<::OifLocalForcesBond> { public: OifLocalForcesBond() { add_parameters({ @@ -631,10 +653,6 @@ class OifLocalForcesBond : public BondedInteraction { }); } - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } - private: void construct_bond(VariantMap const ¶ms) override { m_bonded_ia = @@ -648,19 +666,15 @@ class OifLocalForcesBond : public BondedInteraction { } }; -class VirtualBond : public BondedInteraction { - using CoreBondedInteraction = ::VirtualBond; - +class VirtualBond : public BondedInteractionImpl<::VirtualBond> { public: VirtualBond() { m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>(CoreBondedInteraction()); } -public: - CoreBondedInteraction &get_struct() { - return boost::get(*bonded_ia()); - } +private: + void construct_bond(VariantMap const &) override {} }; } // namespace Interactions diff --git a/src/script_interface/interactions/BondedInteractions.hpp b/src/script_interface/interactions/BondedInteractions.hpp index 047c13aaa7d..fe92ef1e196 100644 --- a/src/script_interface/interactions/BondedInteractions.hpp +++ b/src/script_interface/interactions/BondedInteractions.hpp @@ -96,6 +96,11 @@ class BondedInteractions : public ObjectMap { return {m_bonds.at(bond_id)}; } + if (name == "get_zero_based_type") { + auto const bond_id = get_value(params, "bond_id"); + return ::bonded_ia_params.get_zero_based_type(bond_id); + } + return ObjectMap::do_call_method(name, params); } diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp new file mode 100644 index 00000000000..6ed08ce4906 --- /dev/null +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -0,0 +1,976 @@ +/* + * Copyright (C) 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 . + */ + +/** @file + * The ScriptInterface counterparts of the non-bonded interactions parameters + * structs from the core are defined here. + */ + +#ifndef SCRIPT_INTERFACE_INTERACTIONS_NONBONDED_INTERACTION_HPP +#define SCRIPT_INTERFACE_INTERACTIONS_NONBONDED_INTERACTION_HPP + +#include "script_interface/ScriptInterface.hpp" +#include "script_interface/auto_parameters/AutoParameters.hpp" +#include "script_interface/get_value.hpp" + +#include "core/event.hpp" +#include "core/nonbonded_interactions/nonbonded_interaction_data.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ScriptInterface { +namespace Interactions { + +template +class InteractionPotentialInterface + : public AutoParameters> { + /** @brief Particle type pair. */ + std::array m_types = {-1, -1}; + +public: + using CoreInteraction = CoreIA; + +protected: + using AutoParameters>::context; + using AutoParameters>::valid_parameters; + /** @brief Managed object. */ + std::shared_ptr m_ia_si; + /** @brief Pointer to the corresponding member in a handle. */ + virtual CoreInteraction IA_parameters::*get_ptr_offset() const = 0; + /** @brief Create a new instance using the constructor with range checks. */ + virtual void make_new_instance(VariantMap const ¶ms) = 0; + /** @brief Which parameter indicates whether the potential is inactive. */ + virtual std::string inactive_parameter() const { return "cutoff"; } + /** @brief Which magic value indicates the potential is inactive. */ + virtual double inactive_cutoff() const { return INACTIVE_CUTOFF; } + + template + auto make_autoparameter(T CoreInteraction::*ptr, char const *name) { + return AutoParameter{name, AutoParameter::read_only, + [this, ptr]() { return m_ia_si.get()->*ptr; }}; + } + +private: + std::set get_valid_parameters() const { + auto const vec = valid_parameters(); + auto valid_keys = std::set(); + std::transform(vec.begin(), vec.end(), + std::inserter(valid_keys, valid_keys.begin()), + [](auto const &key) { return std::string{key}; }); + return valid_keys; + } + + void check_valid_parameters(VariantMap const ¶ms) const { + auto const valid_keys = get_valid_parameters(); + for (auto const &key : valid_keys) { + if (params.count(std::string(key)) == 0) { + throw std::runtime_error("Parameter '" + key + "' is missing"); + } + } + for (auto const &kv : params) { + if (valid_keys.count(kv.first) == 0) { + throw std::runtime_error("Parameter '" + kv.first + + "' is not recognized"); + } + } + } + +public: + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "set_params") { + context()->parallel_try_catch([this, ¶ms]() { + check_valid_parameters(params); + make_new_instance(params); + }); + if (m_types[0] != -1) { + copy_si_to_core(); + on_non_bonded_ia_change(); + } + return {}; + } + if (name == "deactivate") { + m_ia_si = std::make_shared(); + if (m_types[0] != -1) { + copy_si_to_core(); + on_non_bonded_ia_change(); + } + return {}; + } + if (name == "is_registered") { + return m_types[0] != -1; + } + if (name == "bind_types") { + auto types = get_value>(params, "_types"); + if (types[0] > types[1]) { + std::swap(types[0], types[1]); + } + if (m_types[0] == -1 or + (m_types[0] == types[0] and m_types[1] == types[1])) { + m_types[0] = types[0]; + m_types[1] = types[1]; + } else { + context()->parallel_try_catch([this]() { + throw std::runtime_error( + "Non-bonded interaction is already bound to interaction pair [" + + std::to_string(m_types[0]) + ", " + std::to_string(m_types[1]) + + "]"); + }); + } + return {}; + } + return {}; + } + + void do_construct(VariantMap const ¶ms) final { + if (params.count("_types") != 0) { + do_call_method("bind_types", params); + m_ia_si = std::make_shared(); + copy_core_to_si(); + } else { + if (std::abs(get_value(params, inactive_parameter()) - + inactive_cutoff()) < 1e-9) { + m_ia_si = std::make_shared(); + } else { + context()->parallel_try_catch([this, ¶ms]() { + check_valid_parameters(params); + make_new_instance(params); + }); + } + } + } + + void copy_si_to_core() { + assert(m_ia_si != nullptr); + auto const key = get_ia_param_key(m_types[0], m_types[1]); + assert(key < ::nonbonded_ia_params.size()); + ::nonbonded_ia_params[key].get()->*get_ptr_offset() = *m_ia_si; + } + + void copy_core_to_si() { + assert(m_ia_si != nullptr); + auto const key = get_ia_param_key(m_types[0], m_types[1]); + assert(key < ::nonbonded_ia_params.size()); + *m_ia_si = ::nonbonded_ia_params[key].get()->*get_ptr_offset(); + } +}; + +#ifdef WCA +class InteractionWCA : public InteractionPotentialInterface<::WCA_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::wca; + } + +public: + InteractionWCA() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "epsilon"), + make_autoparameter(&CoreInteraction::sig, "sigma"), + }); + } + +private: + std::string inactive_parameter() const override { return "sigma"; } + double inactive_cutoff() const override { return 0.; } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "epsilon", "sigma"); + } + +public: + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "get_cutoff") { + return m_ia_si.get()->cut; + } + return InteractionPotentialInterface::do_call_method( + name, params); + } +}; +#endif // WCA + +#ifdef LENNARD_JONES +class InteractionLJ : public InteractionPotentialInterface<::LJ_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::lj; + } + +public: + InteractionLJ() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "epsilon"), + make_autoparameter(&CoreInteraction::sig, "sigma"), + make_autoparameter(&CoreInteraction::cut, "cutoff"), + make_autoparameter(&CoreInteraction::shift, "shift"), + make_autoparameter(&CoreInteraction::offset, "offset"), + make_autoparameter(&CoreInteraction::min, "min"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + auto new_params = params; + auto const *shift_string = boost::get(¶ms.at("shift")); + if (shift_string != nullptr) { + if (*shift_string != "auto") { + throw std::invalid_argument( + "LJ parameter 'shift' has to be 'auto' or a float"); + } + new_params["shift"] = 0.; + } + m_ia_si = make_shared_from_args( + new_params, "epsilon", "sigma", "cutoff", "offset", "min", "shift"); + if (shift_string != nullptr) { + m_ia_si->shift = m_ia_si->get_auto_shift(); + } + } +}; +#endif // LENNARD_JONES + +#ifdef LENNARD_JONES_GENERIC +class InteractionLJGen + : public InteractionPotentialInterface<::LJGen_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::ljgen; + } + +public: + InteractionLJGen() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "epsilon"), + make_autoparameter(&CoreInteraction::sig, "sigma"), + make_autoparameter(&CoreInteraction::cut, "cutoff"), + make_autoparameter(&CoreInteraction::shift, "shift"), + make_autoparameter(&CoreInteraction::offset, "offset"), +#ifdef LJGEN_SOFTCORE + make_autoparameter(&CoreInteraction::lambda, "lam"), + make_autoparameter(&CoreInteraction::softrad, "delta"), +#endif + make_autoparameter(&CoreInteraction::a1, "e1"), + make_autoparameter(&CoreInteraction::a2, "e2"), + make_autoparameter(&CoreInteraction::b1, "b1"), + make_autoparameter(&CoreInteraction::b2, "b2"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + auto new_params = params; + auto const *shift_string = boost::get(¶ms.at("shift")); + if (shift_string != nullptr) { + if (*shift_string != "auto") { + throw std::invalid_argument( + "Generic LJ parameter 'shift' has to be 'auto' or a float"); + } + new_params["shift"] = 0.; + } + m_ia_si = make_shared_from_args( + new_params, "epsilon", "sigma", "cutoff", "shift", "offset", +#ifdef LJGEN_SOFTCORE + "lam", "delta", +#endif + "e1", "e2", "b1", "b2"); + if (shift_string != nullptr) { + m_ia_si->shift = m_ia_si->get_auto_shift(); + } + } +}; +#endif // LENNARD_JONES_GENERIC + +#ifdef LJCOS +class InteractionLJcos + : public InteractionPotentialInterface<::LJcos_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::ljcos; + } + +public: + InteractionLJcos() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "epsilon"), + make_autoparameter(&CoreInteraction::sig, "sigma"), + make_autoparameter(&CoreInteraction::cut, "cutoff"), + make_autoparameter(&CoreInteraction::offset, "offset"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = + make_shared_from_args( + params, "epsilon", "sigma", "cutoff", "offset"); + } +}; +#endif // LJCOS + +#ifdef LJCOS2 +class InteractionLJcos2 + : public InteractionPotentialInterface<::LJcos2_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::ljcos2; + } + +public: + InteractionLJcos2() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "epsilon"), + make_autoparameter(&CoreInteraction::sig, "sigma"), + make_autoparameter(&CoreInteraction::offset, "offset"), + make_autoparameter(&CoreInteraction::w, "width"), + }); + } + +private: + std::string inactive_parameter() const override { return "sigma"; } + double inactive_cutoff() const override { return 0.; } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = + make_shared_from_args( + params, "epsilon", "sigma", "offset", "width"); + } + +public: + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "get_cutoff") { + return m_ia_si.get()->cut; + } + return InteractionPotentialInterface::do_call_method( + name, params); + } +}; +#endif // LJCOS2 + +#ifdef HERTZIAN +class InteractionHertzian + : public InteractionPotentialInterface<::Hertzian_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::hertzian; + } + +public: + InteractionHertzian() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "eps"), + make_autoparameter(&CoreInteraction::sig, "sig"), + }); + } + +private: + std::string inactive_parameter() const override { return "sig"; } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "eps", "sig"); + } +}; +#endif // HERTZIAN + +#ifdef GAUSSIAN +class InteractionGaussian + : public InteractionPotentialInterface<::Gaussian_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::gaussian; + } + +public: + InteractionGaussian() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "eps"), + make_autoparameter(&CoreInteraction::sig, "sig"), + make_autoparameter(&CoreInteraction::cut, "cutoff"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "eps", "sig", "cutoff"); + } +}; +#endif // GAUSSIAN + +#ifdef BMHTF_NACL +class InteractionBMHTF + : public InteractionPotentialInterface<::BMHTF_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::bmhtf; + } + +public: + InteractionBMHTF() { + add_parameters({ + make_autoparameter(&CoreInteraction::A, "a"), + make_autoparameter(&CoreInteraction::B, "b"), + make_autoparameter(&CoreInteraction::C, "c"), + make_autoparameter(&CoreInteraction::D, "d"), + make_autoparameter(&CoreInteraction::sig, "sig"), + make_autoparameter(&CoreInteraction::cut, "cutoff"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "a", "b", "c", "d", "sig", "cutoff"); + } +}; +#endif // BMHTF_NACL + +#ifdef MORSE +class InteractionMorse + : public InteractionPotentialInterface<::Morse_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::morse; + } + +public: + InteractionMorse() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "eps"), + make_autoparameter(&CoreInteraction::alpha, "alpha"), + make_autoparameter(&CoreInteraction::rmin, "rmin"), + make_autoparameter(&CoreInteraction::cut, "cutoff"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = + make_shared_from_args( + params, "eps", "alpha", "rmin", "cutoff"); + } +}; +#endif // MORSE + +#ifdef BUCKINGHAM +class InteractionBuckingham + : public InteractionPotentialInterface<::Buckingham_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::buckingham; + } + +public: + InteractionBuckingham() { + add_parameters({ + make_autoparameter(&CoreInteraction::A, "a"), + make_autoparameter(&CoreInteraction::B, "b"), + make_autoparameter(&CoreInteraction::C, "c"), + make_autoparameter(&CoreInteraction::D, "d"), + make_autoparameter(&CoreInteraction::cut, "cutoff"), + make_autoparameter(&CoreInteraction::discont, "discont"), + make_autoparameter(&CoreInteraction::shift, "shift"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "a", "b", "c", "d", "cutoff", "discont", "shift"); + } +}; +#endif // BUCKINGHAM + +#ifdef SOFT_SPHERE +class InteractionSoftSphere + : public InteractionPotentialInterface<::SoftSphere_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::soft_sphere; + } + +public: + InteractionSoftSphere() { + add_parameters({ + make_autoparameter(&CoreInteraction::a, "a"), + make_autoparameter(&CoreInteraction::n, "n"), + make_autoparameter(&CoreInteraction::cut, "cutoff"), + make_autoparameter(&CoreInteraction::offset, "offset"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = + make_shared_from_args( + params, "a", "n", "cutoff", "offset"); + } +}; +#endif // SOFT_SPHERE + +#ifdef HAT +class InteractionHat : public InteractionPotentialInterface<::Hat_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::hat; + } + +public: + InteractionHat() { + add_parameters({ + make_autoparameter(&CoreInteraction::Fmax, "F_max"), + make_autoparameter(&CoreInteraction::r, "cutoff"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "F_max", "cutoff"); + } +}; +#endif // HAT + +#ifdef GAY_BERNE +class InteractionGayBerne + : public InteractionPotentialInterface<::GayBerne_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::gay_berne; + } + +public: + InteractionGayBerne() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "eps"), + make_autoparameter(&CoreInteraction::sig, "sig"), + make_autoparameter(&CoreInteraction::cut, "cut"), + make_autoparameter(&CoreInteraction::k1, "k1"), + make_autoparameter(&CoreInteraction::k2, "k2"), + make_autoparameter(&CoreInteraction::mu, "mu"), + make_autoparameter(&CoreInteraction::nu, "nu"), + }); + } + +private: + std::string inactive_parameter() const override { return "cut"; } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "eps", "sig", "cut", "k1", "k2", "mu", "nu"); + } +}; +#endif // GAY_BERNE + +#ifdef TABULATED +class InteractionTabulated + : public InteractionPotentialInterface<::TabulatedPotential> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::tab; + } + +public: + InteractionTabulated() { + add_parameters({ + make_autoparameter(&CoreInteraction::minval, "min"), + make_autoparameter(&CoreInteraction::maxval, "max"), + make_autoparameter(&CoreInteraction::force_tab, "force"), + make_autoparameter(&CoreInteraction::energy_tab, "energy"), + }); + } + +private: + std::string inactive_parameter() const override { return "max"; } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args, std::vector>( + params, "min", "max", "force", "energy"); + } + +public: + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "get_cutoff") { + return m_ia_si.get()->cutoff(); + } + return InteractionPotentialInterface::do_call_method( + name, params); + } +}; +#endif // TABULATED + +#ifdef DPD +class InteractionDPD : public InteractionPotentialInterface<::DPD_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::dpd; + } + +public: + InteractionDPD() { + add_parameters({ + {"weight_function", AutoParameter::read_only, + [this]() { return m_ia_si.get()->radial.wf; }}, + {"gamma", AutoParameter::read_only, + [this]() { return m_ia_si.get()->radial.gamma; }}, + {"k", AutoParameter::read_only, + [this]() { return m_ia_si.get()->radial.k; }}, + {"r_cut", AutoParameter::read_only, + [this]() { return m_ia_si.get()->radial.cutoff; }}, + {"trans_weight_function", AutoParameter::read_only, + [this]() { return m_ia_si.get()->trans.wf; }}, + {"trans_gamma", AutoParameter::read_only, + [this]() { return m_ia_si.get()->trans.gamma; }}, + {"trans_r_cut", AutoParameter::read_only, + [this]() { return m_ia_si.get()->trans.cutoff; }}, + }); + std::ignore = get_ptr_offset(); // for code coverage + } + +private: + std::string inactive_parameter() const override { return "r_cut"; } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "gamma", "k", "r_cut", "weight_function", "trans_gamma", + "trans_r_cut", "trans_weight_function"); + if (m_ia_si->radial.wf != 0 and m_ia_si->radial.wf != 1) { + throw std::domain_error( + "DPDInteraction parameter 'weight_function' must be 0 or 1"); + } + if (m_ia_si->trans.wf != 0 and m_ia_si->trans.wf != 1) { + throw std::domain_error( + "DPDInteraction parameter 'trans_weight_function' must be 0 or 1"); + } + } +}; +#endif // DPD + +#ifdef THOLE +class InteractionThole + : public InteractionPotentialInterface<::Thole_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::thole; + } + +public: + InteractionThole() { + add_parameters({ + make_autoparameter(&CoreInteraction::scaling_coeff, "scaling_coeff"), + make_autoparameter(&CoreInteraction::q1q2, "q1q2"), + }); + } + +private: + std::string inactive_parameter() const override { return "scaling_coeff"; } + double inactive_cutoff() const override { return 0.; } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "scaling_coeff", "q1q2"); + } +}; +#endif // THOLE + +#ifdef SMOOTH_STEP +class InteractionSmoothStep + : public InteractionPotentialInterface<::SmoothStep_Parameters> { +protected: + CoreInteraction IA_parameters::*get_ptr_offset() const override { + return &::IA_parameters::smooth_step; + } + +public: + InteractionSmoothStep() { + add_parameters({ + make_autoparameter(&CoreInteraction::eps, "eps"), + make_autoparameter(&CoreInteraction::sig, "sig"), + make_autoparameter(&CoreInteraction::cut, "cutoff"), + make_autoparameter(&CoreInteraction::d, "d"), + make_autoparameter(&CoreInteraction::n, "n"), + make_autoparameter(&CoreInteraction::k0, "k0"), + }); + } + +private: + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "eps", "sig", "cutoff", "d", "n", "k0"); + } +}; +#endif // SMOOTH_STEP + +class NonBondedInteractionHandle + : public AutoParameters { + std::array m_types = {-1, -1}; + std::shared_ptr<::IA_parameters> m_interaction; +#ifdef WCA + std::shared_ptr m_wca; +#endif +#ifdef LENNARD_JONES + std::shared_ptr m_lj; +#endif +#ifdef LENNARD_JONES_GENERIC + std::shared_ptr m_ljgen; +#endif +#ifdef LJCOS + std::shared_ptr m_ljcos; +#endif +#ifdef LJCOS2 + std::shared_ptr m_ljcos2; +#endif +#ifdef HERTZIAN + std::shared_ptr m_hertzian; +#endif +#ifdef GAUSSIAN + std::shared_ptr m_gaussian; +#endif +#ifdef BMHTF_NACL + std::shared_ptr m_bmhtf; +#endif +#ifdef MORSE + std::shared_ptr m_morse; +#endif +#ifdef BUCKINGHAM + std::shared_ptr m_buckingham; +#endif +#ifdef SOFT_SPHERE + std::shared_ptr m_soft_sphere; +#endif +#ifdef HAT + std::shared_ptr m_hat; +#endif +#ifdef GAY_BERNE + std::shared_ptr m_gay_berne; +#endif +#ifdef TABULATED + std::shared_ptr m_tabulated; +#endif +#ifdef DPD + std::shared_ptr m_dpd; +#endif +#ifdef THOLE + std::shared_ptr m_thole; +#endif +#ifdef SMOOTH_STEP + std::shared_ptr m_smooth_step; +#endif + + template + auto make_autoparameter(std::shared_ptr &member, const char *key) const { + auto const setter = [this, &member](Variant const &v) { + member = get_value>(v); + if (m_types[0] != -1) { + auto const types = Variant{std::vector{{m_types[0], m_types[1]}}}; + member->do_call_method("bind_types", VariantMap{{"_types", types}}); + member->copy_si_to_core(); + on_non_bonded_ia_change(); + } + }; + return AutoParameter{key, setter, [&member]() { return member; }}; + } + +public: + NonBondedInteractionHandle() { + add_parameters({ +#ifdef WCA + make_autoparameter(m_wca, "wca"), +#endif +#ifdef LENNARD_JONES + make_autoparameter(m_lj, "lennard_jones"), +#endif +#ifdef LENNARD_JONES_GENERIC + make_autoparameter(m_ljgen, "generic_lennard_jones"), +#endif +#ifdef LJCOS + make_autoparameter(m_ljcos, "lennard_jones_cos"), +#endif +#ifdef LJCOS2 + make_autoparameter(m_ljcos2, "lennard_jones_cos2"), +#endif +#ifdef HERTZIAN + make_autoparameter(m_hertzian, "hertzian"), +#endif +#ifdef GAUSSIAN + make_autoparameter(m_gaussian, "gaussian"), +#endif +#ifdef BMHTF_NACL + make_autoparameter(m_bmhtf, "bmhtf"), +#endif +#ifdef MORSE + make_autoparameter(m_morse, "morse"), +#endif +#ifdef BUCKINGHAM + make_autoparameter(m_buckingham, "buckingham"), +#endif +#ifdef SOFT_SPHERE + make_autoparameter(m_soft_sphere, "soft_sphere"), +#endif +#ifdef HAT + make_autoparameter(m_hat, "hat"), +#endif +#ifdef GAY_BERNE + make_autoparameter(m_gay_berne, "gay_berne"), +#endif +#ifdef TABULATED + make_autoparameter(m_tabulated, "tabulated"), +#endif +#ifdef DPD + make_autoparameter(m_dpd, "dpd"), +#endif +#ifdef THOLE + make_autoparameter(m_thole, "thole"), +#endif +#ifdef SMOOTH_STEP + make_autoparameter(m_smooth_step, "smooth_step"), +#endif + }); + } + +private: + template + void set_member(std::shared_ptr &member, std::string key, + std::string so_name, VariantMap const ¶ms) { + auto const ia_types = VariantMap{{"_types", params.at("_types")}}; + if (params.count(key) != 0) { + member = get_value>(params.at(key)); + member->do_call_method("bind_types", ia_types); + member->copy_si_to_core(); + } else { + auto so_object = context()->make_shared_local(so_name, ia_types); + member = std::dynamic_pointer_cast(so_object); + member->copy_core_to_si(); + } + } + +public: + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "get_types") { + return std::vector{{m_types[0], m_types[1]}}; + } + return {}; + } + + void do_construct(VariantMap const ¶ms) override { + assert(params.count("_types") != 0); + auto const types = get_value>(params.at("_types")); + m_types[0] = std::min(types[0], types[1]); + m_types[1] = std::max(types[0], types[1]); + // make type exist + mpi_realloc_ia_params_local(m_types[1] + 1); + // create interface objects + auto const key = get_ia_param_key(m_types[0], m_types[1]); + m_interaction = ::nonbonded_ia_params[key]; +#ifdef WCA + set_member(m_wca, "wca", "Interactions::InteractionWCA", params); +#endif +#ifdef LENNARD_JONES + set_member(m_lj, "lennard_jones", "Interactions::InteractionLJ", params); +#endif +#ifdef LENNARD_JONES_GENERIC + set_member(m_ljgen, "generic_lennard_jones", + "Interactions::InteractionLJGen", params); +#endif +#ifdef LJCOS + set_member(m_ljcos, "lennard_jones_cos", "Interactions::InteractionLJcos", + params); +#endif +#ifdef LJCOS2 + set_member(m_ljcos2, "lennard_jones_cos2", + "Interactions::InteractionLJcos2", params); +#endif +#ifdef HERTZIAN + set_member(m_hertzian, "hertzian", "Interactions::InteractionHertzian", + params); +#endif +#ifdef GAUSSIAN + set_member(m_gaussian, "gaussian", "Interactions::InteractionGaussian", + params); +#endif +#ifdef BMHTF_NACL + set_member(m_bmhtf, "bmhtf", "Interactions::InteractionBMHTF", params); +#endif +#ifdef MORSE + set_member(m_morse, "morse", "Interactions::InteractionMorse", params); +#endif +#ifdef BUCKINGHAM + set_member(m_buckingham, "buckingham", + "Interactions::InteractionBuckingham", params); +#endif +#ifdef SOFT_SPHERE + set_member(m_soft_sphere, "soft_sphere", + "Interactions::InteractionSoftSphere", params); +#endif +#ifdef HAT + set_member(m_hat, "hat", "Interactions::InteractionHat", params); +#endif +#ifdef GAY_BERNE + set_member(m_gay_berne, "gay_berne", "Interactions::InteractionGayBerne", + params); +#endif +#ifdef TABULATED + set_member(m_tabulated, "tabulated", "Interactions::InteractionTabulated", + params); +#endif +#ifdef DPD + set_member(m_dpd, "dpd", "Interactions::InteractionDPD", params); +#endif +#ifdef THOLE + set_member(m_thole, "thole", "Interactions::InteractionThole", params); +#endif +#ifdef SMOOTH_STEP + set_member(m_smooth_step, "smooth_step", + "Interactions::InteractionSmoothStep", params); +#endif + } + + auto get_ia() const { return m_interaction; } +}; + +} // namespace Interactions +} // namespace ScriptInterface + +#endif diff --git a/src/script_interface/interactions/NonBondedInteractions.hpp b/src/script_interface/interactions/NonBondedInteractions.hpp new file mode 100644 index 00000000000..945ac1be2f1 --- /dev/null +++ b/src/script_interface/interactions/NonBondedInteractions.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021-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 . + */ + +#ifndef SCRIPT_INTERFACE_INTERACTIONS_NONBONDED_INTERACTIONS_HPP +#define SCRIPT_INTERFACE_INTERACTIONS_NONBONDED_INTERACTIONS_HPP + +#include "NonBondedInteraction.hpp" + +#include "core/event.hpp" +#include "core/nonbonded_interactions/nonbonded_interaction_data.hpp" + +#include "script_interface/ObjectMap.hpp" +#include "script_interface/ScriptInterface.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace ScriptInterface { +namespace Interactions { + +class NonBondedInteractions : public ObjectHandle { + using container_type = + std::unordered_map>; + + auto make_interaction(int i, int j) { + assert(i <= j); + auto const types = std::vector{{i, j}}; + return std::dynamic_pointer_cast( + context()->make_shared_local("Interactions::NonBondedInteractionHandle", + {{"_types", Variant{types}}})); + } + +public: + using key_type = typename container_type::key_type; + using mapped_type = typename container_type::mapped_type; + + void reset() { + auto const size = ::max_seen_particle_type; + for (int i = 0; i < size; i++) { + for (int j = i; j < size; j++) { + auto const key = Utils::upper_triangular(i, j, size); + ::nonbonded_ia_params[i] = std::make_shared<::IA_parameters>(); + m_nonbonded_ia_params[key] = make_interaction(i, j); + } + } + on_non_bonded_ia_change(); + } + + void do_construct(VariantMap const ¶ms) override { + auto const size = ::max_seen_particle_type; + make_particle_type_exist_local(size); + for (int i = 0; i < size; i++) { + for (int j = i; j < size; j++) { + auto const key = Utils::upper_triangular(i, j, size); + m_nonbonded_ia_params[key] = make_interaction(i, j); + } + } + } + + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "get_n_types") { + return Variant{::max_seen_particle_type}; + } + if (name == "reset") { + reset(); + return {}; + } + if (name == "insert") { + auto const types = get_value>(params.at("key")); + make_particle_type_exist_local(std::max(types[0], types[1])); + auto const key = get_ia_param_key(std::min(types[0], types[1]), + std::max(types[0], types[1])); + auto obj_ptr = get_value>( + params.at("object")); + ::nonbonded_ia_params[key] = obj_ptr->get_ia(); + m_nonbonded_ia_params[key] = obj_ptr; + on_non_bonded_ia_change(); + return {}; + } + + return {}; + } + +private: + // disable serialization: pickling done by the python interface + std::string get_internal_state() const override { return {}; } + void set_internal_state(std::string const &state) override {} + container_type m_nonbonded_ia_params; +}; + +} // namespace Interactions +} // namespace ScriptInterface + +#endif diff --git a/src/script_interface/interactions/bonded.hpp b/src/script_interface/interactions/bonded.hpp deleted file mode 100644 index 6370468965b..00000000000 --- a/src/script_interface/interactions/bonded.hpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021-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 . - */ -#ifndef SCRIPT_INTERFACE_INTERACTIONS_BONDED_HPP -#define SCRIPT_INTERFACE_INTERACTIONS_BONDED_HPP - -/** @file - * Functions to interface with the core boost::variant. - */ - -#include "core/bonded_interactions/bonded_interaction_data.hpp" - -#include - -/** Return the 0-based type number of the specified bond. */ -inline int bonded_ia_params_zero_based_type(int bond_id) { - if (bonded_ia_params.contains(bond_id)) { - return (*bonded_ia_params.at(bond_id)).which(); - } - return 0; -} - -#endif diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 77418ae1bff..57129779939 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -20,6 +20,8 @@ #include "BondedInteraction.hpp" #include "BondedInteractions.hpp" +#include "NonBondedInteraction.hpp" +#include "NonBondedInteractions.hpp" #include @@ -49,6 +51,65 @@ void initialize(Utils::Factory *om) { om->register_new("Interactions::OifGlobalForcesBond"); om->register_new("Interactions::OifLocalForcesBond"); om->register_new("Interactions::VirtualBond"); + + om->register_new( + "Interactions::NonBondedInteractions"); + om->register_new( + "Interactions::NonBondedInteractionHandle"); +#ifdef LENNARD_JONES + om->register_new("Interactions::InteractionLJ"); +#endif +#ifdef LENNARD_JONES_GENERIC + om->register_new("Interactions::InteractionLJGen"); +#endif +#ifdef LJCOS + om->register_new("Interactions::InteractionLJcos"); +#endif +#ifdef LJCOS2 + om->register_new("Interactions::InteractionLJcos2"); +#endif +#ifdef WCA + om->register_new("Interactions::InteractionWCA"); +#endif +#ifdef HERTZIAN + om->register_new("Interactions::InteractionHertzian"); +#endif +#ifdef GAUSSIAN + om->register_new("Interactions::InteractionGaussian"); +#endif +#ifdef BMHTF_NACL + om->register_new("Interactions::InteractionBMHTF"); +#endif +#ifdef MORSE + om->register_new("Interactions::InteractionMorse"); +#endif +#ifdef BUCKINGHAM + om->register_new( + "Interactions::InteractionBuckingham"); +#endif +#ifdef SOFT_SPHERE + om->register_new( + "Interactions::InteractionSoftSphere"); +#endif +#ifdef HAT + om->register_new("Interactions::InteractionHat"); +#endif +#ifdef GAY_BERNE + om->register_new("Interactions::InteractionGayBerne"); +#endif +#ifdef TABULATED + om->register_new("Interactions::InteractionTabulated"); +#endif +#ifdef DPD + om->register_new("Interactions::InteractionDPD"); +#endif +#ifdef THOLE + om->register_new("Interactions::InteractionThole"); +#endif +#ifdef SMOOTH_STEP + om->register_new( + "Interactions::InteractionSmoothStep"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/src/script_interface/particle_data/ParticleHandle.cpp b/src/script_interface/particle_data/ParticleHandle.cpp index e08ca7b884d..70b9330a93b 100644 --- a/src/script_interface/particle_data/ParticleHandle.cpp +++ b/src/script_interface/particle_data/ParticleHandle.cpp @@ -22,8 +22,8 @@ #include "ParticleHandle.hpp" #include "script_interface/get_value.hpp" -#include "script_interface/interactions/bonded.hpp" +#include "core/bonded_interactions/bonded_interaction_data.hpp" #include "core/grid.hpp" #include "core/particle_data.hpp" #include "core/particle_node.hpp" @@ -399,7 +399,7 @@ Variant ParticleHandle::do_call_method(std::string const &name, delete_particle_bonds(m_pid); } else if (name == "is_valid_bond_id") { auto const bond_id = get_value(params, "bond_id"); - return bonded_ia_params_zero_based_type(bond_id) != 0; + return ::bonded_ia_params.get_zero_based_type(bond_id) != 0; } if (name == "remove_particle") { remove_particle(m_pid); diff --git a/src/script_interface/tests/LocalContext_test.cpp b/src/script_interface/tests/LocalContext_test.cpp index 9122ffa0fee..13421e5b601 100644 --- a/src/script_interface/tests/LocalContext_test.cpp +++ b/src/script_interface/tests/LocalContext_test.cpp @@ -71,6 +71,16 @@ BOOST_AUTO_TEST_CASE(LocalContext_make_shared) { BOOST_CHECK_EQUAL(ctx->name(res.get()), "Dummy"); } +BOOST_AUTO_TEST_CASE(LocalContext_make_shared_local) { + boost::mpi::communicator comm; + auto ctx = std::make_shared(factory, comm); + + auto res = ctx->make_shared_local("Dummy", {}); + BOOST_REQUIRE(res != nullptr); + BOOST_CHECK_EQUAL(res->context(), ctx.get()); + BOOST_CHECK_EQUAL(ctx->name(res.get()), "Dummy"); +} + BOOST_AUTO_TEST_CASE(LocalContext_serialization) { boost::mpi::communicator comm; auto ctx = std::make_shared(factory, comm); diff --git a/src/script_interface/tests/ObjectHandle_test.cpp b/src/script_interface/tests/ObjectHandle_test.cpp index 5ea001e084a..a70487d7282 100644 --- a/src/script_interface/tests/ObjectHandle_test.cpp +++ b/src/script_interface/tests/ObjectHandle_test.cpp @@ -195,6 +195,10 @@ struct LogContext : public Context { return it; } + std::shared_ptr + make_shared_local(std::string const &s, VariantMap const &v) override { + return make_shared(s, v); + } boost::string_ref name(const ObjectHandle *o) const override { return "Dummy"; @@ -258,8 +262,10 @@ BOOST_AUTO_TEST_CASE(interface_) { using namespace Testing; auto log_ctx = std::make_shared(); auto o = log_ctx->make_shared({}, {}); + auto l = log_ctx->make_shared_local({}, {}); BOOST_CHECK(log_ctx->is_head_node()); BOOST_CHECK_EQUAL(log_ctx->name(o.get()), "Dummy"); + BOOST_CHECK_EQUAL(log_ctx->name(l.get()), "Dummy"); log_ctx->parallel_try_catch([]() {}); std::ignore = log_ctx->get_comm(); } diff --git a/testsuite/python/collision_detection.py b/testsuite/python/collision_detection.py index 0c959c35857..bd621630994 100644 --- a/testsuite/python/collision_detection.py +++ b/testsuite/python/collision_detection.py @@ -309,8 +309,7 @@ def test_bind_at_point_of_collision_random(self): self.assertIn(base_particles, bonds) # Tidy - system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=0, sigma=0, cutoff=0) + system.non_bonded_inter[0, 0].lennard_jones.deactivate() def run_test_glue_to_surface_for_pos(self, *positions): system = self.system @@ -578,8 +577,7 @@ def test_glue_to_surface_random(self): self.assertEqual(p.type, self.part_type_vs) # Tidy - system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=0, sigma=0, cutoff=0) + system.non_bonded_inter[0, 0].lennard_jones.deactivate() def test_bind_three_particles(self): system = self.system diff --git a/testsuite/python/constraint_shape_based.py b/testsuite/python/constraint_shape_based.py index a0ee72d187a..07baeb4c5a2 100644 --- a/testsuite/python/constraint_shape_based.py +++ b/testsuite/python/constraint_shape_based.py @@ -383,8 +383,7 @@ def test_ellipsoid(self): energy = system.analysis.energy() self.assertAlmostEqual(energy["total"], r - 1.) # Reset the interaction to zero - system.non_bonded_inter[0, 1].generic_lennard_jones.set_params( - epsilon=0., sigma=0., cutoff=0., shift=0., offset=0., e1=0, e2=0, b1=0., b2=0.) + system.non_bonded_inter[0, 1].generic_lennard_jones.deactivate() def test_cylinder(self): """Tests if shape based constraints can be added to a system both by diff --git a/testsuite/python/cutoffs.py b/testsuite/python/cutoffs.py index 8afacd6c0bd..51c4ca0522e 100644 --- a/testsuite/python/cutoffs.py +++ b/testsuite/python/cutoffs.py @@ -56,8 +56,6 @@ def test(self): self.assertEqual(system.cell_system.interaction_range, -1) if espressomd.has_features("LENNARD_JONES"): - lj_off_params = system.non_bonded_inter[0, - 0].lennard_jones.get_params() system.non_bonded_inter[0, 0].lennard_jones.set_params( sigma=1, epsilon=1, cutoff=2.5, shift="auto") self.assertEqual( @@ -66,8 +64,7 @@ def test(self): system.cell_system.max_cut_nonbonded + system.cell_system.skin) - system.non_bonded_inter[0, - 0].lennard_jones.set_params(**lj_off_params) + system.non_bonded_inter[0, 0].lennard_jones.deactivate() self.assertEqual(system.cell_system.max_cut_nonbonded, -1) self.assertEqual(system.cell_system.interaction_range, -1) diff --git a/testsuite/python/dawaanr-and-bh-gpu.py b/testsuite/python/dawaanr-and-bh-gpu.py index 885ce47df99..ab244188942 100644 --- a/testsuite/python/dawaanr-and-bh-gpu.py +++ b/testsuite/python/dawaanr-and-bh-gpu.py @@ -67,8 +67,7 @@ def test(self): g.kill_particle_motion(rotation=True) system.integrator.set_vv() - system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=0.0, sigma=0.0, cutoff=-1, shift=0.0) + system.non_bonded_inter[0, 0].lennard_jones.deactivate() system.cell_system.skin = 0.0 system.time_step = 0.01 diff --git a/testsuite/python/dawaanr-and-dds-gpu.py b/testsuite/python/dawaanr-and-dds-gpu.py index 73d36707e94..f52d50f09b2 100644 --- a/testsuite/python/dawaanr-and-dds-gpu.py +++ b/testsuite/python/dawaanr-and-dds-gpu.py @@ -63,8 +63,7 @@ def test(self): g.kill_particle_motion(rotation=True) self.system.integrator.set_vv() - self.system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=0.0, sigma=0.0, cutoff=0.0, shift=0.0) + self.system.non_bonded_inter[0, 0].lennard_jones.deactivate() self.system.cell_system.skin = 0.0 self.system.time_step = 0.01 diff --git a/testsuite/python/dds-and-bh-gpu.py b/testsuite/python/dds-and-bh-gpu.py index 6179b6d7ce1..42543994c2c 100644 --- a/testsuite/python/dds-and-bh-gpu.py +++ b/testsuite/python/dds-and-bh-gpu.py @@ -67,8 +67,7 @@ def test(self): g.kill_particle_motion(rotation=True) system.integrator.set_vv() - system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=0.0, sigma=0.0, cutoff=-1, shift=0.0) + system.non_bonded_inter[0, 0].lennard_jones.deactivate() system.cell_system.skin = 0.0 system.time_step = 0.01 diff --git a/testsuite/python/dipolar_direct_summation.py b/testsuite/python/dipolar_direct_summation.py index c6187e1ff2f..eae75f2376f 100644 --- a/testsuite/python/dipolar_direct_summation.py +++ b/testsuite/python/dipolar_direct_summation.py @@ -151,8 +151,7 @@ def gen_reference_data(self, filepath_energy=OPEN_BOUNDARIES_REF_ENERGY, system.integrator.set_steepest_descent( f_max=1, gamma=0.001, max_displacement=0.01) system.integrator.run(100) - system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=0.0, sigma=0, cutoff=0, shift=0) + system.non_bonded_inter[0, 0].lennard_jones.deactivate() system.integrator.set_vv() assert system.analysis.energy()["total"] == 0 diff --git a/testsuite/python/dpd.py b/testsuite/python/dpd.py index 7d8ce3b6641..64f9dfc003a 100644 --- a/testsuite/python/dpd.py +++ b/testsuite/python/dpd.py @@ -59,7 +59,7 @@ def reset_particles(): gamma = 1.5 # No seed should throw exception - with self.assertRaises(ValueError): + with self.assertRaisesRegex(ValueError, "A seed has to be given as keyword argument on first activation of the thermostat"): system.thermostat.set_dpd(kT=kT) system.thermostat.set_dpd(kT=kT, seed=41) diff --git a/testsuite/python/interactions_bonded_interface.py b/testsuite/python/interactions_bonded_interface.py index c5b0c256232..e8c27588611 100644 --- a/testsuite/python/interactions_bonded_interface.py +++ b/testsuite/python/interactions_bonded_interface.py @@ -22,6 +22,7 @@ import espressomd import espressomd.interactions +import espressomd.code_features import numpy as np @@ -43,47 +44,6 @@ def bondsMatch(self, inType, outBond, inParams, outParams, msg_long): self.assertEqual(outBond, inType, msg="Bonded interaction mismatch") tests_common.assert_params_match(self, inParams, outParams, msg_long) - def parameterKeys(self, bondObject): - """ - Check :meth:`~espressomd.interactions.BondedInteraction.valid_keys` - and :meth:`~espressomd.interactions.BondedInteraction.required_keys` - return sets, and that - :meth:`~espressomd.interactions.BondedInteraction.get_default_params` - returns a dictionary with the correct keys. - - Parameters - ---------- - bondObject: instance of a class derived from :class:`espressomd.interactions.BondedInteraction` - Object of the interaction to test, e.g. - :class:`~espressomd.interactions.FeneBond` - """ - classname = bondObject.__class__.__name__ - valid_keys = bondObject.valid_keys() - required_keys = bondObject.required_keys() - old_params = bondObject.params.copy() - default_keys = set(bondObject.get_default_params()) - self.assertIsInstance(valid_keys, set, - f"{classname}.valid_keys() must return a set") - self.assertIsInstance(required_keys, set, - f"{classname}.required_keys() must return a set") - self.assertTrue(default_keys.issubset(valid_keys), - f"{classname}.get_default_params() has unknown " - f"parameters: {default_keys.difference(valid_keys)}") - self.assertTrue(default_keys.isdisjoint(required_keys), - f"{classname}.get_default_params() has extra " - f"parameters: {default_keys.intersection(required_keys)}") - self.assertSetEqual(default_keys, valid_keys - required_keys, - f"{classname}.get_default_params() should have keys: " - f"{valid_keys - required_keys}, got: {default_keys}") - with self.assertRaisesRegex(RuntimeError, "Bond parameters are immutable"): - bondObject.params = {} - for key in (bondObject.params.keys() | old_params.keys()): - if isinstance(old_params[key], str): - self.assertEqual(bondObject.params[key], old_params[key]) - else: - np.testing.assert_allclose( - bondObject.params[key], old_params[key], atol=1e-10) - def generateTestForBondParams(_bondId, _bondClass, _params, _refs=None): """Generates test cases for checking bond parameters set and gotten back from the espresso core actually match those in the Python classes. @@ -144,14 +104,22 @@ def func(self): # check that parameters written and read back are identical outBond = self.system.bonded_inter[bondId] - tnIn = bondClass(**params).type_number() - tnOut = outBond.type_number() + tnIn = bondClass._type_number + tnOut = outBond._type_number outParams = outBond.params self.bondsMatch( tnIn, tnOut, outParamsRef, outParams, - "{}: value set and value gotten back differ for bond id {}: {} vs. {}" - .format(bondClass(**params).type_name(), bondId, outParamsRef, outParams)) - self.parameterKeys(outBond) + f"{bondClass.__name__}: value set and value gotten back " + f"differ for bond id {bondId}: {outParamsRef} vs. {outParams}") + with self.assertRaisesRegex(RuntimeError, "Bond parameters are immutable"): + outBond.params = {} + old_params = outBond.params.copy() + for key in (outBond.params.keys() | old_params.keys()): + if isinstance(old_params[key], str): + self.assertEqual(outBond.params[key], old_params[key]) + else: + np.testing.assert_allclose( + outBond.params[key], old_params[key], atol=1e-10) # check no-op self.assertIsNone(outBond.call_method('unknown')) @@ -175,6 +143,11 @@ def func(self): {"ind1": 0, "ind2": 1, "ind3": 2, "ind4": 3, "kb": 1.1, "refShape": "Initial"}, {"kb": 1.1, "theta0": 0.0}) + test_ibm_tribend_flat = generateTestForBondParams( + 0, espressomd.interactions.IBM_Tribend, + {"ind1": 0, "ind2": 1, "ind3": 2, "ind4": 3, + "kb": 1.1, "refShape": "Flat"}, + {"kb": 1.1, "theta0": 0.0}) test_ibm_triel = generateTestForBondParams( 0, espressomd.interactions.IBM_Triel, {"ind1": 0, "ind2": 1, "ind3": 2, "k1": 1.1, "k2": 1.2, @@ -230,8 +203,8 @@ def test_bonded_coulomb_sr(self): def test_exceptions(self): error_msg_not_yet_defined = 'The bond with id 0 is not yet defined' - bond_type = espressomd.interactions.get_bonded_interaction_type_from_es_core( - 5000) + bond_type = self.system.bonded_inter.call_method( + 'get_zero_based_type', bond_id=5000) self.assertEqual(bond_type, 0) has_bond = self.system.bonded_inter.call_method('has_bond', bond_id=0) self.assertFalse(has_bond) @@ -251,6 +224,19 @@ def test_exceptions(self): self.system.bonded_inter[0] = fene_bond with self.assertRaisesRegex(ValueError, 'Bonds can only be overwritten by bonds of equal type'): self.system.bonded_inter[0] = angle_bond + with self.assertRaisesRegex(RuntimeError, "No bond with id 8 exists in the ESPResSo core"): + espressomd.interactions.FeneBond(bond_id=8) + with self.assertRaisesRegex(RuntimeError, "The bond with id 0 is not defined as a FENE bond in the ESPResSo core"): + espressomd.interactions.FeneBond(bond_id=0) + + # bonds can only be compared for equality + self.assertEqual(angle_bond, angle_bond) + self.assertNotEqual(harm_bond1, harm_bond2) + self.assertNotEqual(angle_bond, fene_bond) + with self.assertRaises(NotImplementedError): + angle_bond > fene_bond + with self.assertRaises(NotImplementedError): + angle_bond <= fene_bond # bonds are immutable with self.assertRaisesRegex(RuntimeError, "Parameter 'r_0' is read-only"): @@ -259,16 +245,22 @@ def test_exceptions(self): # sanity checks during bond construction with self.assertRaisesRegex(RuntimeError, "Parameter 'r_0' is missing"): espressomd.interactions.HarmonicBond(k=1.) - with self.assertRaisesRegex(ValueError, r"Only the following keys can be given as keyword arguments: " - r"\['k', 'r_0', 'r_cut'\], got \['k', 'r_0', 'rcut'\] " - r"\(unknown \['rcut'\]\)"): + with self.assertRaisesRegex(RuntimeError, f"Parameter 'rcut' is not recognized"): espressomd.interactions.HarmonicBond(k=1., r_0=1., rcut=2.) - with self.assertRaisesRegex(ValueError, "Unknown refShape: 'Unknown'"): + with self.assertRaisesRegex(ValueError, "Invalid value for parameter 'refShape': 'Unknown'"): espressomd.interactions.IBM_Tribend( ind1=0, ind2=1, ind3=2, ind4=3, kb=1.1, refShape='Unknown') - with self.assertRaisesRegex(ValueError, "Unknown elasticLaw: 'Unknown'"): + 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() @@ -336,6 +328,18 @@ def test_exceptions(self): self.assertEqual(len(self.system.bonded_inter), 2) self.system.bonded_inter.clear() + def test_feature_checks(self): + base_class = espressomd.interactions.BondedInteraction + FeaturesError = espressomd.code_features.FeaturesError + for class_bond in espressomd.interactions.__dict__.values(): + if isinstance(class_bond, type) and issubclass( + class_bond, base_class) and class_bond != base_class: + feature = getattr(class_bond, "_so_feature", None) + if feature is not None and not espressomd.code_features.has_features( + feature): + with self.assertRaisesRegex(FeaturesError, f"Missing features {feature}"): + class_bond() + if __name__ == "__main__": ut.main() diff --git a/testsuite/python/interactions_non-bonded.py b/testsuite/python/interactions_non-bonded.py index b5eaec6ec55..ba55bfced47 100644 --- a/testsuite/python/interactions_non-bonded.py +++ b/testsuite/python/interactions_non-bonded.py @@ -291,21 +291,32 @@ def assertItemsFractionAlmostEqual(self, a, b): @utx.skipIfMissingFeatures("LENNARD_JONES_GENERIC") def test_lj_generic(self): + params = {"epsilon": 2.12, + "sigma": 1.37, + "cutoff": 2.122, + "offset": 0.185, + "b1": 4.22, + "b2": 3.63, + "e1": 10.32, + "e2": 5.81, + "shift": -0.13} self.run_test("generic_lennard_jones", - {"epsilon": 2.12, - "sigma": 1.37, - "cutoff": 2.122, - "offset": 0.185, - "b1": 4.22, - "b2": 3.63, - "e1": 10.32, - "e2": 5.81, - "shift": -0.13}, + params, force_kernel=tests_common.lj_generic_force, energy_kernel=tests_common.lj_generic_potential, n_steps=231, force_kernel_needs_espressomd=True) + params["shift"] = "auto" + obj = espressomd.interactions.GenericLennardJonesInteraction(**params) + ref_shift = obj.b2 * (obj.sigma / obj.cutoff)**obj.e2 - \ + obj.b1 * (obj.sigma / obj.cutoff)**obj.e1 + self.assertAlmostEqual(obj.shift, ref_shift, delta=1e-10) + + params["cutoff"] = 0. + obj = espressomd.interactions.GenericLennardJonesInteraction(**params) + self.assertEqual(obj.shift, 0.) + # Test WCA Potential @utx.skipIfMissingFeatures("WCA") def test_wca(self): diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index c0d236b7b0f..1bdaf1f9a6c 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -22,15 +22,14 @@ import espressomd import espressomd.interactions +import espressomd.code_features -class Non_bonded_interactionsTests(ut.TestCase): +class Test(ut.TestCase): system = espressomd.System(box_l=[30.0, 30.0, 30.0]) def tearDown(self): - if espressomd.has_features(["LENNARD_JONES"]): - self.system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=0., sigma=0., cutoff=0., shift=0.) + self.system.non_bonded_inter.reset() def intersMatch(self, inType, outInter, inParams, outParams, msg_long): """Check, if the interaction type set and gotten back as well as the @@ -40,40 +39,6 @@ def intersMatch(self, inType, outInter, inParams, outParams, msg_long): self.assertIsInstance(outInter, inType) tests_common.assert_params_match(self, inParams, outParams, msg_long) - def parameterKeys(self, interObject): - """ - Check :meth:`~espressomd.interactions.NonBondedInteraction.valid_keys` - and :meth:`~espressomd.interactions.NonBondedInteraction.required_keys` - return sets, and that - :meth:`~espressomd.interactions.NonBondedInteraction.default_params` - returns a dictionary with the correct keys. - - Parameters - ---------- - interObject: instance of a class derived from :class:`espressomd.interactions.NonBondedInteraction` - Object of the interaction to test, e.g. - :class:`~espressomd.interactions.LennardJonesInteraction` - """ - classname = interObject.__class__.__name__ - valid_keys = interObject.valid_keys() - required_keys = interObject.required_keys() - default_keys = set(interObject.default_params().keys()) - self.assertIsInstance(valid_keys, set, - "{}.valid_keys() must return a set".format( - classname)) - self.assertIsInstance(required_keys, set, - "{}.required_keys() must return a set".format( - classname)) - self.assertTrue(default_keys.issubset(valid_keys), - "{}.default_params() has unknown parameters: {}".format( - classname, default_keys.difference(valid_keys))) - self.assertTrue(default_keys.isdisjoint(required_keys), - "{}.default_params() has extra parameters: {}".format( - classname, default_keys.intersection(required_keys))) - self.assertSetEqual(default_keys, valid_keys - required_keys, - "{}.default_params() should have keys: {}, got: {}".format( - classname, valid_keys - required_keys, default_keys)) - def generateTestForNon_bonded_interaction( _partType1, _partType2, _interClass, _params, _interName): """Generates test cases for checking interaction parameters set and @@ -114,10 +79,9 @@ def func(self): self.intersMatch( interClass, outInter, params, outParams, - "{}: value set and value gotten back differ for particle types {} and {}: {} vs. {}" - .format(interClass(**params).type_name(), partType1, partType2, - params, outParams)) - self.parameterKeys(outInter) + f"{interClass.__name__}: value set and value gotten back " + f"differ for particle types {partType1} and {partType2}: " + f"{params} vs. {outParams}") return func @@ -162,26 +126,65 @@ def func(self): "k2": 5.0, "mu": 2.0, "nu": 1.0}, "gay_berne") + @utx.skipIfMissingFeatures(["LENNARD_JONES"]) + def test_reset(self): + lj_params_inactive = {"shift": 0., "sigma": 0., "epsilon": 0., + "cutoff": -1., "offset": 0., "min": 0.} + # global reset + self.system.non_bonded_inter[0, 0].lennard_jones.set_params( + epsilon=1., sigma=2., cutoff=3., shift="auto") + self.system.non_bonded_inter.reset() + self.assertEqual(self.system.non_bonded_inter[0, 0].lennard_jones.get_params(), + lj_params_inactive) + # handle reset + self.system.non_bonded_inter[0, 0].lennard_jones.set_params( + epsilon=1., sigma=2., cutoff=3., shift="auto") + self.system.non_bonded_inter[0, 0].reset() + self.assertEqual(self.system.non_bonded_inter[0, 0].lennard_jones.get_params(), + lj_params_inactive) + # potential reset + self.system.non_bonded_inter[0, 0].lennard_jones.set_params( + epsilon=1., sigma=2., cutoff=3., shift="auto") + self.system.non_bonded_inter[0, 0].lennard_jones.deactivate() + self.assertEqual(self.system.non_bonded_inter[0, 0].lennard_jones.get_params(), + lj_params_inactive) + + @utx.skipIfMissingFeatures(["WCA"]) + def test_set_params(self): + wca = espressomd.interactions.WCAInteraction(epsilon=1., sigma=2.) + wca.set_params(epsilon=2., sigma=3.) + self.assertEqual(wca.get_params()["epsilon"], 2.) + self.assertEqual(wca.get_params()["sigma"], 3.) + self.system.non_bonded_inter[0, 0].wca = wca + self.assertEqual( + self.system.non_bonded_inter[0, 0].wca.get_params()["epsilon"], 2.) + self.assertEqual(wca.get_params()["epsilon"], 2.) + self.assertEqual(wca.get_params()["sigma"], 3.) + self.system.non_bonded_inter.reset() + wca.set_params(epsilon=4., sigma=3.) + self.assertEqual( + self.system.non_bonded_inter[0, 0].wca.get_params()["epsilon"], 4.) + self.assertEqual(wca.get_params()["epsilon"], 4.) + with self.assertRaisesRegex(RuntimeError, r"Non-bonded interaction is already bound to interaction pair \[0, 0\]"): + self.system.non_bonded_inter[0, 1].wca = wca + @utx.skipIfMissingFeatures("LENNARD_JONES") def test_exceptions(self): - err_msg_required = (r"The following keys have to be given as keyword arguments: " - r"\['cutoff', 'epsilon', 'shift', 'sigma'\], got " - r"\['epsilon', 'sigma'\] \(missing \['cutoff', 'shift'\]\)") - err_msg_valid = (r"Only the following keys can be given as keyword arguments: " - r"\['cutoff', 'epsilon', 'min', 'offset', 'shift', 'sigma'\], got " - r"\['cutoff', 'epsilon', 'shift', 'sigma', 'unknown'\] \(unknown \['unknown'\]\)") - with self.assertRaisesRegex(ValueError, err_msg_required): + with self.assertRaisesRegex(RuntimeError, "Parameter 'shift' is missing"): espressomd.interactions.LennardJonesInteraction( - epsilon=1., sigma=2.) - with self.assertRaisesRegex(ValueError, err_msg_required): + epsilon=1., sigma=2., cutoff=2.) + with self.assertRaisesRegex(RuntimeError, "Parameter 'shift' is missing"): self.system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=1., sigma=2.) - with self.assertRaisesRegex(ValueError, err_msg_valid): + epsilon=1., sigma=2., cutoff=2.) + with self.assertRaisesRegex(RuntimeError, "Parameter 'unknown' is not recognized"): espressomd.interactions.LennardJonesInteraction( epsilon=1., sigma=2., cutoff=3., shift=4., unknown=5.) - with self.assertRaisesRegex(ValueError, err_msg_valid): + with self.assertRaisesRegex(RuntimeError, "Parameter 'unknown' is not recognized"): self.system.non_bonded_inter[0, 0].lennard_jones.set_params( epsilon=1., sigma=2., cutoff=3., shift=4., unknown=5.) + with self.assertRaisesRegex(ValueError, "LJ parameter 'shift' has to be 'auto' or a float"): + self.system.non_bonded_inter[0, 0].lennard_jones.set_params( + epsilon=1., sigma=2., cutoff=3., shift="automatic") skin = self.system.cell_system.skin box_l = self.system.box_l @@ -190,11 +193,158 @@ def test_exceptions(self): max_ia_cutoff = min(box_l / node_grid) - skin * (n_nodes > 1) wrong_cutoff = 1.01 * max_ia_cutoff lennard_jones = self.system.non_bonded_inter[0, 0].lennard_jones - with self.assertRaisesRegex(Exception, "setting LennardJones raised an error: ERROR: interaction range .+ in direction [0-2] is larger than the local box size"): + with self.assertRaisesRegex(Exception, "setting LennardJonesInteraction raised an error: ERROR: interaction range .+ in direction [0-2] is larger than the local box size"): lennard_jones.set_params( epsilon=1., sigma=1., cutoff=wrong_cutoff, shift="auto") self.assertAlmostEqual( lennard_jones.get_params()['cutoff'], wrong_cutoff, delta=1e-10) + inter_00_obj = self.system.non_bonded_inter[0, 0] + self.assertEqual(inter_00_obj.call_method("get_types"), [0, 0]) + self.assertIsNone(inter_00_obj.call_method("unknown")) + + def test_feature_checks(self): + base_class = espressomd.interactions.NonBondedInteraction + FeaturesError = espressomd.code_features.FeaturesError + for class_ia in espressomd.interactions.__dict__.values(): + if isinstance(class_ia, type) and issubclass( + class_ia, base_class) and class_ia != base_class: + feature = class_ia._so_feature + if not espressomd.code_features.has_features(feature): + with self.assertRaisesRegex(FeaturesError, f"Missing features {feature}"): + class_ia() + + def check_potential_exceptions( + self, ia_class, params, check_keys, invalid_value=-0.1): + for key in check_keys: + with self.assertRaisesRegex(ValueError, f"parameter '{key}'"): + invalid_params = params.copy() + invalid_params[key] = invalid_value + ia_class(**invalid_params) + + @utx.skipIfMissingFeatures("WCA") + def test_wca_exceptions(self): + self.assertEqual(self.system.non_bonded_inter[0, 0].wca.cutoff, -1.) + self.check_potential_exceptions( + espressomd.interactions.WCAInteraction, + {"epsilon": 1., "sigma": 1.}, + ("epsilon", "sigma")) + + @utx.skipIfMissingFeatures("LENNARD_JONES") + def test_lj_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.LennardJonesInteraction, + {"epsilon": 1., "sigma": 1., "cutoff": 1.5, "shift": 0.2}, + ("epsilon", "sigma", "cutoff")) + + @utx.skipIfMissingFeatures("LENNARD_JONES_GENERIC") + def test_ljgen_exceptions(self): + check_keys = ("epsilon", "sigma", "cutoff") + params = {"epsilon": 1., "sigma": 1., "cutoff": 1.5, "shift": 0.2, + "offset": 0.1, "b1": 1., "b2": 1., "e1": 12., "e2": 6.} + if espressomd.has_features(["LJGEN_SOFTCORE"]): + params["delta"] = 0.1 + params["lam"] = 0.3 + check_keys = ("delta", "lam", *check_keys) + self.check_potential_exceptions( + espressomd.interactions.GenericLennardJonesInteraction, + params, + check_keys) + with self.assertRaisesRegex(ValueError, "Generic LJ parameter 'shift' has to be 'auto' or a float"): + invalid_params = params.copy() + invalid_params["shift"] = "unknown" + espressomd.interactions.GenericLennardJonesInteraction( + **invalid_params) + + @utx.skipIfMissingFeatures("LJCOS") + def test_lj_cos_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.LennardJonesCosInteraction, + {"epsilon": 1., "sigma": 1., "cutoff": 1.5, "offset": 0.2}, + ("epsilon", "sigma", "cutoff")) + + @utx.skipIfMissingFeatures("LJCOS2") + def test_lj_cos2_exceptions(self): + self.assertEqual( + self.system.non_bonded_inter[0, 0].lennard_jones_cos2.cutoff, -1.) + self.check_potential_exceptions( + espressomd.interactions.LennardJonesCos2Interaction, + {"epsilon": 1., "sigma": 1., "width": 1.5, "offset": 0.2}, + ("epsilon", "sigma", "width")) + + @utx.skipIfMissingFeatures("HERTZIAN") + def test_hertzian_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.HertzianInteraction, + {"eps": 1., "sig": 1.}, + ("eps", "sig") + ) + + @utx.skipIfMissingFeatures("GAUSSIAN") + def test_gaussian_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.GaussianInteraction, + {"eps": 1., "sig": 1., "cutoff": 1.5}, + ("eps", "sig", "cutoff") + ) + + @utx.skipIfMissingFeatures("BMHTF_NACL") + def test_bmhtf_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.BMHTFInteraction, + {"a": 3., "b": 2., "c": 1., "d": 4., "sig": 0.13, "cutoff": 1.2}, + ("a", "c", "d", "cutoff") + ) + + @utx.skipIfMissingFeatures("MORSE") + def test_morse_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.MorseInteraction, + {"eps": 1., "alpha": 3., "rmin": 0.1, "cutoff": 1.2}, + ("eps", "cutoff") + ) + + @utx.skipIfMissingFeatures("BUCKINGHAM") + def test_buckingham_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.BuckinghamInteraction, + {"a": 2., "b": 3., "c": 5., "d": 4., "discont": 1., "cutoff": 2., + "shift": 0.1}, + ("a", "b", "c", "d", "cutoff") + ) + + @utx.skipIfMissingFeatures("SOFT_SPHERE") + def test_soft_sphere_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.SoftSphereInteraction, + {"a": 1., "n": 3., "cutoff": 1.1, "offset": 0.1}, + ("a", "offset", "cutoff") + ) + + @utx.skipIfMissingFeatures("HAT") + def test_hat_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.HatInteraction, + {"F_max": 10., "cutoff": 1.}, + ("F_max", "cutoff") + ) + + @utx.skipIfMissingFeatures("SMOOTH_STEP") + def test_smooth_step_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.SmoothStepInteraction, + {"eps": 4., "sig": 3., "cutoff": 1., "d": 2., "n": 11, "k0": 2.}, + ("eps", "sig", "cutoff") + ) + + @utx.skipIfMissingFeatures("DPD") + def test_dpd_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.DPDInteraction, + {"weight_function": 1, "gamma": 2., "r_cut": 2., + "trans_weight_function": 1, "trans_gamma": 1., "trans_r_cut": 2.}, + ("weight_function", "trans_weight_function"), + invalid_value=-1 + ) if __name__ == "__main__": diff --git a/testsuite/python/p3m_fft.py b/testsuite/python/p3m_fft.py index 481fc865c4f..21f63440e16 100644 --- a/testsuite/python/p3m_fft.py +++ b/testsuite/python/p3m_fft.py @@ -65,7 +65,7 @@ def minimize(self): self.system.integrator.run(100) self.system.integrator.set_vv() self.system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=0.0, sigma=1.0, cutoff=2) + epsilon=0.0, sigma=1.0, cutoff=2, shift="auto") def add_charged_particles(self): np.random.seed(seed=42) diff --git a/testsuite/python/save_checkpoint.py b/testsuite/python/save_checkpoint.py index 9b502cf10e6..ca001c2e374 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -163,7 +163,7 @@ # constraints system.constraints.add(shape=espressomd.shapes.Sphere(center=system.box_l / 2, radius=0.1), - particle_type=17) + particle_type=7) system.constraints.add( shape=espressomd.shapes.Wall( normal=[1. / np.sqrt(3)] * 3, dist=0.5)) @@ -233,22 +233,39 @@ epsilon=1.2, sigma=1.3, cutoff=2.0, shift=0.1) system.non_bonded_inter[3, 0].lennard_jones.set_params( epsilon=1.2, sigma=1.7, cutoff=2.0, shift=0.1) - system.non_bonded_inter[1, 17].lennard_jones.set_params( + system.non_bonded_inter[1, 7].lennard_jones.set_params( epsilon=1.2e5, sigma=1.7, cutoff=2.0, shift=0.1) +if espressomd.has_features(['DPD']): + dpd_params = {"weight_function": 1, "gamma": 2., "trans_r_cut": 2., "k": 2., + "trans_weight_function": 0, "trans_gamma": 1., "r_cut": 2.} + dpd_ia = espressomd.interactions.DPDInteraction(**dpd_params) + handle_ia = espressomd.interactions.NonBondedInteractionHandle( + _types=(0, 0)) + checkpoint.register("dpd_ia") + checkpoint.register("dpd_params") + checkpoint.register("handle_ia") # bonded interactions harmonic_bond = espressomd.interactions.HarmonicBond(r_0=0.0, k=1.0) system.bonded_inter.add(harmonic_bond) p2.add_bond((harmonic_bond, p1)) if 'THERM.LB' not in modes: - thermalized_bond = espressomd.interactions.ThermalizedBond( - temp_com=0.0, gamma_com=0.0, temp_distance=0.2, gamma_distance=0.5, - r_cut=2, seed=51) - system.bonded_inter.add(thermalized_bond) - p2.add_bond((thermalized_bond, p1)) + # create 3 thermalized bonds that will overwrite each other's seed + thermalized_bond_params = dict(temp_com=0.1, temp_distance=0.2, + gamma_com=0.3, gamma_distance=0.5, r_cut=2.) + thermalized_bond1 = espressomd.interactions.ThermalizedBond( + seed=1, **thermalized_bond_params) + thermalized_bond2 = espressomd.interactions.ThermalizedBond( + seed=2, **thermalized_bond_params) + thermalized_bond3 = espressomd.interactions.ThermalizedBond( + seed=3, **thermalized_bond_params) + system.bonded_inter.add(thermalized_bond1) + p2.add_bond((thermalized_bond1, p1)) + checkpoint.register("thermalized_bond2") + checkpoint.register("thermalized_bond_params") if espressomd.has_features(['ELECTROSTATICS', 'MASS', 'ROTATION']): dh = espressomd.drude_helpers.DrudeHelpers() - dh.add_drude_particle_to_core(system, harmonic_bond, thermalized_bond, + dh.add_drude_particle_to_core(system, harmonic_bond, thermalized_bond1, p2, 10, 1., 4.6, 0.8, 2.) checkpoint.register("dh") strong_harmonic_bond = espressomd.interactions.HarmonicBond(r_0=0.0, k=5e5) diff --git a/testsuite/python/tabulated.py b/testsuite/python/tabulated.py index 199d12528d1..73304f865af 100644 --- a/testsuite/python/tabulated.py +++ b/testsuite/python/tabulated.py @@ -59,6 +59,8 @@ def check(self): def test_non_bonded(self): self.system.non_bonded_inter[0, 0].tabulated.set_params( min=self.min_, max=self.max_, energy=self.energy, force=self.force) + self.assertEqual( + self.system.non_bonded_inter[0, 0].tabulated.cutoff, self.max_) params = self.system.non_bonded_inter[0, 0].tabulated.get_params() np.testing.assert_allclose(params['force'], self.force) @@ -70,6 +72,21 @@ def test_non_bonded(self): self.system.non_bonded_inter[0, 0].tabulated.set_params( min=-1, max=-1, energy=[], force=[]) + self.assertEqual( + self.system.non_bonded_inter[0, 0].tabulated.cutoff, -1.) + + with self.assertRaisesRegex(ValueError, "TabulatedPotential parameter 'max' must be larger than or equal to parameter 'min'"): + espressomd.interactions.TabulatedNonBonded( + min=1., max=0., energy=[0.], force=[0.]) + with self.assertRaisesRegex(ValueError, "TabulatedPotential parameter 'force' must contain 1 element"): + espressomd.interactions.TabulatedNonBonded( + min=1., max=1., energy=[0., 0.], force=[0., 0.]) + with self.assertRaisesRegex(ValueError, "TabulatedPotential parameter 'force' must contain at least 1 element"): + espressomd.interactions.TabulatedNonBonded( + min=1., max=2., energy=[], force=[]) + with self.assertRaisesRegex(ValueError, "TabulatedPotential parameter 'force' must have the same size as parameter 'energy'"): + espressomd.interactions.TabulatedNonBonded( + min=1., max=2., energy=[0.], force=[0., 0.]) @utx.skipIfMissingFeatures("TABULATED") def test_bonded(self): diff --git a/testsuite/python/test_checkpoint.py b/testsuite/python/test_checkpoint.py index 6de38133e73..12bf3ae6739 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -333,7 +333,9 @@ def test_integrator_SDM(self): @utx.skipIfMissingFeatures('LENNARD_JONES') @ut.skipIf('LJ' not in modes, "Skipping test due to missing mode.") - def test_non_bonded_inter(self): + def test_non_bonded_inter_lj(self): + self.assertTrue( + system.non_bonded_inter[0, 0].lennard_jones.call_method("is_registered")) params1 = system.non_bonded_inter[0, 0].lennard_jones.get_params() params2 = system.non_bonded_inter[3, 0].lennard_jones.get_params() reference1 = {'shift': 0.1, 'sigma': 1.3, 'epsilon': 1.2, @@ -342,6 +344,13 @@ def test_non_bonded_inter(self): 'cutoff': 2.0, 'offset': 0.0, 'min': 0.0} self.assertEqual(params1, reference1) self.assertEqual(params2, reference2) + self.assertTrue(handle_ia.lennard_jones.call_method("is_registered")) + self.assertEqual(handle_ia.lennard_jones.get_params(), reference1) + + @utx.skipIfMissingFeatures('DPD') + def test_non_bonded_inter_dpd(self): + self.assertEqual(dpd_ia.get_params(), dpd_params) + self.assertFalse(dpd_ia.call_method("is_registered")) def test_bonded_inter(self): # check the ObjectHandle was correctly initialized (including MPI) @@ -349,18 +358,15 @@ def test_bonded_inter(self): self.assertEqual(len(bond_ids), len(system.bonded_inter)) # check bonded interactions partcl_1 = system.part.by_id(1) - state = partcl_1.bonds[0][0].params reference = {'r_0': 0.0, 'k': 1.0, 'r_cut': 0.0} - self.assertEqual(state, reference) - state = partcl_1.bonds[0][0].params - self.assertEqual(state, reference) + self.assertEqual(partcl_1.bonds[0][0].params, reference) + self.assertEqual(system.bonded_inter[0].params, reference) if 'THERM.LB' not in modes: - state = partcl_1.bonds[1][0].params - reference = {'temp_com': 0., 'gamma_com': 0., 'temp_distance': 0.2, - 'gamma_distance': 0.5, 'r_cut': 2.0, 'seed': 51} - self.assertEqual(state, reference) - state = partcl_1.bonds[1][0].params - self.assertEqual(state, reference) + # all thermalized bonds should be identical + reference = {**thermalized_bond_params, 'seed': 3} + self.assertEqual(partcl_1.bonds[1][0].params, reference) + self.assertEqual(system.bonded_inter[1].params, reference) + self.assertEqual(thermalized_bond2.params, reference) # immersed boundary bonds self.assertEqual( ibm_volcons_bond.params, {'softID': 15, 'kappaV': 0.01}) @@ -629,7 +635,7 @@ def test_constraints(self): self.assertIsInstance(c[0].shape, espressomd.shapes.Sphere) self.assertAlmostEqual(c[0].shape.radius, 0.1, delta=1E-10) - self.assertEqual(c[0].particle_type, 17) + self.assertEqual(c[0].particle_type, 7) self.assertIsInstance(c[1].shape, espressomd.shapes.Wall) np.testing.assert_allclose(np.copy(c[1].shape.normal), diff --git a/testsuite/python/virtual_sites_relative.py b/testsuite/python/virtual_sites_relative.py index 78c5c4a3743..aa0a3b055af 100644 --- a/testsuite/python/virtual_sites_relative.py +++ b/testsuite/python/virtual_sites_relative.py @@ -37,8 +37,7 @@ def tearDown(self): self.system.part.clear() self.system.thermostat.turn_off() self.system.integrator.set_vv() - self.system.non_bonded_inter[0, 0].lennard_jones.set_params( - epsilon=0., sigma=0., cutoff=0., shift=0.) + self.system.non_bonded_inter[0, 0].lennard_jones.deactivate() self.system.virtual_sites = espressomd.virtual_sites.VirtualSitesOff() def multiply_quaternions(self, a, b):