From 4241606a25ad8eedb0f6dc7c99688e85e3b6d78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 18 Aug 2022 17:54:52 +0200 Subject: [PATCH 01/23] Write new non-bonded interaction interface --- src/core/event.cpp | 5 + src/core/event.hpp | 3 + .../nonbonded_interaction_data.cpp | 52 +++-- .../nonbonded_interaction_data.hpp | 21 +- src/python/espressomd/interactions.pyx | 170 +++++++++++++-- src/python/espressomd/script_interface.pyx | 8 +- src/script_interface/Context.hpp | 6 + src/script_interface/GlobalContext.cpp | 14 +- src/script_interface/GlobalContext.hpp | 3 + src/script_interface/LocalContext.hpp | 6 + .../interactions/NonBondedInteraction.hpp | 199 ++++++++++++++++++ .../interactions/NonBondedInteractions.hpp | 128 +++++++++++ .../interactions/initialize.cpp | 7 + .../tests/LocalContext_test.cpp | 10 + .../tests/ObjectHandle_test.cpp | 6 + .../interactions_non-bonded_interface.py | 39 +++- testsuite/python/save_checkpoint.py | 4 + testsuite/python/test_checkpoint.py | 4 + 18 files changed, 638 insertions(+), 47 deletions(-) create mode 100644 src/script_interface/interactions/NonBondedInteraction.hpp create mode 100644 src/script_interface/interactions/NonBondedInteractions.hpp diff --git a/src/core/event.cpp b/src/core/event.cpp index 3419a7cf206..0607f978408 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/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 5250e802c87..8d782caee1e 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -48,7 +48,8 @@ * variables *****************************************/ int max_seen_particle_type = 0; -std::vector nonbonded_ia_params; +std::vector old_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,21 +61,33 @@ 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); + 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_.at(new_key) = std::move(old_nonbonded_ia_params[old_key]); + 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; + std::swap(::old_nonbonded_ia_params, new_params_); + std::swap(::nonbonded_ia_params, new_params); } REGISTER_CALLBACK(mpi_realloc_ia_params_local) @@ -85,12 +98,12 @@ static void mpi_realloc_ia_params(int new_size) { } static void mpi_bcast_all_ia_params_local() { - boost::mpi::broadcast(comm_cart, nonbonded_ia_params, 0); + boost::mpi::broadcast(comm_cart, old_nonbonded_ia_params, 0); } REGISTER_CALLBACK(mpi_bcast_all_ia_params_local) -/** Broadcast @ref nonbonded_ia_params to all nodes. */ +/** Broadcast @ref old_nonbonded_ia_params to all nodes. */ static void mpi_bcast_all_ia_params() { mpi_call_all(mpi_bcast_all_ia_params_local); } @@ -103,7 +116,7 @@ IA_parameters *get_ia_param_safe(int i, int j) { std::string ia_params_get_state() { std::stringstream out; boost::archive::binary_oarchive oa(out); - oa << nonbonded_ia_params; + oa << old_nonbonded_ia_params; oa << max_seen_particle_type; return out.str(); } @@ -113,8 +126,8 @@ void ia_params_set_state(std::string const &state) { 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; + old_nonbonded_ia_params.clear(); + ia >> old_nonbonded_ia_params; ia >> max_seen_particle_type; mpi_realloc_ia_params(max_seen_particle_type); mpi_bcast_all_ia_params(); @@ -204,16 +217,21 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { double maximal_cutoff_nonbonded() { auto max_cut_nonbonded = INACTIVE_CUTOFF; - for (auto &data : nonbonded_ia_params) { + for (auto &data : old_nonbonded_ia_params) { data.max_cut = recalc_maximal_cutoff(data); max_cut_nonbonded = std::max(max_cut_nonbonded, data.max_cut); } + for (auto &data : nonbonded_ia_params) { + 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{}); + boost::fill(old_nonbonded_ia_params, IA_parameters{}); mpi_bcast_all_ia_params(); } diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index defb1d1f122..fd85c9bbb3a 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -185,7 +186,7 @@ struct Thole_Parameters { struct DPDParameters { double gamma = 0.; double k = 1.; - double cutoff = -1.; + double cutoff = INACTIVE_CUTOFF; int wf = 0; double pref = 0.0; }; @@ -275,7 +276,8 @@ struct IA_parameters { #endif }; -extern std::vector nonbonded_ia_params; +extern std::vector old_nonbonded_ia_params; +extern std::vector> nonbonded_ia_params; /** Maximal particle type seen so far. */ extern int max_seen_particle_type; @@ -285,6 +287,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,11 +306,7 @@ 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 ::old_nonbonded_ia_params[get_ia_param_key(i, j)]; } /** Get interaction parameters between particle types i and j. @@ -318,6 +323,8 @@ std::string ia_params_get_state(); */ 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 * for this particle type. The interactions are initialized with values diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 2344513c14b..73b4dac197c 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -205,6 +205,85 @@ cdef class NonBondedInteraction: raise Exception( "Subclasses of NonBondedInteraction must define the required_keys() method.") + +class NewNonBondedInteraction(ScriptInterfaceHelper): + """ + Represents an instance of a non-bonded interaction, such as Lennard-Jones. + + """ + + def __init__(self, **kwargs): + if "sip" in kwargs: + super().__init__(**kwargs) + else: + self._validate(params=kwargs, check_required=True) + params = self.default_params() + params.update(kwargs) + super().__init__(**params) + + def __str__(self): + return f'{self.__class__.__name__}({self.get_params()})' + + def _validate(self, params, check_required=False): + # Check, if any key was passed, which is not known + keys = {x for x in params.keys() if x != "_types"} + utils.check_valid_keys(self.valid_keys(), keys) + # When an interaction is newly activated, all required keys must be + # given + if check_required: + utils.check_required_keys(self.required_keys(), params.keys()) + + def set_params(self, **kwargs): + """Update the given parameters. + + """ + self._validate(params=kwargs, check_required=not self.is_active()) + + params = self.get_params() + params.update(kwargs) + + err_msg = f"setting {self.type_name()} raised an error" + self.call_method("set_params", handle_errors_message=err_msg, **params) + + def _serialize(self): + return (self.__class__.__name__, self.get_params()) + + def default_params(self): + """Virtual method. + + """ + raise Exception( + "Subclasses of NonBondedInteraction must define the default_params() method.") + + def is_active(self): + """Virtual method. + + """ + 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): @@ -1528,17 +1607,32 @@ IF GAUSSIAN == 1: return {"eps", "sig", "cutoff"} -class NonBondedInteractionHandle: +@script_interface_register +class NonBondedInteractionHandle(ScriptInterfaceHelper): """ Provides access to all non-bonded interactions between two particle types. """ + _so_name = "Interactions::NonBondedInteractionHandle" - def __init__(self, _type1, _type2): + def __getattr__(self, key): + obj = super().__getattr__(key) + return globals()[obj.__class__.__name__]( + _types=self.call_method("get_types"), **obj.get_params()) + + def __init__(self, *args, **kwargs): + if "sip" in kwargs: + super().__init__(**kwargs) + return + if len(args): + _type1, _type2 = args + else: + _type1, _type2 = kwargs.pop("_types") 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.") + super().__init__(_types=[_type1, _type2], **kwargs) # Here, add one line for each nonbonded ia IF LENNARD_JONES: @@ -1578,40 +1672,78 @@ class NonBondedInteractionHandle: IF THOLE: self.thole = TholeInteraction(_type1, _type2) + def _serialize(self): + serialized = [] + for name, obj in self.get_params().items(): + serialized.append((name, *obj._serialize())) + return serialized + -cdef class NonBondedInteractions: +@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. - Also: access to force capping. """ + _so_name = "Interactions::NonBondedInteractions" + _so_creation_policy = "GLOBAL" + # _so_bind_methods = ("reset",) - 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( + 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.") - return NonBondedInteractionHandle(key[0], key[1]) + + 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): - cdef string core_state - core_state = ia_params_get_state() - return core_state + cdef string core_state = ia_params_get_state() + 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, "core_state": core_state} - def __setstate__(self, core_state): - cdef string state = core_state - ia_params_set_state(state) + def __setstate__(self, params): + for types, kwargs in params["state"]: + objects = {} + for name, class_name, obj_params in kwargs: + objects[name] = globals()[class_name](**obj_params) + obj = NonBondedInteractionHandle(_types=types, **objects) + self.call_method("insert", key=types, object=obj) def reset(self): """ Reset all interaction parameters to their default values. """ - reset_ia_params() + self.call_method("reset") + + @classmethod + def _restore_object(cls, so_callback, so_callback_args, state): + cdef string core_state = state["core_state"] + ia_params_set_state(core_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 BondedInteraction(ScriptInterfaceHelper): 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 5aace50f5a3..21717a46db1 100644 --- a/src/script_interface/Context.hpp +++ b/src/script_interface/Context.hpp @@ -81,6 +81,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 d4de6ebbc02..53ac78cf88a 100644 --- a/src/script_interface/GlobalContext.hpp +++ b/src/script_interface/GlobalContext.hpp @@ -163,6 +163,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 dcb81be5edc..c12047f9002 100644 --- a/src/script_interface/LocalContext.hpp +++ b/src/script_interface/LocalContext.hpp @@ -69,6 +69,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/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp new file mode 100644 index 00000000000..f810ec3ab02 --- /dev/null +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -0,0 +1,199 @@ +/* + * 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 + +namespace ScriptInterface { +namespace Interactions { + +template +class InteractionPotentialInterface + : public AutoParameters> { + std::array m_types = {-1, -1}; + +public: + using CoreInteraction = CoreIA; + +protected: + using AutoParameters>::context; + std::shared_ptr m_ia_si; + virtual CoreInteraction IA_parameters::*get_ptr_offset() const = 0; + virtual void make_new_instance(VariantMap const ¶ms) = 0; + + template + auto make_autoparameter(T CoreInteraction::*ptr, char const *name) { + return AutoParameter{name, AutoParameter::read_only, + [this, ptr]() { return m_ia_si.get()->*ptr; }}; + } + +public: + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "set_params") { + context()->parallel_try_catch( + [this, ¶ms]() { make_new_instance(params); }); + if (m_types[0] != -1) { + copy_si_to_core(); + on_non_bonded_ia_change(); + } + return {}; + } + 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.empty()) { + m_ia_si = std::make_shared(); + } else if (params.count("_types") != 0) { + do_call_method("bind_types", params); + m_ia_si = std::make_shared(); + copy_core_to_si(); + } else { + context()->parallel_try_catch( + [this, ¶ms]() { make_new_instance(params); }); + } + } + + auto const &get_ia() const { return *m_ia_si; } + + 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; + ::old_nonbonded_ia_params[key].*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(); + } +}; + +class NonBondedInteractionHandle + : public AutoParameters { + std::array m_types = {-1, -1}; + std::shared_ptr<::IA_parameters> m_interaction; + + 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({ + }); + } + +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]; + } + + 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..0817210c4cf --- /dev/null +++ b/src/script_interface/interactions/NonBondedInteractions.hpp @@ -0,0 +1,128 @@ +/* + * 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>(); + ::old_nonbonded_ia_params[key] = ::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; + { + // when reloading from a checkpoint file, need to resize IA lists + auto const new_size = ::max_seen_particle_type; + auto const n_pairs = new_size * (new_size + 1) / 2; + ::old_nonbonded_ia_params.resize(n_pairs); + ::nonbonded_ia_params.resize(n_pairs); + for (auto &ia_params : ::nonbonded_ia_params) { + if (ia_params == nullptr) { + ia_params = std::make_shared<::IA_parameters>(); + } + } + } + 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")); + 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; + ::old_nonbonded_ia_params[key] = *(obj_ptr->get_ia()); + 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/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 77418ae1bff..47e9c8adaf7 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,11 @@ 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"); } } // namespace Interactions } // namespace ScriptInterface diff --git a/src/script_interface/tests/LocalContext_test.cpp b/src/script_interface/tests/LocalContext_test.cpp index 60a8ed88df8..d678012c68e 100644 --- a/src/script_interface/tests/LocalContext_test.cpp +++ b/src/script_interface/tests/LocalContext_test.cpp @@ -70,6 +70,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 ed24a0ec4a2..06e96a97aa3 100644 --- a/src/script_interface/tests/ObjectHandle_test.cpp +++ b/src/script_interface/tests/ObjectHandle_test.cpp @@ -188,6 +188,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"; @@ -247,7 +251,9 @@ 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"); static_cast(log_ctx->parallel_try_catch([]() {})); } diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index c0d236b7b0f..b4f9d8cdeb5 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -24,7 +24,7 @@ import espressomd.interactions -class Non_bonded_interactionsTests(ut.TestCase): +class Test(ut.TestCase): system = espressomd.System(box_l=[30.0, 30.0, 30.0]) def tearDown(self): @@ -162,6 +162,33 @@ def func(self): "k2": 5.0, "mu": 2.0, "nu": 1.0}, "gay_berne") + @utx.skipIfMissingFeatures(["LENNARD_JONES", "WCA"]) + def test_set_params(self): + 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(), + {'shift': 0., 'sigma': 0., 'epsilon': 0., + 'cutoff': -1., 'offset': 0., 'min': 0.}) + wca = espressomd.interactions.WCAInteraction(epsilon=1., sigma=2.) + wca.set_params(epsilon=2.) + self.assertEqual(wca.get_params()["epsilon"], 2.) + 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.) + wca.set_params(epsilon=3.) + self.assertEqual( + self.system.non_bonded_inter[0, 0].wca.get_params()["epsilon"], 3.) + self.assertEqual(wca.get_params()["epsilon"], 3.) + self.system.non_bonded_inter.reset() + wca.set_params(epsilon=4.) + 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: " @@ -182,6 +209,9 @@ def test_exceptions(self): with self.assertRaisesRegex(ValueError, err_msg_valid): 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, "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 @@ -196,6 +226,13 @@ def test_exceptions(self): self.assertAlmostEqual( lennard_jones.get_params()['cutoff'], wrong_cutoff, delta=1e-10) + def check_potential_exceptions(self, ia_class, params, check_keys): + for key in check_keys: + with self.assertRaisesRegex(ValueError, f"parameter '{key}'"): + invalid_params = params.copy() + invalid_params[key] = -0.1 + ia_class(**invalid_params) + if __name__ == "__main__": ut.main() diff --git a/testsuite/python/save_checkpoint.py b/testsuite/python/save_checkpoint.py index 09ebda6f002..aef41515bc5 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -233,6 +233,10 @@ epsilon=1.2, sigma=1.7, cutoff=2.0, shift=0.1) system.non_bonded_inter[1, 17].lennard_jones.set_params( epsilon=1.2e5, sigma=1.7, cutoff=2.0, shift=0.1) + # add inactive interaction + system.non_bonded_inter[4, 4].lennard_jones.set_params( + epsilon=1.4, sigma=1.2, cutoff=1.5, shift=0.2, offset=0.1, min=0.2) + system.non_bonded_inter[4, 4].lennard_jones.set_params(cutoff=-2.) # bonded interactions harmonic_bond = espressomd.interactions.HarmonicBond(r_0=0.0, k=1.0) diff --git a/testsuite/python/test_checkpoint.py b/testsuite/python/test_checkpoint.py index b9f7d953ae9..0aaba75e277 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -333,12 +333,16 @@ def test_integrator_SDM(self): def test_non_bonded_inter(self): params1 = system.non_bonded_inter[0, 0].lennard_jones.get_params() params2 = system.non_bonded_inter[3, 0].lennard_jones.get_params() + params3 = system.non_bonded_inter[4, 4].lennard_jones.get_params() reference1 = {'shift': 0.1, 'sigma': 1.3, 'epsilon': 1.2, 'cutoff': 2.0, 'offset': 0.0, 'min': 0.0} reference2 = {'shift': 0.1, 'sigma': 1.7, 'epsilon': 1.2, 'cutoff': 2.0, 'offset': 0.0, 'min': 0.0} + reference3 = {'shift': 0.2, 'sigma': 1.2, 'epsilon': 1.4, + 'cutoff': -2.0, 'offset': 0.1, 'min': 0.2} self.assertEqual(params1, reference1) self.assertEqual(params2, reference2) + self.assertEqual(params3, reference3) def test_bonded_inter(self): # check the ObjectHandle was correctly initialized (including MPI) From a1ea710d3b244335f388156b06bfe9f3c583ab3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Fri, 19 Aug 2022 00:01:53 +0200 Subject: [PATCH 02/23] Rewrite WCA interaction interface --- .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 3 + src/core/nonbonded_interactions/wca.cpp | 27 ++++---- src/core/nonbonded_interactions/wca.hpp | 2 - src/python/espressomd/interactions.pxd | 12 ---- src/python/espressomd/interactions.pyx | 63 ++++++------------- .../interactions/NonBondedInteraction.hpp | 41 ++++++++++++ .../interactions/initialize.cpp | 3 + .../interactions_non-bonded_interface.py | 7 +++ 9 files changed, 87 insertions(+), 73 deletions(-) diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 8d782caee1e..70540c32b9d 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -141,7 +141,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index fd85c9bbb3a..2a4c971a382 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -55,6 +55,9 @@ struct WCA_Parameters { double eps = 0.0; double sig = 0.0; double cut = INACTIVE_CUTOFF; + WCA_Parameters() = default; + WCA_Parameters(double eps, double sig); + double max_cutoff() const { return cut; } }; /** Generic Lennard-Jones with shift */ diff --git a/src/core/nonbonded_interactions/wca.cpp b/src/core/nonbonded_interactions/wca.cpp index 29a4a2d14dd..389bc65544a 100644 --- a/src/core/nonbonded_interactions/wca.cpp +++ b/src/core/nonbonded_interactions/wca.cpp @@ -23,24 +23,21 @@ #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 eps, double sig) : eps{eps}, sig{sig} { + if (eps < 0.) { + throw std::domain_error("WCA parameter 'epsilon' has to be >= 0"); + } + if (sig < 0.) { + throw std::domain_error("WCA parameter 'sigma' has to be >= 0"); + } + if (sig != 0.) { + cut = sig * 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 6c91b291afb..452496c8d3f 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/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 88b3a4b5b07..d96572265a2 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -47,11 +47,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": double offset double min - cdef struct WCA_Parameters: - double eps - double sig - double cut - cdef struct LJGen_Parameters: double eps double sig @@ -154,8 +149,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": cdef struct IA_parameters: LJ_Parameters lj - WCA_Parameters wca - LJcos_Parameters ljcos LJcos2_Parameters ljcos2 @@ -199,11 +192,6 @@ IF LENNARD_JONES: 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, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 73b4dac197c..1521b3218a9 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -389,57 +389,32 @@ IF LENNARD_JONES == 1: 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) + @script_interface_register + class WCAInteraction(NewNonBondedInteraction): + """ + Standard 6-12 Weeks-Chandler-Andersen potential. - def set_params(self, **kwargs): - """Set parameters for the WCA 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. - """ - 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") + _so_name = "Interactions::InteractionWCA" + + def is_active(self): + return self.epsilon > 0. def default_params(self): """Python dictionary of default parameters. @@ -465,6 +440,10 @@ IF WCA == 1: """ return {"epsilon", "sigma"} + @property + def cutoff(self): + return self.call_method("get_cutoff") + IF LENNARD_JONES_GENERIC == 1: cdef class GenericLennardJonesInteraction(NonBondedInteraction): @@ -1637,8 +1616,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): # 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: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index f810ec3ab02..aff6d6de50d 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -128,10 +128,44 @@ class InteractionPotentialInterface } }; +#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"), + }); + } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "epsilon", "sigma"); + } + + 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 + 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 template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -150,6 +184,9 @@ class NonBondedInteractionHandle public: NonBondedInteractionHandle() { add_parameters({ +#ifdef WCA + make_autoparameter(m_wca, "wca"), +#endif }); } @@ -188,6 +225,10 @@ class NonBondedInteractionHandle // 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 } auto get_ia() const { return m_interaction; } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 47e9c8adaf7..08fd863cbc3 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -56,6 +56,9 @@ void initialize(Utils::Factory *om) { "Interactions::NonBondedInteractions"); om->register_new( "Interactions::NonBondedInteractionHandle"); +#ifdef WCA + om->register_new("Interactions::InteractionWCA"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index b4f9d8cdeb5..8a60fb48e02 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -233,6 +233,13 @@ def check_potential_exceptions(self, ia_class, params, check_keys): invalid_params[key] = -0.1 ia_class(**invalid_params) + @utx.skipIfMissingFeatures("WCA") + def test_wca_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.WCAInteraction, + {"epsilon": 1., "sigma": 1.}, + ("epsilon", "sigma")) + if __name__ == "__main__": ut.main() From f56f5f358f20685127cb375f6faf84f3e1c806b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Fri, 19 Aug 2022 14:08:03 +0200 Subject: [PATCH 03/23] Rewrite LJ interaction interface --- src/core/nonbonded_interactions/lj.cpp | 40 +++++----- src/core/nonbonded_interactions/lj.hpp | 4 - .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 5 ++ .../EspressoSystemStandAlone_test.cpp | 22 +++++- src/core/unit_tests/Verlet_list_test.cpp | 16 +++- src/python/espressomd/interactions.pxd | 16 ---- src/python/espressomd/interactions.pyx | 77 +++++-------------- .../interactions/NonBondedInteraction.hpp | 47 +++++++++++ .../interactions/initialize.cpp | 3 + .../interactions_non-bonded_interface.py | 9 ++- 11 files changed, 138 insertions(+), 103 deletions(-) diff --git a/src/core/nonbonded_interactions/lj.cpp b/src/core/nonbonded_interactions/lj.cpp index ebc988949be..f915e4acfba 100644 --- a/src/core/nonbonded_interactions/lj.cpp +++ b/src/core/nonbonded_interactions/lj.cpp @@ -25,31 +25,33 @@ #include "lj.hpp" #ifdef LENNARD_JONES -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#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); +#include +#include - 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 eps, double sig, double cut, double offset, + double min) + : LJ_Parameters(eps, sig, cut, offset, min, 0.) { + if (cut != 0.) { + auto const sig_cut = sig / cut; + shift = Utils::int_pow<6>(sig_cut) - Utils::int_pow<12>(sig_cut); } - /* broadcast interaction parameters */ - mpi_bcast_ia_params(part_type_a, part_type_b); +} - return ES_OK; +LJ_Parameters::LJ_Parameters(double eps, double sig, double cut, double offset, + double min, double shift) + : eps{eps}, sig{sig}, cut{cut}, shift{shift}, offset{offset}, min{std::max( + min, + 0.)} { + if (eps < 0.) { + throw std::domain_error("LJ parameter 'epsilon' has to be >= 0"); + } + if (sig < 0.) { + throw std::domain_error("LJ parameter 'sigma' has to be >= 0"); + } } #endif /* ifdef LENNARD_JONES */ diff --git a/src/core/nonbonded_interactions/lj.hpp b/src/core/nonbonded_interactions/lj.hpp index 0cbee73cee2..b6651943e72 100644 --- a/src/core/nonbonded_interactions/lj.hpp +++ b/src/core/nonbonded_interactions/lj.hpp @@ -37,10 +37,6 @@ #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) { diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 70540c32b9d..10d33891b5e 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -137,7 +137,7 @@ 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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index 2a4c971a382..1c3f68a0ea8 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -48,6 +48,11 @@ struct LJ_Parameters { double shift = 0.0; double offset = 0.0; double min = 0.0; + LJ_Parameters() = default; + LJ_Parameters(double eps, double sig, double cut, double offset, double min); + LJ_Parameters(double eps, double sig, double cut, double offset, double min, + double shift); + double max_cutoff() const { return cut + offset; } }; /** WCA potential */ diff --git a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp index 94cbc51a24d..224f3703cd5 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" @@ -126,6 +127,18 @@ 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; + ::old_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(); @@ -219,9 +232,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 ec9f840f13d..d006d5b9439 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" @@ -143,8 +144,19 @@ 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; + ::old_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(); } @@ -188,7 +200,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 index d96572265a2..6fd9f585b22 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -39,14 +39,6 @@ cdef extern from "TabulatedPotential.hpp": 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 LJGen_Parameters: double eps double sig @@ -147,7 +139,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": double pref cdef struct IA_parameters: - LJ_Parameters lj LJcos_Parameters ljcos @@ -185,13 +176,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": 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 LJCOS: cdef extern from "nonbonded_interactions/ljcos.hpp": cdef int ljcos_set_params(int part_type_a, int part_type_b, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 1521b3218a9..2bb2d5aff61 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -286,48 +286,21 @@ class NewNonBondedInteraction(ScriptInterfaceHelper): 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) + @script_interface_register + class LennardJonesInteraction(NewNonBondedInteraction): + """ + Standard 6-12 Lennard-Jones potential. - def set_params(self, **kwargs): - """Set parameters for the Lennard-Jones 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` @@ -343,25 +316,15 @@ IF LENNARD_JONES == 1: 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") + _so_name = "Interactions::InteractionLJ" + + def is_active(self): + """Check if interaction is active. + + """ + return self.epsilon > 0. def default_params(self): """Python dictionary of default parameters. @@ -1614,8 +1577,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): super().__init__(_types=[_type1, _type2], **kwargs) # Here, add one line for each nonbonded ia - IF LENNARD_JONES: - self.lennard_jones = LennardJonesInteraction(_type1, _type2) IF SOFT_SPHERE: self.soft_sphere = SoftSphereInteraction(_type1, _type2) IF LENNARD_JONES_GENERIC: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index aff6d6de50d..7b961cfa2ad 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -159,6 +159,43 @@ class InteractionWCA : public InteractionPotentialInterface<::WCA_Parameters> { }; #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"), + }); + } + + void make_new_instance(VariantMap const ¶ms) override { + if (auto const *shift = boost::get(¶ms.at("shift"))) { + if (*shift != "auto") { + throw std::invalid_argument( + "LJ parameter 'shift' has to be 'auto' or a float"); + } + m_ia_si = make_shared_from_args( + params, "epsilon", "sigma", "cutoff", "offset", "min"); + } else { + m_ia_si = make_shared_from_args( + params, "epsilon", "sigma", "cutoff", "offset", "min", "shift"); + } + } +}; +#endif // LENNARD_JONES + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -166,6 +203,9 @@ class NonBondedInteractionHandle #ifdef WCA std::shared_ptr m_wca; #endif +#ifdef LENNARD_JONES + std::shared_ptr m_lj; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -186,6 +226,9 @@ class NonBondedInteractionHandle add_parameters({ #ifdef WCA make_autoparameter(m_wca, "wca"), +#endif +#ifdef LENNARD_JONES + make_autoparameter(m_lj, "lennard_jones"), #endif }); } @@ -228,6 +271,10 @@ class NonBondedInteractionHandle #ifdef WCA set_member(m_wca, "wca", "Interactions::InteractionWCA", params); +#endif +#ifdef LENNARD_JONES + set_member(m_lj, "lennard_jones", + "Interactions::InteractionLJ", params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 08fd863cbc3..03bba64c9ea 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -56,6 +56,9 @@ void initialize(Utils::Factory *om) { "Interactions::NonBondedInteractions"); om->register_new( "Interactions::NonBondedInteractionHandle"); +#ifdef LENNARD_JONES + om->register_new("Interactions::InteractionLJ"); +#endif #ifdef WCA om->register_new("Interactions::InteractionWCA"); #endif diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 8a60fb48e02..82a45ab8e50 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -209,7 +209,7 @@ def test_exceptions(self): with self.assertRaisesRegex(ValueError, err_msg_valid): 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, "Parameter 'shift' has to be 'auto' or a float"): + 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") @@ -240,6 +240,13 @@ def test_wca_exceptions(self): {"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")) + if __name__ == "__main__": ut.main() From c817352d7be69ce9548f187396a07029ac7ed550 Mon Sep 17 00:00:00 2001 From: Julian Hossbach Date: Wed, 24 Aug 2022 14:10:45 +0200 Subject: [PATCH 04/23] Rewrite LJcos interaction interface --- src/core/nonbonded_interactions/ljcos.cpp | 39 +++++------- src/core/nonbonded_interactions/ljcos.hpp | 4 -- .../nonbonded_interaction_data.cpp | 3 +- .../nonbonded_interaction_data.hpp | 3 + src/python/espressomd/interactions.pxd | 14 ----- src/python/espressomd/interactions.pyx | 61 +++++++------------ .../interactions/NonBondedInteraction.hpp | 36 +++++++++++ .../interactions/initialize.cpp | 3 + .../interactions_non-bonded_interface.py | 7 +++ 9 files changed, 88 insertions(+), 82 deletions(-) diff --git a/src/core/nonbonded_interactions/ljcos.cpp b/src/core/nonbonded_interactions/ljcos.cpp index 78c29d28a80..ba33702b446 100644 --- a/src/core/nonbonded_interactions/ljcos.cpp +++ b/src/core/nonbonded_interactions/ljcos.cpp @@ -25,34 +25,27 @@ #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 eps, double sig, double cut, + double offset) + : eps{eps}, sig{sig}, cut{cut}, offset{offset} { + if (eps < 0.) { + throw std::domain_error("LJcos parameter 'epsilon' has to be >= 0"); + } + if (sig < 0.) { + throw std::domain_error("LJcos parameter 'sigma' 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 342c08c99c6..b75ce5544de 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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 10d33891b5e..05a49776e63 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -188,8 +188,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index 1c3f68a0ea8..30b60202909 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -159,6 +159,9 @@ struct LJcos_Parameters { double alfa = 0.0; double beta = 0.0; double rmin = 0.0; + LJcos_Parameters() = default; + LJcos_Parameters(double eps, double sig, double cut, double offset); + double max_cutoff() const { return cut + offset; } }; /** Lennard-Jones with a different Cos potential */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 6fd9f585b22..99a3c1cb61a 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -106,12 +106,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": double Fmax double r - cdef struct LJcos_Parameters: - double eps - double sig - double cut - double offset - cdef struct LJcos2_Parameters: double eps double sig @@ -140,8 +134,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": cdef struct IA_parameters: - LJcos_Parameters ljcos - LJcos2_Parameters ljcos2 LJGen_Parameters ljgen @@ -176,12 +168,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": cdef void ia_params_set_state(string) cdef void reset_ia_params() -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, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 2bb2d5aff61..6753eb86e34 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -563,37 +563,22 @@ IF LENNARD_JONES_GENERIC == 1: return {"epsilon", "sigma", "cutoff", "shift", "offset", "e1", "e2", "b1", "b2"} -IF LJCOS: +IF LJCOS == 1: - 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) + @script_interface_register + class LennardJonesCosInteraction(NewNonBondedInteraction): + """Lennard-Jones cosine interaction. - def set_params(self, **kwargs): - """Set parameters for the 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` @@ -602,20 +587,20 @@ IF LJCOS: 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") + _so_name = "Interactions::InteractionLJcos" + + def is_active(self): + """Check if interactions is active. + + """ + return self.epsilon > 0. def default_params(self): + """Python dictionary of default parameters. + + """ return {"offset": 0.} def type_name(self): @@ -1582,8 +1567,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): 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) diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 7b961cfa2ad..5e3bb962652 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -196,6 +196,32 @@ class InteractionLJ : public InteractionPotentialInterface<::LJ_Parameters> { }; #endif // LENNARD_JONES +#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"), + }); + } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = + make_shared_from_args( + params, "epsilon", "sigma", "cutoff", "offset"); + } +}; +#endif // LJCOS + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -206,6 +232,9 @@ class NonBondedInteractionHandle #ifdef LENNARD_JONES std::shared_ptr m_lj; #endif +#ifdef LJCOS + std::shared_ptr m_ljcos; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -229,6 +258,9 @@ class NonBondedInteractionHandle #endif #ifdef LENNARD_JONES make_autoparameter(m_lj, "lennard_jones"), +#endif +#ifdef LJCOS + make_autoparameter(m_ljcos, "lennard_jones_cos"), #endif }); } @@ -275,6 +307,10 @@ class NonBondedInteractionHandle #ifdef LENNARD_JONES set_member(m_lj, "lennard_jones", "Interactions::InteractionLJ", params); +#endif +#ifdef LJCOS + set_member(m_ljcos, "lennard_jones_cos", + "Interactions::InteractionLJcos", params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 03bba64c9ea..2c5e0e60639 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -59,6 +59,9 @@ void initialize(Utils::Factory *om) { #ifdef LENNARD_JONES om->register_new("Interactions::InteractionLJ"); #endif +#ifdef LJCOS + om->register_new("Interactions::InteractionLJcos"); +#endif #ifdef WCA om->register_new("Interactions::InteractionWCA"); #endif diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 82a45ab8e50..a45d7a08fbe 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -247,6 +247,13 @@ def test_lj_exceptions(self): {"epsilon": 1., "sigma": 1., "cutoff": 1.5, "shift": 0.2}, ("epsilon", "sigma")) + @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")) + if __name__ == "__main__": ut.main() From 587b341721f33ce47bc01c3db32b67be41e85b78 Mon Sep 17 00:00:00 2001 From: Julian Hossbach Date: Fri, 26 Aug 2022 14:18:05 +0200 Subject: [PATCH 05/23] Rewrite LJcos2 interaction interface --- src/core/nonbonded_interactions/ljcos2.cpp | 39 ++++------- src/core/nonbonded_interactions/ljcos2.hpp | 4 -- .../nonbonded_interaction_data.cpp | 3 +- .../nonbonded_interaction_data.hpp | 3 + src/python/espressomd/interactions.pxd | 14 ---- src/python/espressomd/interactions.pyx | 69 ++++++++----------- .../interactions/NonBondedInteraction.hpp | 45 ++++++++++++ .../interactions/initialize.cpp | 3 + .../interactions_non-bonded_interface.py | 7 ++ 9 files changed, 101 insertions(+), 86 deletions(-) diff --git a/src/core/nonbonded_interactions/ljcos2.cpp b/src/core/nonbonded_interactions/ljcos2.cpp index 56237327934..31ccb14196b 100644 --- a/src/core/nonbonded_interactions/ljcos2.cpp +++ b/src/core/nonbonded_interactions/ljcos2.cpp @@ -25,33 +25,24 @@ #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 eps, double sig, double offset, + double w) + : eps{eps}, sig{sig}, offset{offset}, w{w} { + if (eps < 0.) { + throw std::domain_error("LJcos2 parameter 'epsilon' has to be >= 0"); + } + if (sig < 0.) { + throw std::domain_error("LJcos2 parameter 'sigma' has to be >= 0"); + } + if (sig != 0.) { + rchange = std::pow(2., 1. / 6.) * sig; + cut = w + rchange; + } } #endif /* ifdef LJCOS2 */ diff --git a/src/core/nonbonded_interactions/ljcos2.hpp b/src/core/nonbonded_interactions/ljcos2.hpp index 3df298586fd..b04f6c64d88 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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 05a49776e63..b2cf2747d14 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -192,8 +192,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index 30b60202909..7e807f66d70 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -172,6 +172,9 @@ struct LJcos2_Parameters { double offset = 0.0; double w = 0.0; double rchange = 0.0; + LJcos2_Parameters() = default; + LJcos2_Parameters(double eps, double sig, double offset, double w); + double max_cutoff() const { return cut + offset; } }; /** Gay-Berne potential */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 99a3c1cb61a..926a8c782ba 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -106,12 +106,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": double Fmax double r - cdef struct LJcos2_Parameters: - double eps - double sig - double offset - double w - cdef struct GayBerne_Parameters: double eps double sig @@ -134,8 +128,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": cdef struct IA_parameters: - LJcos2_Parameters ljcos2 - LJGen_Parameters ljgen SoftSphere_Parameters soft_sphere @@ -168,12 +160,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": cdef void ia_params_set_state(string) cdef void reset_ia_params() -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, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 6753eb86e34..c21cafb3fe2 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -621,38 +621,22 @@ IF LJCOS == 1: """ return {"epsilon", "sigma", "cutoff"} -IF LJCOS2: +IF LJCOS2 == 1: - 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) + @script_interface_register + class LennardJonesCos2Interaction(NewNonBondedInteraction): + """Second variant of the Lennard-Jones cosine interaction. - def set_params(self, **kwargs): - """Set parameters for the Lennard-Jones cosine squared 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` @@ -661,20 +645,20 @@ IF LJCOS2: 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") + _so_name = "Interactions::InteractionLJcos2" + + def is_active(self): + """Check if interactions is active. + + """ + return self.epsilon > 0. def default_params(self): + """Python dictionary of default parameters. + + """ return {"offset": 0.} def type_name(self): @@ -687,7 +671,7 @@ IF LJCOS2: """All parameters that can be set. """ - return {"epsilon", "sigma", "offset", "width"} + return {"epsilon", "sigma", "width", "offset"} def required_keys(self): """Parameters that have to be set. @@ -695,6 +679,10 @@ IF LJCOS2: """ return {"epsilon", "sigma", "width"} + @property + def cutoff(self): + return self.call_method("get_cutoff") + IF HAT == 1: cdef class HatInteraction(NonBondedInteraction): @@ -1567,9 +1555,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): IF LENNARD_JONES_GENERIC: self.generic_lennard_jones = GenericLennardJonesInteraction( _type1, _type2) - IF LJCOS2: - self.lennard_jones_cos2 = LennardJonesCos2Interaction( - _type1, _type2) IF SMOOTH_STEP: self.smooth_step = SmoothStepInteraction(_type1, _type2) IF BMHTF_NACL: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 5e3bb962652..f78c09e6f9f 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -222,6 +222,41 @@ class InteractionLJcos }; #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"), + }); + } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = + make_shared_from_args( + params, "epsilon", "sigma", "offset", "width"); + } + + 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 + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -235,6 +270,9 @@ class NonBondedInteractionHandle #ifdef LJCOS std::shared_ptr m_ljcos; #endif +#ifdef LJCOS2 + std::shared_ptr m_ljcos2; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -261,6 +299,9 @@ class NonBondedInteractionHandle #endif #ifdef LJCOS make_autoparameter(m_ljcos, "lennard_jones_cos"), +#endif +#ifdef LJCOS2 + make_autoparameter(m_ljcos2, "lennard_jones_cos2"), #endif }); } @@ -311,6 +352,10 @@ class NonBondedInteractionHandle #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 } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 2c5e0e60639..1e63b0136c1 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -62,6 +62,9 @@ void initialize(Utils::Factory *om) { #ifdef LJCOS om->register_new("Interactions::InteractionLJcos"); #endif +#ifdef LJCOS2 + om->register_new("Interactions::InteractionLJcos2"); +#endif #ifdef WCA om->register_new("Interactions::InteractionWCA"); #endif diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index a45d7a08fbe..c275e338973 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -254,6 +254,13 @@ def test_lj_cos_exceptions(self): {"epsilon": 1., "sigma": 1., "cutoff": 1.5, "offset": 0.2}, ("epsilon", "sigma")) + @utx.skipIfMissingFeatures("LJCOS2") + def test_lj_cos2_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.LennardJonesCos2Interaction, + {"epsilon": 1., "sigma": 1., "width": 1.5, "offset": 0.2}, + ("epsilon", "sigma")) + if __name__ == "__main__": ut.main() From 9cdf1d98b8dcbbdb9e525a3af9e7d4f1a1de8c5d Mon Sep 17 00:00:00 2001 From: Julian Hossbach Date: Tue, 30 Aug 2022 11:36:16 +0200 Subject: [PATCH 06/23] Rewrite Gaussian interaction interface --- src/core/nonbonded_interactions/gaussian.cpp | 29 +++----- src/core/nonbonded_interactions/gaussian.hpp | 4 -- .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 3 + src/python/espressomd/interactions.pxd | 12 ---- src/python/espressomd/interactions.pyx | 69 ++++++------------- .../interactions/NonBondedInteraction.hpp | 33 +++++++++ .../interactions/initialize.cpp | 3 + .../interactions_non-bonded_interface.py | 8 +++ 9 files changed, 79 insertions(+), 84 deletions(-) diff --git a/src/core/nonbonded_interactions/gaussian.cpp b/src/core/nonbonded_interactions/gaussian.cpp index 51643bcfa27..97f0e8c20aa 100644 --- a/src/core/nonbonded_interactions/gaussian.cpp +++ b/src/core/nonbonded_interactions/gaussian.cpp @@ -25,26 +25,17 @@ #include "gaussian.hpp" #ifdef GAUSSIAN -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include +#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; +Gaussian_Parameters::Gaussian_Parameters(double eps, double sig, double cut) + : eps{eps}, sig{sig}, cut{cut} { + 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"); + } } - -#endif +#endif // GAUSSIAN diff --git a/src/core/nonbonded_interactions/gaussian.hpp b/src/core/nonbonded_interactions/gaussian.hpp index fd004b13cc6..71f7daa369b 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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index b2cf2747d14..fa1bf31c599 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -163,7 +163,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index 7e807f66d70..cecab1f511b 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -101,6 +101,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 cut); + double max_cutoff() const { return cut; } }; /** BMHTF NaCl potential */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 926a8c782ba..f2cdbfd9c72 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -64,11 +64,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": double eps double sig - cdef struct Gaussian_Parameters: - double eps - double sig - double cut - cdef struct BMHTF_Parameters: double A double B @@ -146,8 +141,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": Hertzian_Parameters hertzian - Gaussian_Parameters gaussian - DPDParameters dpd_radial DPDParameters dpd_trans @@ -219,11 +212,6 @@ IF HERTZIAN: 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, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index c21cafb3fe2..b3e008f03f7 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -1442,44 +1442,35 @@ IF HERTZIAN == 1: IF GAUSSIAN == 1: - cdef class GaussianInteraction(NonBondedInteraction): + @script_interface_register + class GaussianInteraction(NewNonBondedInteraction): + """Gaussian interaction. - def validate_params(self): - """Check that parameters are valid. + 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. - """ - 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") + Parameters + ---------- + eps : :obj:`float` + Overlap energy epsilon. + sig : :obj:`float` + Variance sigma of the Gaussian interaction. + cutoff : :obj:`float` + Cutoff distance of the interaction. + """ - 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 - } + _so_name = "Interactions::InteractionGaussian" 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") + return self.eps > 0. def default_params(self): """Python dictionary of default parameters. @@ -1493,22 +1484,6 @@ IF GAUSSIAN == 1: """ 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. @@ -1565,8 +1540,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): 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: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index f78c09e6f9f..704f35c97eb 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -257,6 +257,29 @@ class InteractionLJcos2 }; #endif // LJCOS2 +#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"), + }); + } + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "eps", "sig", "cutoff"); + } +}; +#endif // GAUSSIAN + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -273,6 +296,9 @@ class NonBondedInteractionHandle #ifdef LJCOS2 std::shared_ptr m_ljcos2; #endif +#ifdef GAUSSIAN + std::shared_ptr m_gaussian; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -302,6 +328,9 @@ class NonBondedInteractionHandle #endif #ifdef LJCOS2 make_autoparameter(m_ljcos2, "lennard_jones_cos2"), +#endif +#ifdef GAUSSIAN + make_autoparameter(m_gaussian, "gaussian"), #endif }); } @@ -356,6 +385,10 @@ class NonBondedInteractionHandle #ifdef LJCOS2 set_member(m_ljcos2, "lennard_jones_cos2", "Interactions::InteractionLJcos2", params); +#endif +#ifdef GAUSSIAN + set_member( + m_gaussian, "gaussian", "Interactions::InteractionGaussian", params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 1e63b0136c1..507858d0515 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -68,6 +68,9 @@ void initialize(Utils::Factory *om) { #ifdef WCA om->register_new("Interactions::InteractionWCA"); #endif +#ifdef GAUSSIAN + om->register_new("Interactions::InteractionGaussian"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index c275e338973..a22311dffc6 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -261,6 +261,14 @@ def test_lj_cos2_exceptions(self): {"epsilon": 1., "sigma": 1., "width": 1.5, "offset": 0.2}, ("epsilon", "sigma")) + @utx.skipIfMissingFeatures("GAUSSIAN") + def test_gaussian_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.GaussianInteraction, + {"eps": 1., "sig": 1., "cutoff": 1.5}, + ("eps", "sig") + ) + if __name__ == "__main__": ut.main() From a42a5864065ba2bc6c86b9685e8421815d9bda0a Mon Sep 17 00:00:00 2001 From: Julian Hossbach Date: Tue, 30 Aug 2022 16:14:18 +0200 Subject: [PATCH 07/23] Rewrite Hertzian interaction interface --- src/core/nonbonded_interactions/hertzian.cpp | 24 +++---- src/core/nonbonded_interactions/hertzian.hpp | 5 -- .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 3 + src/python/espressomd/interactions.pxd | 11 ---- src/python/espressomd/interactions.pyx | 62 ++++++------------- .../interactions/NonBondedInteraction.hpp | 32 ++++++++++ .../interactions/initialize.cpp | 3 + .../interactions_non-bonded_interface.py | 8 +++ 9 files changed, 73 insertions(+), 77 deletions(-) diff --git a/src/core/nonbonded_interactions/hertzian.cpp b/src/core/nonbonded_interactions/hertzian.cpp index 025f513b9e5..4b3439f4982 100644 --- a/src/core/nonbonded_interactions/hertzian.cpp +++ b/src/core/nonbonded_interactions/hertzian.cpp @@ -25,25 +25,15 @@ #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"); + } } -#endif +#endif // HERTZIAN diff --git a/src/core/nonbonded_interactions/hertzian.hpp b/src/core/nonbonded_interactions/hertzian.hpp index 6f45ce363d7..9b3698917a3 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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index fa1bf31c599..68ca1341910 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -159,7 +159,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index cecab1f511b..c48b4823a82 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -94,6 +94,9 @@ struct SmoothStep_Parameters { 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 */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index f2cdbfd9c72..53edb5645c4 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -60,10 +60,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": int n double k0 - cdef struct Hertzian_Parameters: - double eps - double sig - cdef struct BMHTF_Parameters: double A double B @@ -139,8 +135,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": Buckingham_Parameters buckingham - Hertzian_Parameters hertzian - DPDParameters dpd_radial DPDParameters dpd_trans @@ -207,11 +201,6 @@ IF SOFT_SPHERE: 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 DPD: cdef extern from "dpd.hpp": int dpd_set_params(int part_type_a, int part_type_b, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index b3e008f03f7..a0c9673495a 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -1364,42 +1364,35 @@ IF SOFT_SPHERE == 1: """ return {"a", "n", "cutoff"} - IF HERTZIAN == 1: - cdef class HertzianInteraction(NonBondedInteraction): + @script_interface_register + class HertzianInteraction(NewNonBondedInteraction): + """Hertzian interaction. - def validate_params(self): - """Check that parameters are valid. + 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. - """ - 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") + Parameters + ---------- + eps : :obj:`float` + Magnitude of the interaction. + sig : :obj:`float` + Interaction length scale. + """ - 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 - } + _so_name = "Interactions::InteractionHertzian" 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") + return self.eps > 0. def default_params(self): """Python dictionary of default parameters. @@ -1413,21 +1406,6 @@ IF HERTZIAN == 1: """ 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. @@ -1538,8 +1516,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): self.morse = MorseInteraction(_type1, _type2) IF BUCKINGHAM: self.buckingham = BuckinghamInteraction(_type1, _type2) - IF HERTZIAN: - self.hertzian = HertzianInteraction(_type1, _type2) IF TABULATED: self.tabulated = TabulatedNonBonded(_type1, _type2) IF GAY_BERNE: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 704f35c97eb..4929a67f822 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -257,6 +257,28 @@ class InteractionLJcos2 }; #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"), + }); + } + 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> { @@ -296,6 +318,9 @@ class NonBondedInteractionHandle #ifdef LJCOS2 std::shared_ptr m_ljcos2; #endif +#ifdef HERTZIAN + std::shared_ptr m_hertzian; +#endif #ifdef GAUSSIAN std::shared_ptr m_gaussian; #endif @@ -329,6 +354,9 @@ class NonBondedInteractionHandle #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 @@ -386,6 +414,10 @@ class NonBondedInteractionHandle 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); diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 507858d0515..bf1208c89c2 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -68,6 +68,9 @@ void initialize(Utils::Factory *om) { #ifdef WCA om->register_new("Interactions::InteractionWCA"); #endif +#ifdef HERTZIAN + om->register_new("Interactions::InteractionHertzian"); +#endif #ifdef GAUSSIAN om->register_new("Interactions::InteractionGaussian"); #endif diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index a22311dffc6..b128fb2b9f6 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -261,6 +261,14 @@ def test_lj_cos2_exceptions(self): {"epsilon": 1., "sigma": 1., "width": 1.5, "offset": 0.2}, ("epsilon", "sigma")) + @utx.skipIfMissingFeatures("HERTZIAN") + def test_hertzian_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.HertzianInteraction, + {"eps": 1., "sig": 1.}, + ("eps",) + ) + @utx.skipIfMissingFeatures("GAUSSIAN") def test_gaussian_exceptions(self): self.check_potential_exceptions( From 8576bf891fe4259f3081501b1268b8777b4cb7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 5 Sep 2022 22:31:04 +0200 Subject: [PATCH 08/23] Rewrite generic Lennard-Jones interaction interface --- src/core/nonbonded_interactions/ljgen.cpp | 55 +++---- src/core/nonbonded_interactions/ljgen.hpp | 23 +-- .../nonbonded_interaction_data.cpp | 3 +- .../nonbonded_interaction_data.hpp | 20 ++- src/python/espressomd/interactions.pxd | 29 ---- src/python/espressomd/interactions.pyx | 140 +++++------------- .../interactions/NonBondedInteraction.hpp | 65 ++++++++ .../interactions/initialize.cpp | 3 + testsuite/python/interactions_non-bonded.py | 29 ++-- .../interactions_non-bonded_interface.py | 19 +++ 10 files changed, 191 insertions(+), 195 deletions(-) diff --git a/src/core/nonbonded_interactions/ljgen.cpp b/src/core/nonbonded_interactions/ljgen.cpp index 862d0dfef81..51feb1bc448 100644 --- a/src/core/nonbonded_interactions/ljgen.cpp +++ b/src/core/nonbonded_interactions/ljgen.cpp @@ -25,43 +25,36 @@ #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"); + } +#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 b0a734ff514..c753ba1db20 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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 68ca1341910..c2801e512ba 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -150,8 +150,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index c48b4823a82..922638e3b43 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -72,12 +73,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 */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 53edb5645c4..839de598f65 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -39,19 +39,6 @@ cdef extern from "TabulatedPotential.hpp": vector[double] force_tab cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": - 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 @@ -119,8 +106,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": cdef struct IA_parameters: - LJGen_Parameters ljgen - SoftSphere_Parameters soft_sphere TabulatedPotential tab @@ -158,20 +143,6 @@ 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, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index a0c9673495a..9fba81af9a6 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -409,109 +409,18 @@ IF WCA == 1: 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" + @script_interface_register + class GenericLennardJonesInteraction(NewNonBondedInteraction): + """ + Generalized Lennard-Jones potential. - def set_params(self, **kwargs): - """ - Set parameters for the generic Lennard-Jones 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 ---------- @@ -542,8 +451,30 @@ IF LENNARD_JONES_GENERIC == 1: ``LJGEN_SOFTCORE`` parameter lambda. Tune the strength of the interaction. + """ + + _so_name = "Interactions::InteractionLJGen" + + def is_active(self): + """Check if interaction is active. + """ - super().set_params(**kwargs) + return self.epsilon > 0. + + 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 valid_keys(self): """All parameters that can be set. @@ -1505,9 +1436,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): # Here, add one line for each nonbonded ia IF SOFT_SPHERE: self.soft_sphere = SoftSphereInteraction(_type1, _type2) - IF LENNARD_JONES_GENERIC: - self.generic_lennard_jones = GenericLennardJonesInteraction( - _type1, _type2) IF SMOOTH_STEP: self.smooth_step = SmoothStepInteraction(_type1, _type2) IF BMHTF_NACL: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 4929a67f822..7fe6a846c30 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -196,6 +196,61 @@ class InteractionLJ : public InteractionPotentialInterface<::LJ_Parameters> { }; #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"), + }); + } + + 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> { @@ -312,6 +367,9 @@ class NonBondedInteractionHandle #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 @@ -348,6 +406,9 @@ class NonBondedInteractionHandle #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 @@ -406,6 +467,10 @@ class NonBondedInteractionHandle 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); diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index bf1208c89c2..b4359575250 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -59,6 +59,9 @@ void initialize(Utils::Factory *om) { #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 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 b128fb2b9f6..3efde27b40d 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -247,6 +247,25 @@ def test_lj_exceptions(self): {"epsilon": 1., "sigma": 1., "cutoff": 1.5, "shift": 0.2}, ("epsilon", "sigma")) + @utx.skipIfMissingFeatures("LENNARD_JONES_GENERIC") + def test_ljgen_exceptions(self): + check_keys = ("epsilon", "sigma") + 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, f"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( From 53368cc7622c0ea9ca5f83bc209a099a53e96346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 5 Sep 2022 23:37:38 +0200 Subject: [PATCH 09/23] Rewrite BMHTF interaction interface --- .../nonbonded_interactions/bmhtf-nacl.cpp | 47 ++++------ .../nonbonded_interactions/bmhtf-nacl.hpp | 6 +- .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 4 + src/python/espressomd/interactions.pxd | 17 ---- src/python/espressomd/interactions.pyx | 88 ++++++------------- .../interactions/NonBondedInteraction.hpp | 38 ++++++++ .../interactions/initialize.cpp | 3 + .../interactions_non-bonded_interface.py | 8 ++ 9 files changed, 101 insertions(+), 112 deletions(-) diff --git a/src/core/nonbonded_interactions/bmhtf-nacl.cpp b/src/core/nonbonded_interactions/bmhtf-nacl.cpp index 74c2d0df9d0..e89fbf193df 100644 --- a/src/core/nonbonded_interactions/bmhtf-nacl.cpp +++ b/src/core/nonbonded_interactions/bmhtf-nacl.cpp @@ -25,35 +25,26 @@ #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"); + } + 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 c2a5b87d110..91ff2179d20 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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index c2801e512ba..2abad44a70f 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -166,7 +166,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index 922638e3b43..a2fcc9343ed 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -134,6 +134,10 @@ 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 */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 839de598f65..f30569e5f29 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -47,15 +47,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": int n double k0 - 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 @@ -114,8 +105,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": SmoothStep_Parameters smooth_step - BMHTF_Parameters bmhtf - Morse_Parameters morse Buckingham_Parameters buckingham @@ -149,12 +138,6 @@ IF SMOOTH_STEP: 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, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 9fba81af9a6..20ad80c9662 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -938,51 +938,41 @@ IF SMOOTH_STEP == 1: IF BMHTF_NACL == 1: - cdef class BMHTFInteraction(NonBondedInteraction): + @script_interface_register + class BMHTFInteraction(NewNonBondedInteraction): + """BMHTF interaction. - def validate_params(self): - """Check that parameters are valid. + 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. - """ - 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") + 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. + """ - 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, - } + _so_name = "Interactions::InteractionBMHTF" 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") + return self.a > 0. and self.c > 0. and self.d > 0. def default_params(self): """Python dictionary of default parameters. @@ -996,28 +986,6 @@ IF BMHTF_NACL == 1: """ 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. @@ -1438,8 +1406,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): self.soft_sphere = SoftSphereInteraction(_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: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 7fe6a846c30..ccf4569a8bc 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -357,6 +357,34 @@ class InteractionGaussian }; #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"), + }); + } + + 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 + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -382,6 +410,9 @@ class NonBondedInteractionHandle #ifdef GAUSSIAN std::shared_ptr m_gaussian; #endif +#ifdef BMHTF_NACL + std::shared_ptr m_bmhtf; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -420,6 +451,9 @@ class NonBondedInteractionHandle #endif #ifdef GAUSSIAN make_autoparameter(m_gaussian, "gaussian"), +#endif +#ifdef BMHTF_NACL + make_autoparameter(m_bmhtf, "bmhtf"), #endif }); } @@ -486,6 +520,10 @@ class NonBondedInteractionHandle #ifdef GAUSSIAN set_member( m_gaussian, "gaussian", "Interactions::InteractionGaussian", params); +#endif +#ifdef BMHTF_NACL + set_member(m_bmhtf, "bmhtf", + "Interactions::InteractionBMHTF", params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index b4359575250..ab4c766f575 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -77,6 +77,9 @@ void initialize(Utils::Factory *om) { #ifdef GAUSSIAN om->register_new("Interactions::InteractionGaussian"); #endif +#ifdef BMHTF_NACL + om->register_new("Interactions::InteractionBMHTF"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 3efde27b40d..4dea913dc48 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -296,6 +296,14 @@ def test_gaussian_exceptions(self): ("eps", "sig") ) + @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") + ) + if __name__ == "__main__": ut.main() From 6173439832c5578487521c13982ccb04f3ee17cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Tue, 6 Sep 2022 12:48:30 +0200 Subject: [PATCH 10/23] Rewrite Morse interaction interface --- src/core/nonbonded_interactions/morse.cpp | 39 ++++------ src/core/nonbonded_interactions/morse.hpp | 8 +- .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 5 +- src/python/espressomd/interactions.pxd | 15 ---- src/python/espressomd/interactions.pyx | 74 ++++++------------- .../interactions/NonBondedInteraction.hpp | 36 +++++++++ .../interactions/initialize.cpp | 3 + .../interactions_non-bonded_interface.py | 8 ++ 9 files changed, 90 insertions(+), 100 deletions(-) diff --git a/src/core/nonbonded_interactions/morse.cpp b/src/core/nonbonded_interactions/morse.cpp index f31d2335be5..f4465491b54 100644 --- a/src/core/nonbonded_interactions/morse.cpp +++ b/src/core/nonbonded_interactions/morse.cpp @@ -25,33 +25,20 @@ #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"); + } + 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 fab45cfe3a5..41252931b5e 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 2abad44a70f..341bb5cf1d7 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -170,7 +170,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index a2fcc9343ed..9f803b85621 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -142,11 +142,14 @@ struct BMHTF_Parameters { /** 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 */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index f30569e5f29..5523c237e8d 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -47,13 +47,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": int n double k0 - cdef struct Morse_Parameters: - double eps - double alpha - double rmin - double cut - double rest - cdef struct Buckingham_Parameters: double A double B @@ -105,8 +98,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": SmoothStep_Parameters smooth_step - Morse_Parameters morse - Buckingham_Parameters buckingham DPDParameters dpd_radial @@ -138,12 +129,6 @@ IF SMOOTH_STEP: double d, int n, double eps, double k0, 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, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 20ad80c9662..691062413c5 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -1000,45 +1000,37 @@ IF BMHTF_NACL == 1: IF MORSE == 1: - cdef class MorseInteraction(NonBondedInteraction): + @script_interface_register + class MorseInteraction(NewNonBondedInteraction): + """Morse interaction. - def validate_params(self): - """Check that parameters are valid. + 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. - """ - 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") + 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. + """ - 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 - } + _so_name = "Interactions::InteractionMorse" 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") + return self.eps > 0. def default_params(self): """Python dictionary of default parameters. @@ -1052,24 +1044,6 @@ IF MORSE == 1: """ 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. @@ -1406,8 +1380,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): self.soft_sphere = SoftSphereInteraction(_type1, _type2) IF SMOOTH_STEP: self.smooth_step = SmoothStepInteraction(_type1, _type2) - IF MORSE: - self.morse = MorseInteraction(_type1, _type2) IF BUCKINGHAM: self.buckingham = BuckinghamInteraction(_type1, _type2) IF TABULATED: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index ccf4569a8bc..9bbc72ddb6e 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -385,6 +385,32 @@ class InteractionBMHTF }; #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"), + }); + } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = + make_shared_from_args( + params, "eps", "alpha", "rmin", "cutoff"); + } +}; +#endif // MORSE + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -413,6 +439,9 @@ class NonBondedInteractionHandle #ifdef BMHTF_NACL std::shared_ptr m_bmhtf; #endif +#ifdef MORSE + std::shared_ptr m_morse; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -454,6 +483,9 @@ class NonBondedInteractionHandle #endif #ifdef BMHTF_NACL make_autoparameter(m_bmhtf, "bmhtf"), +#endif +#ifdef MORSE + make_autoparameter(m_morse, "morse"), #endif }); } @@ -524,6 +556,10 @@ class NonBondedInteractionHandle #ifdef BMHTF_NACL set_member(m_bmhtf, "bmhtf", "Interactions::InteractionBMHTF", params); +#endif +#ifdef MORSE + set_member(m_morse, "morse", + "Interactions::InteractionMorse", params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index ab4c766f575..57cd45770f7 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -80,6 +80,9 @@ void initialize(Utils::Factory *om) { #ifdef BMHTF_NACL om->register_new("Interactions::InteractionBMHTF"); #endif +#ifdef MORSE + om->register_new("Interactions::InteractionMorse"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 4dea913dc48..631747c2ea9 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -304,6 +304,14 @@ def test_bmhtf_exceptions(self): ("a", "c", "d") ) + @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",) + ) + if __name__ == "__main__": ut.main() From b053cc50a5bdf358897c08626d99634045b71c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Tue, 6 Sep 2022 13:04:46 +0200 Subject: [PATCH 11/23] Rewrite Buckingham interaction interface --- .../nonbonded_interactions/buckingham.cpp | 48 +++++----- .../nonbonded_interactions/buckingham.hpp | 8 +- .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 4 + src/python/espressomd/interactions.pxd | 19 ---- src/python/espressomd/interactions.pyx | 94 ++++++------------- .../interactions/NonBondedInteraction.hpp | 40 ++++++++ .../interactions/initialize.cpp | 4 + .../interactions_non-bonded_interface.py | 9 ++ 9 files changed, 109 insertions(+), 119 deletions(-) diff --git a/src/core/nonbonded_interactions/buckingham.cpp b/src/core/nonbonded_interactions/buckingham.cpp index b8b77565d01..e8febf5ff32 100644 --- a/src/core/nonbonded_interactions/buckingham.cpp +++ b/src/core/nonbonded_interactions/buckingham.cpp @@ -25,38 +25,32 @@ #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"); + } /* 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 33a78a8625c..76fc3b1bd39 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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 341bb5cf1d7..0ba701e8b56 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -174,7 +174,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index 9f803b85621..eed08f6f359 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -163,6 +163,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 */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 5523c237e8d..6d57e62deaf 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -47,17 +47,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": int n double k0 - 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 @@ -98,8 +87,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": SmoothStep_Parameters smooth_step - Buckingham_Parameters buckingham - DPDParameters dpd_radial DPDParameters dpd_trans @@ -129,12 +116,6 @@ IF SMOOTH_STEP: double d, int n, double eps, double k0, double sig, 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, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 691062413c5..d3cdfae0377 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -1058,69 +1058,17 @@ IF MORSE == 1: 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" + @script_interface_register + class BuckinghamInteraction(NewNonBondedInteraction): + """Buckingham interaction. - def set_params(self, **kwargs): - """ - Set parameters for the Buckingham 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 ---------- @@ -1138,9 +1086,27 @@ IF BUCKINGHAM == 1: Cutoff distance of the interaction. shift: :obj:`float`, optional Constant potential shift. + """ + + _so_name = "Interactions::InteractionBuckingham" + + def is_active(self): + """Check if interaction is active. """ - super().set_params(**kwargs) + return self.a > 0. or self.b > 0. or self.d > 0. or self.shift > 0. + + 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 valid_keys(self): """All parameters that can be set. @@ -1380,8 +1346,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): self.soft_sphere = SoftSphereInteraction(_type1, _type2) IF SMOOTH_STEP: self.smooth_step = SmoothStepInteraction(_type1, _type2) - IF BUCKINGHAM: - self.buckingham = BuckinghamInteraction(_type1, _type2) IF TABULATED: self.tabulated = TabulatedNonBonded(_type1, _type2) IF GAY_BERNE: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 9bbc72ddb6e..719aafdba1d 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -411,6 +411,35 @@ class InteractionMorse }; #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"), + }); + } + + 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 + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -442,6 +471,9 @@ class NonBondedInteractionHandle #ifdef MORSE std::shared_ptr m_morse; #endif +#ifdef BUCKINGHAM + std::shared_ptr m_buckingham; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -486,6 +518,9 @@ class NonBondedInteractionHandle #endif #ifdef MORSE make_autoparameter(m_morse, "morse"), +#endif +#ifdef BUCKINGHAM + make_autoparameter(m_buckingham, "buckingham"), #endif }); } @@ -560,6 +595,11 @@ class NonBondedInteractionHandle #ifdef MORSE set_member(m_morse, "morse", "Interactions::InteractionMorse", params); +#endif +#ifdef BUCKINGHAM + set_member(m_buckingham, "buckingham", + "Interactions::InteractionBuckingham", + params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 57cd45770f7..17983855d75 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -83,6 +83,10 @@ void initialize(Utils::Factory *om) { #ifdef MORSE om->register_new("Interactions::InteractionMorse"); #endif +#ifdef BUCKINGHAM + om->register_new( + "Interactions::InteractionBuckingham"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 631747c2ea9..342749f739a 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -312,6 +312,15 @@ def test_morse_exceptions(self): ("eps",) ) + @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") + ) + if __name__ == "__main__": ut.main() From 04db15c6bd5738202bb08be05b0c77615644daf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Tue, 6 Sep 2022 13:31:02 +0200 Subject: [PATCH 12/23] Rewrite SoftSphere interaction interface --- .../nonbonded_interaction_data.cpp | 3 +- .../nonbonded_interaction_data.hpp | 3 + .../nonbonded_interactions/soft_sphere.cpp | 32 +++----- .../nonbonded_interactions/soft_sphere.hpp | 11 +-- src/python/espressomd/interactions.pxd | 19 ----- src/python/espressomd/interactions.pyx | 73 ++++++------------- .../interactions/NonBondedInteraction.hpp | 37 ++++++++++ .../interactions/initialize.cpp | 4 + .../interactions_non-bonded_interface.py | 8 ++ 9 files changed, 91 insertions(+), 99 deletions(-) diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 0ba701e8b56..bcff9a8bbd4 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -178,8 +178,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index eed08f6f359..e4fe1e0564e 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -175,6 +175,9 @@ 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 */ diff --git a/src/core/nonbonded_interactions/soft_sphere.cpp b/src/core/nonbonded_interactions/soft_sphere.cpp index ffc13dc9ad6..bfa8e44d928 100644 --- a/src/core/nonbonded_interactions/soft_sphere.cpp +++ b/src/core/nonbonded_interactions/soft_sphere.cpp @@ -26,27 +26,19 @@ #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 (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 b7a0b0acc39..658e30020ed 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/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 6d57e62deaf..37a5d00bad8 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -47,12 +47,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": int n double k0 - cdef struct SoftSphere_Parameters: - double a - double n - double cut - double offset - cdef struct Hat_Parameters: double Fmax double r @@ -79,8 +73,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": cdef struct IA_parameters: - SoftSphere_Parameters soft_sphere - TabulatedPotential tab GayBerne_Parameters gay_berne @@ -116,11 +108,6 @@ IF SMOOTH_STEP: double d, int n, double eps, double k0, double sig, double cut) -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 DPD: cdef extern from "dpd.hpp": int dpd_set_params(int part_type_a, int part_type_b, @@ -132,12 +119,6 @@ IF HAT: 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, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index d3cdfae0377..0a12522d926 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -1122,44 +1122,37 @@ IF BUCKINGHAM == 1: IF SOFT_SPHERE == 1: - cdef class SoftSphereInteraction(NonBondedInteraction): + @script_interface_register + class SoftSphereInteraction(NewNonBondedInteraction): + """Soft sphere interaction. - def validate_params(self): - """Check that parameters are valid. + 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. - """ - 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") + 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. + """ - 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 - } + _so_name = "Interactions::InteractionSoftSphere" 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") + return self.a > 0. def default_params(self): """Python dictionary of default parameters. @@ -1173,24 +1166,6 @@ IF SOFT_SPHERE == 1: """ 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. @@ -1342,8 +1317,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): super().__init__(_types=[_type1, _type2], **kwargs) # Here, add one line for each nonbonded ia - IF SOFT_SPHERE: - self.soft_sphere = SoftSphereInteraction(_type1, _type2) IF SMOOTH_STEP: self.smooth_step = SmoothStepInteraction(_type1, _type2) IF TABULATED: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 719aafdba1d..8904cf03705 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -440,6 +440,32 @@ class InteractionBuckingham }; #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"), + }); + } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = + make_shared_from_args( + params, "a", "n", "cutoff", "offset"); + } +}; +#endif // SOFT_SPHERE + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -474,6 +500,9 @@ class NonBondedInteractionHandle #ifdef BUCKINGHAM std::shared_ptr m_buckingham; #endif +#ifdef SOFT_SPHERE + std::shared_ptr m_soft_sphere; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -521,6 +550,9 @@ class NonBondedInteractionHandle #endif #ifdef BUCKINGHAM make_autoparameter(m_buckingham, "buckingham"), +#endif +#ifdef SOFT_SPHERE + make_autoparameter(m_soft_sphere, "soft_sphere"), #endif }); } @@ -600,6 +632,11 @@ class NonBondedInteractionHandle set_member(m_buckingham, "buckingham", "Interactions::InteractionBuckingham", params); +#endif +#ifdef SOFT_SPHERE + set_member(m_soft_sphere, "soft_sphere", + "Interactions::InteractionSoftSphere", + params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 17983855d75..8174935106e 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -87,6 +87,10 @@ void initialize(Utils::Factory *om) { om->register_new( "Interactions::InteractionBuckingham"); #endif +#ifdef SOFT_SPHERE + om->register_new( + "Interactions::InteractionSoftSphere"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 342749f739a..6edea52a3ed 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -321,6 +321,14 @@ def test_buckingham_exceptions(self): ("a", "b", "c", "d") ) + @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") + ) + if __name__ == "__main__": ut.main() From d77ee0805906939e2b68aed484deb9d21e266f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Wed, 7 Sep 2022 16:17:19 +0200 Subject: [PATCH 13/23] Rewrite Hat interaction interface --- src/core/nonbonded_interactions/hat.cpp | 23 +++------- src/core/nonbonded_interactions/hat.hpp | 8 +--- .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 3 ++ src/python/espressomd/interactions.pxd | 11 ----- src/python/espressomd/interactions.pyx | 45 +++++++------------ .../interactions/NonBondedInteraction.hpp | 32 +++++++++++++ .../interactions/initialize.cpp | 3 ++ .../interactions_non-bonded_interface.py | 8 ++++ 9 files changed, 71 insertions(+), 64 deletions(-) diff --git a/src/core/nonbonded_interactions/hat.cpp b/src/core/nonbonded_interactions/hat.cpp index c861851a6d8..ede5d20066e 100644 --- a/src/core/nonbonded_interactions/hat.cpp +++ b/src/core/nonbonded_interactions/hat.cpp @@ -25,24 +25,15 @@ #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"); + } } -#endif +#endif // HAT diff --git a/src/core/nonbonded_interactions/hat.hpp b/src/core/nonbonded_interactions/hat.hpp index 60d6fdc40e9..e5c84a1c993 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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index bcff9a8bbd4..e844ab84553 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -182,7 +182,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index e4fe1e0564e..ece89b974fa 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -184,6 +184,9 @@ struct SoftSphere_Parameters { 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 */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 37a5d00bad8..9fd85e33426 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -47,10 +47,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": int n double k0 - cdef struct Hat_Parameters: - double Fmax - double r - cdef struct GayBerne_Parameters: double eps double sig @@ -82,8 +78,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": DPDParameters dpd_radial DPDParameters dpd_trans - Hat_Parameters hat - Thole_Parameters thole cdef IA_parameters * get_ia_param_safe(int i, int j) @@ -114,11 +108,6 @@ IF DPD: 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 TABULATED: cdef extern from "nonbonded_interactions/nonbonded_tab.hpp": int tabulated_set_params(int part_type_a, int part_type_b, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 0a12522d926..94756808bc9 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -616,28 +616,17 @@ IF LJCOS2 == 1: 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) + @script_interface_register + class HatInteraction(NewNonBondedInteraction): + """Hat interaction. - def set_params(self, **kwargs): - """Set parameters for the Hat 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 ---------- @@ -646,14 +635,12 @@ IF HAT == 1: 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") + _so_name = "Interactions::InteractionHat" + + def is_active(self): + return self.F_max > 0. def default_params(self): return {} @@ -1325,8 +1312,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): 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) diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 8904cf03705..a8aa0d667bf 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -466,6 +466,28 @@ class InteractionSoftSphere }; #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"), + }); + } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "F_max", "cutoff"); + } +}; +#endif // HAT + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -503,6 +525,9 @@ class NonBondedInteractionHandle #ifdef SOFT_SPHERE std::shared_ptr m_soft_sphere; #endif +#ifdef HAT + std::shared_ptr m_hat; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -553,6 +578,9 @@ class NonBondedInteractionHandle #endif #ifdef SOFT_SPHERE make_autoparameter(m_soft_sphere, "soft_sphere"), +#endif +#ifdef HAT + make_autoparameter(m_hat, "hat"), #endif }); } @@ -637,6 +665,10 @@ class NonBondedInteractionHandle set_member(m_soft_sphere, "soft_sphere", "Interactions::InteractionSoftSphere", params); +#endif +#ifdef HAT + set_member(m_hat, "hat", "Interactions::InteractionHat", + params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 8174935106e..ad1f5c08b6b 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -91,6 +91,9 @@ void initialize(Utils::Factory *om) { om->register_new( "Interactions::InteractionSoftSphere"); #endif +#ifdef HAT + om->register_new("Interactions::InteractionHat"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 6edea52a3ed..1483af4b18a 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -329,6 +329,14 @@ def test_soft_sphere_exceptions(self): ("a", "offset") ) + @utx.skipIfMissingFeatures("HAT") + def test_hat_exceptions(self): + self.check_potential_exceptions( + espressomd.interactions.HatInteraction, + {"F_max": 10., "cutoff": 1.}, + ("F_max",) + ) + if __name__ == "__main__": ut.main() From f4787cca1e59246ec8217a6872013bd8fbb0c7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Wed, 7 Sep 2022 17:57:14 +0200 Subject: [PATCH 14/23] Rewrite Gay-Berne interaction interface --- src/core/nonbonded_interactions/gay_berne.cpp | 40 +++--------- src/core/nonbonded_interactions/gay_berne.hpp | 4 -- .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 4 ++ src/python/espressomd/interactions.pxd | 18 ------ src/python/espressomd/interactions.pyx | 62 ++++++------------- .../interactions/NonBondedInteraction.hpp | 39 ++++++++++++ .../interactions/initialize.cpp | 3 + 8 files changed, 73 insertions(+), 99 deletions(-) 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 4696f267af4..b667b83de01 100644 --- a/src/core/nonbonded_interactions/gay_berne.hpp +++ b/src/core/nonbonded_interactions/gay_berne.hpp @@ -44,10 +44,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::Vector3d const &ui, Utils::Vector3d const &uj, diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index e844ab84553..1c0be99411f 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -194,7 +194,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index ece89b974fa..a0bee47bb43 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -227,6 +227,10 @@ 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 */ diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 9fd85e33426..da0312ff344 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -47,15 +47,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": int n double k0 - 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 @@ -71,8 +62,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": TabulatedPotential tab - GayBerne_Parameters gay_berne - SmoothStep_Parameters smooth_step DPDParameters dpd_radial @@ -85,13 +74,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": cdef void ia_params_set_state(string) cdef void reset_ia_params() -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) diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 94756808bc9..65dcf99941e 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -656,36 +656,17 @@ IF HAT == 1: 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) + @script_interface_register + class GayBerneInteraction(NewNonBondedInteraction): + """Gay--Berne interaction. - def set_params(self, **kwargs): - """Set parameters for the Gay-Berne 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 ---------- @@ -705,20 +686,15 @@ IF GAY_BERNE: 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") + _so_name = "Interactions::InteractionGayBerne" + + def is_active(self): + """Check if interaction is active. + + """ + return self.eps > 0. def default_params(self): """Python dictionary of default parameters. @@ -1308,8 +1284,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): self.smooth_step = SmoothStepInteraction(_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 THOLE: diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index a8aa0d667bf..36fe39350c9 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -488,6 +488,35 @@ class InteractionHat : public InteractionPotentialInterface<::Hat_Parameters> { }; #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"), + }); + } + + 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 + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -528,6 +557,9 @@ class NonBondedInteractionHandle #ifdef HAT std::shared_ptr m_hat; #endif +#ifdef GAY_BERNE + std::shared_ptr m_gay_berne; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -581,6 +613,9 @@ class NonBondedInteractionHandle #endif #ifdef HAT make_autoparameter(m_hat, "hat"), +#endif +#ifdef GAY_BERNE + make_autoparameter(m_gay_berne, "gay_berne"), #endif }); } @@ -669,6 +704,10 @@ class NonBondedInteractionHandle #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 } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index ad1f5c08b6b..00e0e121507 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -94,6 +94,9 @@ void initialize(Utils::Factory *om) { #ifdef HAT om->register_new("Interactions::InteractionHat"); #endif +#ifdef GAY_BERNE + om->register_new("Interactions::InteractionGayBerne"); +#endif } } // namespace Interactions } // namespace ScriptInterface From 23448f69a4c710c9f35734f69bd860767a9e0b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Wed, 7 Sep 2022 18:56:33 +0200 Subject: [PATCH 15/23] Rewrite Tabulated interaction interface --- src/core/CMakeLists.txt | 3 +- src/core/TabulatedPotential.cpp | 55 +++++++ src/core/TabulatedPotential.hpp | 6 +- src/core/bonded_interactions/bonded_tab.cpp | 15 +- .../nonbonded_interactions/CMakeLists.txt | 1 - .../nonbonded_interactions/nonbonded_tab.cpp | 58 ------- .../nonbonded_interactions/nonbonded_tab.hpp | 19 +-- src/python/espressomd/interactions.pxd | 17 --- src/python/espressomd/interactions.pyx | 142 ++++++++---------- .../interactions/NonBondedInteraction.hpp | 45 ++++++ .../interactions/initialize.cpp | 3 + testsuite/python/tabulated.py | 13 ++ 12 files changed, 188 insertions(+), 189 deletions(-) create mode 100644 src/core/TabulatedPotential.cpp delete mode 100644 src/core/nonbonded_interactions/nonbonded_tab.cpp 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..819dee91060 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. 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/nonbonded_interactions/CMakeLists.txt b/src/core/nonbonded_interactions/CMakeLists.txt index 66570208137..d655a1b11f0 100644 --- a/src/core/nonbonded_interactions/CMakeLists.txt +++ b/src/core/nonbonded_interactions/CMakeLists.txt @@ -12,7 +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 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 692d17ef604..3b1d35a2939 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/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index da0312ff344..22ed81c6066 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -20,7 +20,6 @@ # Handling of interactions from libcpp.string cimport string -from libcpp.vector cimport vector from libc cimport stdint from .thermostat cimport thermalized_bond @@ -31,13 +30,6 @@ include "myconfig.pxi" cdef extern from "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 SmoothStep_Parameters: double eps @@ -60,8 +52,6 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": cdef struct IA_parameters: - TabulatedPotential tab - SmoothStep_Parameters smooth_step DPDParameters dpd_radial @@ -90,13 +80,6 @@ IF DPD: double gamma, double k, double r_c, int wf, double tgamma, double tr_c, int twf) -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 + diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 65dcf99941e..e7a09e3b981 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -720,6 +720,69 @@ IF GAY_BERNE: """ return {"eps", "sig", "cut", "k1", "k2", "mu", "nu"} +IF TABULATED: + + @script_interface_register + class TabulatedNonBonded(NewNonBondedInteraction): + """Tabulated 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 + ---------- + 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" + + def is_active(self): + """Check if interaction is active. + + """ + return self.cutoff > 0 + + def default_params(self): + """Python dictionary of default parameters. + + """ + return {} + + 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"} + + @property + def cutoff(self): + return self.call_method("get_cutoff") + IF DPD: cdef class DPDInteraction(NonBondedInteraction): @@ -1282,8 +1345,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): # Here, add one line for each nonbonded ia IF SMOOTH_STEP: self.smooth_step = SmoothStepInteraction(_type1, _type2) - IF TABULATED: - self.tabulated = TabulatedNonBonded(_type1, _type2) IF DPD: self.dpd = DPDInteraction(_type1, _type2) IF THOLE: @@ -2042,83 +2103,6 @@ IF TABULATED: 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): diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 36fe39350c9..0cdc53b4523 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -517,6 +517,41 @@ class InteractionGayBerne }; #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"), + }); + } + + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args, std::vector>( + params, "min", "max", "force", "energy"); + } + + 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 + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -560,6 +595,9 @@ class NonBondedInteractionHandle #ifdef GAY_BERNE std::shared_ptr m_gay_berne; #endif +#ifdef TABULATED + std::shared_ptr m_tabulated; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -616,6 +654,9 @@ class NonBondedInteractionHandle #endif #ifdef GAY_BERNE make_autoparameter(m_gay_berne, "gay_berne"), +#endif +#ifdef TABULATED + make_autoparameter(m_tabulated, "tabulated"), #endif }); } @@ -708,6 +749,10 @@ class NonBondedInteractionHandle #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 } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 00e0e121507..5e33081b41d 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -97,6 +97,9 @@ void initialize(Utils::Factory *om) { #ifdef GAY_BERNE om->register_new("Interactions::InteractionGayBerne"); #endif +#ifdef TABULATED + om->register_new("Interactions::InteractionTabulated"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/testsuite/python/tabulated.py b/testsuite/python/tabulated.py index 199d12528d1..198fdd34cc1 100644 --- a/testsuite/python/tabulated.py +++ b/testsuite/python/tabulated.py @@ -71,6 +71,19 @@ def test_non_bonded(self): self.system.non_bonded_inter[0, 0].tabulated.set_params( min=-1, max=-1, energy=[], force=[]) + 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): tb = espressomd.interactions.TabulatedDistance( From 7314aea40dcc1f12b9ec4ac65a968cb40287564f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 8 Sep 2022 01:18:18 +0200 Subject: [PATCH 16/23] Rewrite DPD interaction interface --- src/core/dpd.cpp | 36 ++++-------- src/core/dpd.hpp | 2 - .../nonbonded_interaction_data.cpp | 3 +- .../nonbonded_interaction_data.hpp | 18 ++++-- src/python/espressomd/interactions.pxd | 15 ----- src/python/espressomd/interactions.pyx | 57 +++++-------------- .../interactions/NonBondedInteraction.hpp | 48 ++++++++++++++++ .../interactions/initialize.cpp | 3 + 8 files changed, 92 insertions(+), 90 deletions(-) diff --git a/src/core/dpd.cpp b/src/core/dpd.cpp index d59b6007c12..5b4488b17bb 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 091ca91cde7..9ca2d1878ad 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/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 1c0be99411f..c813a250fe8 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -145,8 +145,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index a0bee47bb43..98d95a817ec 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -248,6 +248,18 @@ struct DPDParameters { 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 @@ -321,11 +333,7 @@ struct IA_parameters { #endif #ifdef DPD - /** \name DPD as interaction */ - /**@{*/ - DPDParameters dpd_radial; - DPDParameters dpd_trans; - /**@}*/ + DPD_Parameters dpd; #endif #ifdef THOLE diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 22ed81c6066..2a50bd6f596 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -43,20 +43,10 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": double scaling_coeff double q1q2 - cdef struct DPDParameters: - double gamma - double k - double cutoff - int wf - double pref - cdef struct IA_parameters: SmoothStep_Parameters smooth_step - DPDParameters dpd_radial - DPDParameters dpd_trans - Thole_Parameters thole cdef IA_parameters * get_ia_param_safe(int i, int j) @@ -74,11 +64,6 @@ IF SMOOTH_STEP: double d, int n, double eps, double k0, 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) cdef extern from "script_interface/interactions/bonded.hpp": int bonded_ia_params_zero_based_type(int bond_id) except + diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index e7a09e3b981..7d8d5660a4a 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -785,34 +785,17 @@ IF TABULATED: 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) + @script_interface_register + class DPDInteraction(NewNonBondedInteraction): + """DPD interaction. - def set_params(self, **kwargs): - """Set parameters for the DPD 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 ---------- @@ -833,20 +816,12 @@ IF DPD: 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") + _so_name = "Interactions::InteractionDPD" + + def is_active(self): + return self.r_cut > 0. or self.trans_r_cut > 0. def default_params(self): return { @@ -1345,8 +1320,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): # Here, add one line for each nonbonded ia IF SMOOTH_STEP: self.smooth_step = SmoothStepInteraction(_type1, _type2) - IF DPD: - self.dpd = DPDInteraction(_type1, _type2) IF THOLE: self.thole = TholeInteraction(_type1, _type2) diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 0cdc53b4523..420a866ee80 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -38,6 +38,7 @@ #include #include #include +#include #include namespace ScriptInterface { @@ -552,6 +553,43 @@ class InteractionTabulated }; #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 + } + + 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"); + } +}; +#endif // DPD + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -598,6 +636,9 @@ class NonBondedInteractionHandle #ifdef TABULATED std::shared_ptr m_tabulated; #endif +#ifdef DPD + std::shared_ptr m_dpd; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -657,6 +698,9 @@ class NonBondedInteractionHandle #endif #ifdef TABULATED make_autoparameter(m_tabulated, "tabulated"), +#endif +#ifdef DPD + make_autoparameter(m_dpd, "dpd"), #endif }); } @@ -753,6 +797,10 @@ class NonBondedInteractionHandle #ifdef TABULATED set_member( m_tabulated, "tabulated", "Interactions::InteractionTabulated", params); +#endif +#ifdef DPD + set_member(m_dpd, "dpd", "Interactions::InteractionDPD", + params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 5e33081b41d..02a63dab2f2 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -100,6 +100,9 @@ void initialize(Utils::Factory *om) { #ifdef TABULATED om->register_new("Interactions::InteractionTabulated"); #endif +#ifdef DPD + om->register_new("Interactions::InteractionDPD"); +#endif } } // namespace Interactions } // namespace ScriptInterface From 3a9102aa6322548ad4f58519403a39519b85b750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 8 Sep 2022 12:28:12 +0200 Subject: [PATCH 17/23] Rewrite Thole interaction interface --- .../nonbonded_interactions/CMakeLists.txt | 1 - .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 7 +- src/core/nonbonded_interactions/thole.cpp | 47 -------- src/core/nonbonded_interactions/thole.hpp | 3 - src/python/espressomd/interactions.pxd | 10 -- src/python/espressomd/interactions.pyx | 106 ++++++++---------- .../interactions/NonBondedInteraction.hpp | 32 ++++++ .../interactions/initialize.cpp | 3 + 9 files changed, 87 insertions(+), 124 deletions(-) delete mode 100644 src/core/nonbonded_interactions/thole.cpp diff --git a/src/core/nonbonded_interactions/CMakeLists.txt b/src/core/nonbonded_interactions/CMakeLists.txt index d655a1b11f0..1e52b0fe4d0 100644 --- a/src/core/nonbonded_interactions/CMakeLists.txt +++ b/src/core/nonbonded_interactions/CMakeLists.txt @@ -14,5 +14,4 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/nonbonded_interaction_data.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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index c813a250fe8..6e000f81e99 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -202,7 +202,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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index 98d95a817ec..f4a9feac3d0 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -235,8 +235,11 @@ struct GayBerne_Parameters { /** 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 */ 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 5c0eb75732b..9c9f4acdebb 100644 --- a/src/core/nonbonded_interactions/thole.hpp +++ b/src/core/nonbonded_interactions/thole.hpp @@ -41,9 +41,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/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 2a50bd6f596..3a30ad945af 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -39,25 +39,15 @@ cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": int n double k0 - cdef struct Thole_Parameters: - double scaling_coeff - double q1q2 - cdef struct IA_parameters: SmoothStep_Parameters smooth_step - 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 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 SMOOTH_STEP: cdef extern from "nonbonded_interactions/smooth_step.hpp": int smooth_step_set_params(int part_type_a, int part_type_b, diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 7d8d5660a4a..fa2859b2455 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -1289,6 +1289,52 @@ IF GAUSSIAN == 1: """ return {"eps", "sig", "cutoff"} +IF THOLE: + + @script_interface_register + class TholeInteraction(NewNonBondedInteraction): + """Thole 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 + ---------- + 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" + + def is_active(self): + return self.scaling_coeff != 0. + + 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"} + @script_interface_register class NonBondedInteractionHandle(ScriptInterfaceHelper): @@ -1320,8 +1366,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): # Here, add one line for each nonbonded ia IF SMOOTH_STEP: self.smooth_step = SmoothStepInteraction(_type1, _type2) - IF THOLE: - self.thole = TholeInteraction(_type1, _type2) def _serialize(self): serialized = [] @@ -1771,64 +1815,6 @@ class ThermalizedBond(BondedInteraction): 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 diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 420a866ee80..b3b2f46db59 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -590,6 +590,28 @@ class InteractionDPD : public InteractionPotentialInterface<::DPD_Parameters> { }; #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"), + }); + } + void make_new_instance(VariantMap const ¶ms) override { + m_ia_si = make_shared_from_args( + params, "scaling_coeff", "q1q2"); + } +}; +#endif // THOLE + class NonBondedInteractionHandle : public AutoParameters { std::array m_types = {-1, -1}; @@ -639,6 +661,9 @@ class NonBondedInteractionHandle #ifdef DPD std::shared_ptr m_dpd; #endif +#ifdef THOLE + std::shared_ptr m_thole; +#endif template auto make_autoparameter(std::shared_ptr &member, const char *key) const { @@ -701,6 +726,9 @@ class NonBondedInteractionHandle #endif #ifdef DPD make_autoparameter(m_dpd, "dpd"), +#endif +#ifdef THOLE + make_autoparameter(m_thole, "thole"), #endif }); } @@ -801,6 +829,10 @@ class NonBondedInteractionHandle #ifdef DPD set_member(m_dpd, "dpd", "Interactions::InteractionDPD", params); +#endif +#ifdef THOLE + set_member(m_thole, "thole", + "Interactions::InteractionThole", params); #endif } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index 02a63dab2f2..b1a2b09a1a8 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -103,6 +103,9 @@ void initialize(Utils::Factory *om) { #ifdef DPD om->register_new("Interactions::InteractionDPD"); #endif +#ifdef THOLE + om->register_new("Interactions::InteractionThole"); +#endif } } // namespace Interactions } // namespace ScriptInterface From d8cd0c33c195cafd8fb87a3f922ad0e04990c1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 8 Sep 2022 13:07:59 +0200 Subject: [PATCH 18/23] Rewrite SmoothStep interaction interface --- .../nonbonded_interaction_data.cpp | 2 +- .../nonbonded_interaction_data.hpp | 4 + .../nonbonded_interactions/smooth_step.cpp | 30 ++---- .../nonbonded_interactions/smooth_step.hpp | 3 - src/python/espressomd/interactions.pxd | 17 +--- src/python/espressomd/interactions.pyx | 93 ++++++------------- .../interactions/NonBondedInteraction.hpp | 38 ++++++++ .../interactions/initialize.cpp | 4 + .../interactions_non-bonded_interface.py | 8 ++ 9 files changed, 94 insertions(+), 105 deletions(-) diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 6e000f81e99..673407afcc4 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -153,7 +153,7 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #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 diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index f4a9feac3d0..10eb3e1c97e 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -104,6 +104,10 @@ 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 */ diff --git a/src/core/nonbonded_interactions/smooth_step.cpp b/src/core/nonbonded_interactions/smooth_step.cpp index 34996d91776..fd9d8f16e0d 100644 --- a/src/core/nonbonded_interactions/smooth_step.cpp +++ b/src/core/nonbonded_interactions/smooth_step.cpp @@ -25,29 +25,17 @@ #include "smooth_step.hpp" #ifdef SMOOTH_STEP -#include "interactions.hpp" #include "nonbonded_interaction_data.hpp" -#include +#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; +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"); + } } -#endif +#endif // SMOOTH_STEP diff --git a/src/core/nonbonded_interactions/smooth_step.hpp b/src/core/nonbonded_interactions/smooth_step.hpp index 50cf2152796..df3c858e20a 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/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 3a30ad945af..2407460c749 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -31,30 +31,15 @@ cdef extern from "config.hpp": pass cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": - cdef struct SmoothStep_Parameters: - double eps - double sig - double cut - double d - int n - double k0 cdef struct IA_parameters: - - SmoothStep_Parameters smooth_step + pass 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 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) - cdef extern from "script_interface/interactions/bonded.hpp": int bonded_ia_params_zero_based_type(int bond_id) except + diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index fa2859b2455..c52124fc26e 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -845,51 +845,42 @@ IF DPD: IF SMOOTH_STEP == 1: - cdef class SmoothStepInteraction(NonBondedInteraction): + @script_interface_register + class SmoothStepInteraction(NewNonBondedInteraction): + """Smooth step interaction. - def validate_params(self): - """Check that parameters are valid. + 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. - """ - 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 - } + 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" 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") + return self.eps > 0. and self.sig > 0. def default_params(self): """Python dictionary of default parameters. @@ -903,28 +894,6 @@ IF SMOOTH_STEP == 1: """ 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. @@ -1363,10 +1332,6 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): raise TypeError("The particle types have to be of type integer.") super().__init__(_types=[_type1, _type2], **kwargs) - # Here, add one line for each nonbonded ia - IF SMOOTH_STEP: - self.smooth_step = SmoothStepInteraction(_type1, _type2) - def _serialize(self): serialized = [] for name, obj in self.get_params().items(): diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index b3b2f46db59..076e95ef48b 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -612,6 +612,33 @@ class InteractionThole }; #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"), + }); + } + 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}; @@ -664,6 +691,9 @@ class NonBondedInteractionHandle #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 { @@ -729,6 +759,9 @@ class NonBondedInteractionHandle #endif #ifdef THOLE make_autoparameter(m_thole, "thole"), +#endif +#ifdef SMOOTH_STEP + make_autoparameter(m_smooth_step, "smooth_step"), #endif }); } @@ -833,6 +866,11 @@ class NonBondedInteractionHandle #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 } diff --git a/src/script_interface/interactions/initialize.cpp b/src/script_interface/interactions/initialize.cpp index b1a2b09a1a8..57129779939 100644 --- a/src/script_interface/interactions/initialize.cpp +++ b/src/script_interface/interactions/initialize.cpp @@ -106,6 +106,10 @@ void initialize(Utils::Factory *om) { #ifdef THOLE om->register_new("Interactions::InteractionThole"); #endif +#ifdef SMOOTH_STEP + om->register_new( + "Interactions::InteractionSmoothStep"); +#endif } } // namespace Interactions } // namespace ScriptInterface diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 1483af4b18a..e04c0b509f4 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -337,6 +337,14 @@ def test_hat_exceptions(self): ("F_max",) ) + @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",) + ) + if __name__ == "__main__": ut.main() From 05ace34d1bd52d8abb9f2380f54e9b577d528d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Fri, 9 Sep 2022 11:24:01 +0200 Subject: [PATCH 19/23] Remove old non-bonded interaction interface --- src/core/TabulatedPotential.hpp | 11 - src/core/interactions.cpp | 16 +- src/core/interactions.hpp | 13 - .../nonbonded_interaction_data.cpp | 69 +---- .../nonbonded_interaction_data.hpp | 23 +- src/core/serialization/IA_parameters.hpp | 56 ----- .../EspressoSystemStandAlone_test.cpp | 1 - src/core/unit_tests/Verlet_list_test.cpp | 1 - src/python/espressomd/interactions.pxd | 11 - src/python/espressomd/interactions.pyx | 238 ++---------------- .../interactions/NonBondedInteraction.hpp | 1 - .../interactions/NonBondedInteractions.hpp | 3 - 12 files changed, 30 insertions(+), 413 deletions(-) delete mode 100644 src/core/serialization/IA_parameters.hpp diff --git a/src/core/TabulatedPotential.hpp b/src/core/TabulatedPotential.hpp index 819dee91060..e63a3bef9d4 100644 --- a/src/core/TabulatedPotential.hpp +++ b/src/core/TabulatedPotential.hpp @@ -71,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/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/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 673407afcc4..6673ee62feb 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,6 @@ * variables *****************************************/ int max_seen_particle_type = 0; -std::vector old_nonbonded_ia_params; std::vector> nonbonded_ia_params; /** Minimal global interaction cutoff. Particles with a distance @@ -67,7 +56,6 @@ void mpi_realloc_ia_params_local(int new_size) { return; auto const n_pairs = new_size * (new_size + 1) / 2; - auto new_params_ = std::vector(n_pairs); auto new_params = std::vector>(n_pairs); /* if there is an old field, move entries */ @@ -75,7 +63,6 @@ void mpi_realloc_ia_params_local(int new_size) { 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_.at(new_key) = std::move(old_nonbonded_ia_params[old_key]); new_params[new_key] = std::move(nonbonded_ia_params[old_key]); } } @@ -86,53 +73,11 @@ void mpi_realloc_ia_params_local(int new_size) { } ::max_seen_particle_type = new_size; - std::swap(::old_nonbonded_ia_params, new_params_); std::swap(::nonbonded_ia_params, 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, old_nonbonded_ia_params, 0); -} - -REGISTER_CALLBACK(mpi_bcast_all_ia_params_local) - -/** Broadcast @ref old_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 << old_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); - old_nonbonded_ia_params.clear(); - ia >> old_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; @@ -212,11 +157,6 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { double maximal_cutoff_nonbonded() { auto max_cut_nonbonded = INACTIVE_CUTOFF; - for (auto &data : old_nonbonded_ia_params) { - data.max_cut = recalc_maximal_cutoff(data); - max_cut_nonbonded = std::max(max_cut_nonbonded, data.max_cut); - } - for (auto &data : nonbonded_ia_params) { data->max_cut = recalc_maximal_cutoff(*data); max_cut_nonbonded = std::max(max_cut_nonbonded, data->max_cut); @@ -225,18 +165,13 @@ double maximal_cutoff_nonbonded() { return max_cut_nonbonded; } -void reset_ia_params() { - boost::fill(old_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 10eb3e1c97e..b778530f051 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -33,7 +33,6 @@ #include #include #include -#include #include /** Cutoff for deactivated interactions. Must be negative, so that even @@ -348,7 +347,6 @@ struct IA_parameters { #endif }; -extern std::vector old_nonbonded_ia_params; extern std::vector> nonbonded_ia_params; /** Maximal particle type seen so far. */ @@ -378,23 +376,9 @@ inline int get_ia_param_key(int i, int j) { * @return Reference to interaction parameters for the type pair. */ inline IA_parameters &get_ia_param(int i, int j) { - return ::old_nonbonded_ia_params[get_ia_param_key(i, j)]; + 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); @@ -406,11 +390,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/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 224f3703cd5..f632f3e0b05 100644 --- a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp +++ b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp @@ -132,7 +132,6 @@ 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; - ::old_nonbonded_ia_params[key].lj = lj; on_non_bonded_ia_change(); } diff --git a/src/core/unit_tests/Verlet_list_test.cpp b/src/core/unit_tests/Verlet_list_test.cpp index d006d5b9439..170aad1e061 100644 --- a/src/core/unit_tests/Verlet_list_test.cpp +++ b/src/core/unit_tests/Verlet_list_test.cpp @@ -151,7 +151,6 @@ 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; - ::old_nonbonded_ia_params[key].lj = lj; on_non_bonded_ia_change(); } diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 2407460c749..6d5ca5f26fc 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -19,7 +19,6 @@ # Handling of interactions -from libcpp.string cimport string from libc cimport stdint from .thermostat cimport thermalized_bond @@ -30,16 +29,6 @@ include "myconfig.pxi" cdef extern from "config.hpp": pass -cdef extern from "nonbonded_interactions/nonbonded_interaction_data.hpp": - - cdef struct IA_parameters: - pass - - 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() - cdef extern from "script_interface/interactions/bonded.hpp": int bonded_ia_params_zero_based_type(int bond_id) except + diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index c52124fc26e..dda684e5983 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -16,197 +16,14 @@ # 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.") - - -class NewNonBondedInteraction(ScriptInterfaceHelper): +class NonBondedInteraction(ScriptInterfaceHelper): """ Represents an instance of a non-bonded interaction, such as Lennard-Jones. @@ -287,7 +104,7 @@ class NewNonBondedInteraction(ScriptInterfaceHelper): IF LENNARD_JONES == 1: @script_interface_register - class LennardJonesInteraction(NewNonBondedInteraction): + class LennardJonesInteraction(NonBondedInteraction): """ Standard 6-12 Lennard-Jones potential. @@ -353,7 +170,7 @@ IF LENNARD_JONES == 1: IF WCA == 1: @script_interface_register - class WCAInteraction(NewNonBondedInteraction): + class WCAInteraction(NonBondedInteraction): """ Standard 6-12 Weeks-Chandler-Andersen potential. @@ -410,7 +227,7 @@ IF WCA == 1: IF LENNARD_JONES_GENERIC == 1: @script_interface_register - class GenericLennardJonesInteraction(NewNonBondedInteraction): + class GenericLennardJonesInteraction(NonBondedInteraction): """ Generalized Lennard-Jones potential. @@ -497,7 +314,7 @@ IF LENNARD_JONES_GENERIC == 1: IF LJCOS == 1: @script_interface_register - class LennardJonesCosInteraction(NewNonBondedInteraction): + class LennardJonesCosInteraction(NonBondedInteraction): """Lennard-Jones cosine interaction. Methods @@ -555,7 +372,7 @@ IF LJCOS == 1: IF LJCOS2 == 1: @script_interface_register - class LennardJonesCos2Interaction(NewNonBondedInteraction): + class LennardJonesCos2Interaction(NonBondedInteraction): """Second variant of the Lennard-Jones cosine interaction. Methods @@ -617,7 +434,7 @@ IF LJCOS2 == 1: IF HAT == 1: @script_interface_register - class HatInteraction(NewNonBondedInteraction): + class HatInteraction(NonBondedInteraction): """Hat interaction. Methods @@ -657,7 +474,7 @@ IF HAT == 1: IF GAY_BERNE: @script_interface_register - class GayBerneInteraction(NewNonBondedInteraction): + class GayBerneInteraction(NonBondedInteraction): """Gay--Berne interaction. Methods @@ -723,7 +540,7 @@ IF GAY_BERNE: IF TABULATED: @script_interface_register - class TabulatedNonBonded(NewNonBondedInteraction): + class TabulatedNonBonded(NonBondedInteraction): """Tabulated interaction. Methods @@ -786,7 +603,7 @@ IF TABULATED: IF DPD: @script_interface_register - class DPDInteraction(NewNonBondedInteraction): + class DPDInteraction(NonBondedInteraction): """DPD interaction. Methods @@ -846,7 +663,7 @@ IF DPD: IF SMOOTH_STEP == 1: @script_interface_register - class SmoothStepInteraction(NewNonBondedInteraction): + class SmoothStepInteraction(NonBondedInteraction): """Smooth step interaction. Methods @@ -909,7 +726,7 @@ IF SMOOTH_STEP == 1: IF BMHTF_NACL == 1: @script_interface_register - class BMHTFInteraction(NewNonBondedInteraction): + class BMHTFInteraction(NonBondedInteraction): """BMHTF interaction. Methods @@ -971,7 +788,7 @@ IF BMHTF_NACL == 1: IF MORSE == 1: @script_interface_register - class MorseInteraction(NewNonBondedInteraction): + class MorseInteraction(NonBondedInteraction): """Morse interaction. Methods @@ -1029,7 +846,7 @@ IF MORSE == 1: IF BUCKINGHAM == 1: @script_interface_register - class BuckinghamInteraction(NewNonBondedInteraction): + class BuckinghamInteraction(NonBondedInteraction): """Buckingham interaction. Methods @@ -1093,7 +910,7 @@ IF BUCKINGHAM == 1: IF SOFT_SPHERE == 1: @script_interface_register - class SoftSphereInteraction(NewNonBondedInteraction): + class SoftSphereInteraction(NonBondedInteraction): """Soft sphere interaction. Methods @@ -1151,7 +968,7 @@ IF SOFT_SPHERE == 1: IF HERTZIAN == 1: @script_interface_register - class HertzianInteraction(NewNonBondedInteraction): + class HertzianInteraction(NonBondedInteraction): """Hertzian interaction. Methods @@ -1205,7 +1022,7 @@ IF HERTZIAN == 1: IF GAUSSIAN == 1: @script_interface_register - class GaussianInteraction(NewNonBondedInteraction): + class GaussianInteraction(NonBondedInteraction): """Gaussian interaction. Methods @@ -1261,7 +1078,7 @@ IF GAUSSIAN == 1: IF THOLE: @script_interface_register - class TholeInteraction(NewNonBondedInteraction): + class TholeInteraction(NonBondedInteraction): """Thole interaction. Methods @@ -1345,10 +1162,15 @@ 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",) + _so_bind_methods = ("reset",) def keys(self): return [tuple(x) for x in self.call_method("keys")] @@ -1368,14 +1190,13 @@ class NonBondedInteractions(ScriptInterfaceHelper): self.call_method("insert", key=key, object=value) def __getstate__(self): - cdef string core_state = ia_params_get_state() 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, "core_state": core_state} + return {"state": state} def __setstate__(self, params): for types, kwargs in params["state"]: @@ -1385,17 +1206,8 @@ class NonBondedInteractions(ScriptInterfaceHelper): obj = NonBondedInteractionHandle(_types=types, **objects) self.call_method("insert", key=types, object=obj) - def reset(self): - """ - Reset all interaction parameters to their default values. - """ - reset_ia_params() - self.call_method("reset") - @classmethod def _restore_object(cls, so_callback, so_callback_args, state): - cdef string core_state = state["core_state"] - ia_params_set_state(core_state) so = so_callback(*so_callback_args) so.__setstate__(state) return so diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index 076e95ef48b..fd76e16b4c3 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -118,7 +118,6 @@ class InteractionPotentialInterface 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; - ::old_nonbonded_ia_params[key].*get_ptr_offset() = *m_ia_si; } void copy_core_to_si() { diff --git a/src/script_interface/interactions/NonBondedInteractions.hpp b/src/script_interface/interactions/NonBondedInteractions.hpp index 0817210c4cf..9680d37419d 100644 --- a/src/script_interface/interactions/NonBondedInteractions.hpp +++ b/src/script_interface/interactions/NonBondedInteractions.hpp @@ -61,7 +61,6 @@ class NonBondedInteractions : public ObjectHandle { 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>(); - ::old_nonbonded_ia_params[key] = ::IA_parameters{}; m_nonbonded_ia_params[key] = make_interaction(i, j); } } @@ -74,7 +73,6 @@ class NonBondedInteractions : public ObjectHandle { // when reloading from a checkpoint file, need to resize IA lists auto const new_size = ::max_seen_particle_type; auto const n_pairs = new_size * (new_size + 1) / 2; - ::old_nonbonded_ia_params.resize(n_pairs); ::nonbonded_ia_params.resize(n_pairs); for (auto &ia_params : ::nonbonded_ia_params) { if (ia_params == nullptr) { @@ -107,7 +105,6 @@ class NonBondedInteractions : public ObjectHandle { params.at("object")); ::nonbonded_ia_params[key] = obj_ptr->get_ia(); m_nonbonded_ia_params[key] = obj_ptr; - ::old_nonbonded_ia_params[key] = *(obj_ptr->get_ia()); on_non_bonded_ia_change(); return {}; } From f0ba1c2f4258c0119d5e413f3cecdfa72df0fcc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Fri, 9 Sep 2022 11:48:06 +0200 Subject: [PATCH 20/23] Refactor new non-bonded interface Interactions can now be reset. Skip inactive interactions when reloading from a checkpoint file to avoid triggering range checks. Re-introduce the original cutoff range checks. --- doc/sphinx/inter_non-bonded.rst | 34 +- .../nonbonded_interactions/bmhtf-nacl.cpp | 3 + .../nonbonded_interactions/buckingham.cpp | 3 + src/core/nonbonded_interactions/gaussian.cpp | 7 +- src/core/nonbonded_interactions/hat.cpp | 3 + src/core/nonbonded_interactions/hertzian.cpp | 3 + src/core/nonbonded_interactions/lj.cpp | 27 +- src/core/nonbonded_interactions/lj.hpp | 6 +- src/core/nonbonded_interactions/ljcos.cpp | 11 +- src/core/nonbonded_interactions/ljcos2.cpp | 19 +- src/core/nonbonded_interactions/ljgen.cpp | 3 + src/core/nonbonded_interactions/morse.cpp | 3 + .../nonbonded_interaction_data.hpp | 22 +- .../nonbonded_interactions/smooth_step.cpp | 6 + .../nonbonded_interactions/soft_sphere.cpp | 3 + src/core/nonbonded_interactions/wca.cpp | 11 +- src/python/espressomd/interactions.pyx | 424 ++---------------- .../interactions/NonBondedInteraction.hpp | 135 +++--- testsuite/python/collision_detection.py | 6 +- testsuite/python/constraint_shape_based.py | 3 +- testsuite/python/cutoffs.py | 5 +- testsuite/python/dawaanr-and-bh-gpu.py | 3 +- testsuite/python/dawaanr-and-dds-gpu.py | 3 +- testsuite/python/dds-and-bh-gpu.py | 3 +- testsuite/python/dipolar_direct_summation.py | 3 +- .../interactions_non-bonded_interface.py | 97 ++-- testsuite/python/p3m_fft.py | 2 +- testsuite/python/save_checkpoint.py | 8 +- testsuite/python/test_checkpoint.py | 6 +- testsuite/python/virtual_sites_relative.py | 3 +- 30 files changed, 305 insertions(+), 560 deletions(-) diff --git a/doc/sphinx/inter_non-bonded.rst b/doc/sphinx/inter_non-bonded.rst index 4a0de03ef31..877b07eb59d 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/nonbonded_interactions/bmhtf-nacl.cpp b/src/core/nonbonded_interactions/bmhtf-nacl.cpp index e89fbf193df..df7975415d2 100644 --- a/src/core/nonbonded_interactions/bmhtf-nacl.cpp +++ b/src/core/nonbonded_interactions/bmhtf-nacl.cpp @@ -43,6 +43,9 @@ BMHTF_Parameters::BMHTF_Parameters(double a, double b, double c, double d, 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)); } diff --git a/src/core/nonbonded_interactions/buckingham.cpp b/src/core/nonbonded_interactions/buckingham.cpp index e8febf5ff32..7fd90cdad50 100644 --- a/src/core/nonbonded_interactions/buckingham.cpp +++ b/src/core/nonbonded_interactions/buckingham.cpp @@ -45,6 +45,9 @@ Buckingham_Parameters::Buckingham_Parameters(double a, double b, double c, 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) */ diff --git a/src/core/nonbonded_interactions/gaussian.cpp b/src/core/nonbonded_interactions/gaussian.cpp index 97f0e8c20aa..76821cf4c11 100644 --- a/src/core/nonbonded_interactions/gaussian.cpp +++ b/src/core/nonbonded_interactions/gaussian.cpp @@ -29,13 +29,16 @@ #include -Gaussian_Parameters::Gaussian_Parameters(double eps, double sig, double cut) - : eps{eps}, sig{sig}, cut{cut} { +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 // GAUSSIAN diff --git a/src/core/nonbonded_interactions/hat.cpp b/src/core/nonbonded_interactions/hat.cpp index ede5d20066e..00dd4661ecc 100644 --- a/src/core/nonbonded_interactions/hat.cpp +++ b/src/core/nonbonded_interactions/hat.cpp @@ -34,6 +34,9 @@ Hat_Parameters::Hat_Parameters(double F_max, double 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 // HAT diff --git a/src/core/nonbonded_interactions/hertzian.cpp b/src/core/nonbonded_interactions/hertzian.cpp index 4b3439f4982..9ee911d0757 100644 --- a/src/core/nonbonded_interactions/hertzian.cpp +++ b/src/core/nonbonded_interactions/hertzian.cpp @@ -34,6 +34,9 @@ Hertzian_Parameters::Hertzian_Parameters(double eps, double 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 // HERTZIAN diff --git a/src/core/nonbonded_interactions/lj.cpp b/src/core/nonbonded_interactions/lj.cpp index f915e4acfba..afb8a63b341 100644 --- a/src/core/nonbonded_interactions/lj.cpp +++ b/src/core/nonbonded_interactions/lj.cpp @@ -27,31 +27,22 @@ #ifdef LENNARD_JONES #include "nonbonded_interaction_data.hpp" -#include - #include #include -LJ_Parameters::LJ_Parameters(double eps, double sig, double cut, double offset, - double min) - : LJ_Parameters(eps, sig, cut, offset, min, 0.) { - if (cut != 0.) { - auto const sig_cut = sig / cut; - shift = Utils::int_pow<6>(sig_cut) - Utils::int_pow<12>(sig_cut); - } -} - -LJ_Parameters::LJ_Parameters(double eps, double sig, double cut, double offset, - double min, double shift) - : eps{eps}, sig{sig}, cut{cut}, shift{shift}, offset{offset}, min{std::max( - min, - 0.)} { - if (eps < 0.) { +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 (sig < 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"); + } } #endif /* ifdef LENNARD_JONES */ diff --git a/src/core/nonbonded_interactions/lj.hpp b/src/core/nonbonded_interactions/lj.hpp index b6651943e72..d95aaaea3ae 100644 --- a/src/core/nonbonded_interactions/lj.hpp +++ b/src/core/nonbonded_interactions/lj.hpp @@ -40,8 +40,7 @@ /** 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); @@ -51,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 ba33702b446..1fa4d75e266 100644 --- a/src/core/nonbonded_interactions/ljcos.cpp +++ b/src/core/nonbonded_interactions/ljcos.cpp @@ -32,15 +32,18 @@ #include -LJcos_Parameters::LJcos_Parameters(double eps, double sig, double cut, +LJcos_Parameters::LJcos_Parameters(double epsilon, double sigma, double cutoff, double offset) - : eps{eps}, sig{sig}, cut{cut}, offset{offset} { - if (eps < 0.) { + : eps{epsilon}, sig{sigma}, cut{cutoff}, offset{offset} { + if (epsilon < 0.) { throw std::domain_error("LJcos parameter 'epsilon' has to be >= 0"); } - if (sig < 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); rmin = sqrt(Utils::cbrt_2()) * sig; diff --git a/src/core/nonbonded_interactions/ljcos2.cpp b/src/core/nonbonded_interactions/ljcos2.cpp index 31ccb14196b..1f4539e26f0 100644 --- a/src/core/nonbonded_interactions/ljcos2.cpp +++ b/src/core/nonbonded_interactions/ljcos2.cpp @@ -30,18 +30,21 @@ #include #include -LJcos2_Parameters::LJcos2_Parameters(double eps, double sig, double offset, - double w) - : eps{eps}, sig{sig}, offset{offset}, w{w} { - if (eps < 0.) { +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 (sig < 0.) { + if (sigma < 0.) { throw std::domain_error("LJcos2 parameter 'sigma' has to be >= 0"); } - if (sig != 0.) { - rchange = std::pow(2., 1. / 6.) * sig; - cut = w + rchange; + 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; } } diff --git a/src/core/nonbonded_interactions/ljgen.cpp b/src/core/nonbonded_interactions/ljgen.cpp index 51feb1bc448..d97e80f7258 100644 --- a/src/core/nonbonded_interactions/ljgen.cpp +++ b/src/core/nonbonded_interactions/ljgen.cpp @@ -46,6 +46,9 @@ LJGen_Parameters::LJGen_Parameters(double epsilon, double sigma, double cutoff, 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"); diff --git a/src/core/nonbonded_interactions/morse.cpp b/src/core/nonbonded_interactions/morse.cpp index f4465491b54..a4e4cfe0212 100644 --- a/src/core/nonbonded_interactions/morse.cpp +++ b/src/core/nonbonded_interactions/morse.cpp @@ -36,6 +36,9 @@ Morse_Parameters::Morse_Parameters(double eps, double alpha, double rmin, 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); diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index b778530f051..48a89767b33 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -28,6 +28,7 @@ #include "config.hpp" #include +#include #include #include @@ -49,10 +50,17 @@ struct LJ_Parameters { double offset = 0.0; double min = 0.0; LJ_Parameters() = default; - LJ_Parameters(double eps, double sig, double cut, double offset, double min); - LJ_Parameters(double eps, double sig, double cut, double offset, double min, - double shift); + 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 */ @@ -61,7 +69,7 @@ struct WCA_Parameters { double sig = 0.0; double cut = INACTIVE_CUTOFF; WCA_Parameters() = default; - WCA_Parameters(double eps, double sig); + WCA_Parameters(double epsilon, double sigma); double max_cutoff() const { return cut; } }; @@ -124,7 +132,7 @@ struct Gaussian_Parameters { double sig = 1.0; double cut = INACTIVE_CUTOFF; Gaussian_Parameters() = default; - Gaussian_Parameters(double eps, double sig, double cut); + Gaussian_Parameters(double eps, double sig, double cutoff); double max_cutoff() const { return cut; } }; @@ -202,7 +210,7 @@ struct LJcos_Parameters { double beta = 0.0; double rmin = 0.0; LJcos_Parameters() = default; - LJcos_Parameters(double eps, double sig, double cut, double offset); + LJcos_Parameters(double epsilon, double sigma, double cutoff, double offset); double max_cutoff() const { return cut + offset; } }; @@ -215,7 +223,7 @@ struct LJcos2_Parameters { double w = 0.0; double rchange = 0.0; LJcos2_Parameters() = default; - LJcos2_Parameters(double eps, double sig, double offset, double w); + LJcos2_Parameters(double epsilon, double sigma, double offset, double width); double max_cutoff() const { return cut + offset; } }; diff --git a/src/core/nonbonded_interactions/smooth_step.cpp b/src/core/nonbonded_interactions/smooth_step.cpp index fd9d8f16e0d..6c1b9ee7f93 100644 --- a/src/core/nonbonded_interactions/smooth_step.cpp +++ b/src/core/nonbonded_interactions/smooth_step.cpp @@ -36,6 +36,12 @@ SmoothStep_Parameters::SmoothStep_Parameters(double eps, double sig, 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 // SMOOTH_STEP diff --git a/src/core/nonbonded_interactions/soft_sphere.cpp b/src/core/nonbonded_interactions/soft_sphere.cpp index bfa8e44d928..84812c1f4f0 100644 --- a/src/core/nonbonded_interactions/soft_sphere.cpp +++ b/src/core/nonbonded_interactions/soft_sphere.cpp @@ -36,6 +36,9 @@ SoftSphere_Parameters::SoftSphere_Parameters(double a, double n, double cutoff, 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"); } diff --git a/src/core/nonbonded_interactions/wca.cpp b/src/core/nonbonded_interactions/wca.cpp index 389bc65544a..4de3020b2ed 100644 --- a/src/core/nonbonded_interactions/wca.cpp +++ b/src/core/nonbonded_interactions/wca.cpp @@ -28,15 +28,16 @@ #include #include -WCA_Parameters::WCA_Parameters(double eps, double sig) : eps{eps}, sig{sig} { - if (eps < 0.) { +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 (sig < 0.) { + if (sigma < 0.) { throw std::domain_error("WCA parameter 'sigma' has to be >= 0"); } - if (sig != 0.) { - cut = sig * std::pow(2., 1. / 6.); + if (sigma != 0.) { + cut = sigma * std::pow(2., 1. / 6.); } } diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index dda684e5983..36c2b25d1df 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -27,39 +27,50 @@ class NonBondedInteraction(ScriptInterfaceHelper): """ 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): if "sip" in kwargs: super().__init__(**kwargs) else: - self._validate(params=kwargs, check_required=True) + self._validate(kwargs) params = self.default_params() params.update(kwargs) super().__init__(**params) + self._validate_post(params) def __str__(self): return f'{self.__class__.__name__}({self.get_params()})' - def _validate(self, params, check_required=False): - # Check, if any key was passed, which is not known - keys = {x for x in params.keys() if x != "_types"} - utils.check_valid_keys(self.valid_keys(), keys) - # When an interaction is newly activated, all required keys must be - # given - if check_required: - utils.check_required_keys(self.required_keys(), params.keys()) + def _validate(self, params): + for key in self.required_keys(): + if key not in params: + raise RuntimeError( + f"{self.__class__.__name__} parameter '{key}' is missing") + + def _validate_post(self, params): + valid_parameters = self.valid_keys() + for key in params: + if key != "_types" and key not in valid_parameters: + raise RuntimeError( + f"Parameter '{key}' is not a valid {self.__class__.__name__} parameter") def set_params(self, **kwargs): - """Update the given parameters. + """Set new parameters. """ - self._validate(params=kwargs, check_required=not self.is_active()) - - params = self.get_params() + self._validate(kwargs) + params = self.default_params() params.update(kwargs) + self._validate_post(params) - err_msg = f"setting {self.type_name()} raised an error" + err_msg = f"setting {self.__class__.__name__} raised an error" self.call_method("set_params", handle_errors_message=err_msg, **params) def _serialize(self): @@ -72,26 +83,11 @@ class NonBondedInteraction(ScriptInterfaceHelper): raise Exception( "Subclasses of NonBondedInteraction must define the default_params() method.") - def is_active(self): - """Virtual method. - - """ - 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. + """All parameters that can be set. """ - raise Exception( - "Subclasses of NonBondedInteraction must define the valid_keys() method.") + return set(self._valid_parameters()) def required_keys(self): """Virtual method. @@ -111,10 +107,7 @@ IF LENNARD_JONES == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -137,30 +130,12 @@ IF LENNARD_JONES == 1: _so_name = "Interactions::InteractionLJ" - def is_active(self): - """Check if interaction is active. - - """ - return self.epsilon > 0. - 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. @@ -177,10 +152,7 @@ IF WCA == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -193,27 +165,12 @@ IF WCA == 1: _so_name = "Interactions::InteractionWCA" - def is_active(self): - return self.epsilon > 0. - 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. @@ -234,10 +191,7 @@ IF LENNARD_JONES_GENERIC == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -272,12 +226,6 @@ IF LENNARD_JONES_GENERIC == 1: _so_name = "Interactions::InteractionLJGen" - def is_active(self): - """Check if interaction is active. - - """ - return self.epsilon > 0. - def default_params(self): """Python dictionary of default parameters. @@ -287,23 +235,6 @@ IF LENNARD_JONES_GENERIC == 1: ELSE: return {} - def type_name(self): - """Name of interaction type. - - """ - return "GenericLennardJones" - - 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. @@ -339,30 +270,12 @@ IF LJCOS == 1: _so_name = "Interactions::InteractionLJcos" - def is_active(self): - """Check if interactions is active. - - """ - return self.epsilon > 0. - def default_params(self): """Python dictionary of default parameters. """ 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. @@ -378,10 +291,7 @@ IF LJCOS2 == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -397,30 +307,12 @@ IF LJCOS2 == 1: _so_name = "Interactions::InteractionLJcos2" - def is_active(self): - """Check if interactions is active. - - """ - return self.epsilon > 0. - def default_params(self): """Python dictionary of default parameters. """ 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", "width", "offset"} - def required_keys(self): """Parameters that have to be set. @@ -440,10 +332,7 @@ IF HAT == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -456,18 +345,9 @@ IF HAT == 1: _so_name = "Interactions::InteractionHat" - def is_active(self): - return self.F_max > 0. - 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"} @@ -480,10 +360,7 @@ IF GAY_BERNE: 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. + Set new parameters for the interaction. Parameters ---------- @@ -507,30 +384,12 @@ IF GAY_BERNE: _so_name = "Interactions::InteractionGayBerne" - def is_active(self): - """Check if interaction is active. - - """ - return self.eps > 0. - 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. @@ -546,10 +405,7 @@ IF TABULATED: 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. + Set new parameters for the interaction. Parameters ---------- @@ -566,30 +422,12 @@ IF TABULATED: _so_name = "Interactions::InteractionTabulated" - def is_active(self): - """Check if interaction is active. - - """ - return self.cutoff > 0 - def default_params(self): """Python dictionary of default parameters. """ return {} - 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. @@ -609,10 +447,7 @@ IF DPD: 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. + Set new parameters for the interaction. Parameters ---------- @@ -637,9 +472,6 @@ IF DPD: _so_name = "Interactions::InteractionDPD" - def is_active(self): - return self.r_cut > 0. or self.trans_r_cut > 0. - def default_params(self): return { "weight_function": 0, @@ -650,13 +482,6 @@ IF DPD: "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() @@ -669,10 +494,7 @@ IF SMOOTH_STEP == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -693,30 +515,12 @@ IF SMOOTH_STEP == 1: _so_name = "Interactions::InteractionSmoothStep" - def is_active(self): - """Check if interaction is active. - - """ - return self.eps > 0. and self.sig > 0. - 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 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. @@ -732,10 +536,7 @@ IF BMHTF_NACL == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -755,30 +556,12 @@ IF BMHTF_NACL == 1: _so_name = "Interactions::InteractionBMHTF" - def is_active(self): - """Check if interaction is active. - - """ - return self.a > 0. and self.c > 0. and self.d > 0. - def default_params(self): """Python dictionary of default parameters. """ return {} - def type_name(self): - """Name of interaction type. - - """ - return "BMHTF" - - 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. @@ -794,10 +577,7 @@ IF MORSE == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -813,30 +593,12 @@ IF MORSE == 1: _so_name = "Interactions::InteractionMorse" - def is_active(self): - """Check if interaction is active. - - """ - return self.eps > 0. - def default_params(self): """Python dictionary of default parameters. """ return {"cutoff": 0.} - def type_name(self): - """Name of interaction type. - - """ - return "Morse" - - 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. @@ -852,10 +614,7 @@ IF BUCKINGHAM == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -877,30 +636,12 @@ IF BUCKINGHAM == 1: _so_name = "Interactions::InteractionBuckingham" - def is_active(self): - """Check if interaction is active. - - """ - return self.a > 0. or self.b > 0. or self.d > 0. or self.shift > 0. - 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 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. @@ -916,10 +657,7 @@ IF SOFT_SPHERE == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -935,30 +673,12 @@ IF SOFT_SPHERE == 1: _so_name = "Interactions::InteractionSoftSphere" - def is_active(self): - """Check if interaction is active. - - """ - return self.a > 0. - def default_params(self): """Python dictionary of default parameters. """ return {"offset": 0.} - def type_name(self): - """Name of interaction type. - - """ - return "SoftSphere" - - 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. @@ -974,10 +694,7 @@ IF HERTZIAN == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -989,30 +706,12 @@ IF HERTZIAN == 1: _so_name = "Interactions::InteractionHertzian" - def is_active(self): - """Check if interaction is active. - - """ - return self.eps > 0. - def default_params(self): """Python dictionary of default parameters. """ return {} - def type_name(self): - """Name of interaction type. - - """ - return "Hertzian" - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"eps", "sig"} - def required_keys(self): """Parameters that have to be set. @@ -1028,10 +727,7 @@ IF GAUSSIAN == 1: 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. + Set new parameters for the interaction. Parameters ---------- @@ -1045,30 +741,12 @@ IF GAUSSIAN == 1: _so_name = "Interactions::InteractionGaussian" - def is_active(self): - """Check if interaction is active. - - """ - return self.eps > 0. - def default_params(self): """Python dictionary of default parameters. """ return {} - def type_name(self): - """Name of interaction type. - - """ - return "Gaussian" - - def valid_keys(self): - """All parameters that can be set. - - """ - return {"eps", "sig", "cutoff"} - def required_keys(self): """Parameters that have to be set. @@ -1084,10 +762,7 @@ IF THOLE: 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. + Set new parameters for the interaction. Parameters ---------- @@ -1106,18 +781,9 @@ IF THOLE: _so_name = "Interactions::InteractionThole" - def is_active(self): - return self.scaling_coeff != 0. - 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"} @@ -1155,6 +821,10 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): serialized.append((name, *obj._serialize())) return serialized + def reset(self): + for key in self._valid_parameters(): + getattr(self, key).deactivate() + @script_interface_register class NonBondedInteractions(ScriptInterfaceHelper): diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index fd76e16b4c3..effd77a3d26 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -47,6 +47,7 @@ namespace Interactions { template class InteractionPotentialInterface : public AutoParameters> { + /** @brief Particle type pair. */ std::array m_types = {-1, -1}; public: @@ -54,9 +55,16 @@ class InteractionPotentialInterface protected: using AutoParameters>::context; + /** @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) { @@ -76,6 +84,14 @@ class InteractionPotentialInterface } 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 == "bind_types") { auto types = get_value>(params, "_types"); if (types[0] > types[1]) { @@ -99,20 +115,21 @@ class InteractionPotentialInterface } void do_construct(VariantMap const ¶ms) final { - if (params.empty()) { - m_ia_si = std::make_shared(); - } else if (params.count("_types") != 0) { + if (params.count("_types") != 0) { do_call_method("bind_types", params); m_ia_si = std::make_shared(); copy_core_to_si(); } else { - context()->parallel_try_catch( - [this, ¶ms]() { make_new_instance(params); }); + 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]() { make_new_instance(params); }); + } } } - auto const &get_ia() const { return *m_ia_si; } - void copy_si_to_core() { assert(m_ia_si != nullptr); auto const key = get_ia_param_key(m_types[0], m_types[1]); @@ -143,6 +160,9 @@ class InteractionWCA : public InteractionPotentialInterface<::WCA_Parameters> { }); } + 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"); @@ -179,18 +199,20 @@ class InteractionLJ : public InteractionPotentialInterface<::LJ_Parameters> { } void make_new_instance(VariantMap const ¶ms) override { - if (auto const *shift = boost::get(¶ms.at("shift"))) { - if (*shift != "auto") { + 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"); } - m_ia_si = make_shared_from_args( - params, "epsilon", "sigma", "cutoff", "offset", "min"); - } else { - m_ia_si = make_shared_from_args( - params, "epsilon", "sigma", "cutoff", "offset", "min", "shift"); + 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(); } } }; @@ -295,6 +317,9 @@ class InteractionLJcos2 }); } + 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( @@ -327,6 +352,9 @@ class InteractionHertzian make_autoparameter(&CoreInteraction::sig, "sig"), }); } + + 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"); @@ -350,6 +378,7 @@ class InteractionGaussian make_autoparameter(&CoreInteraction::cut, "cutoff"), }); } + void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( params, "eps", "sig", "cutoff"); @@ -509,6 +538,8 @@ class InteractionGayBerne }); } + std::string inactive_parameter() const override { return "cut"; } + void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( @@ -535,6 +566,8 @@ class InteractionTabulated }); } + 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>( @@ -580,6 +613,8 @@ class InteractionDPD : public InteractionPotentialInterface<::DPD_Parameters> { std::ignore = get_ptr_offset(); // for code coverage } + std::string inactive_parameter() const override { return "r_cut"; } + void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( @@ -604,6 +639,10 @@ class InteractionThole make_autoparameter(&CoreInteraction::q1q2, "q1q2"), }); } + + 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"); @@ -801,75 +840,65 @@ class NonBondedInteractionHandle 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); + set_member(m_wca, "wca", "Interactions::InteractionWCA", params); #endif #ifdef LENNARD_JONES - set_member(m_lj, "lennard_jones", - "Interactions::InteractionLJ", params); + set_member(m_lj, "lennard_jones", "Interactions::InteractionLJ", params); #endif #ifdef LENNARD_JONES_GENERIC - set_member(m_ljgen, "generic_lennard_jones", - "Interactions::InteractionLJGen", params); + set_member(m_ljgen, "generic_lennard_jones", + "Interactions::InteractionLJGen", params); #endif #ifdef LJCOS - set_member(m_ljcos, "lennard_jones_cos", - "Interactions::InteractionLJcos", params); + set_member(m_ljcos, "lennard_jones_cos", "Interactions::InteractionLJcos", + params); #endif #ifdef LJCOS2 - set_member(m_ljcos2, "lennard_jones_cos2", - "Interactions::InteractionLJcos2", params); + set_member(m_ljcos2, "lennard_jones_cos2", + "Interactions::InteractionLJcos2", params); #endif #ifdef HERTZIAN - set_member( - m_hertzian, "hertzian", "Interactions::InteractionHertzian", params); + set_member(m_hertzian, "hertzian", "Interactions::InteractionHertzian", + params); #endif #ifdef GAUSSIAN - set_member( - m_gaussian, "gaussian", "Interactions::InteractionGaussian", params); + set_member(m_gaussian, "gaussian", "Interactions::InteractionGaussian", + params); #endif #ifdef BMHTF_NACL - set_member(m_bmhtf, "bmhtf", - "Interactions::InteractionBMHTF", params); + set_member(m_bmhtf, "bmhtf", "Interactions::InteractionBMHTF", params); #endif #ifdef MORSE - set_member(m_morse, "morse", - "Interactions::InteractionMorse", params); + set_member(m_morse, "morse", "Interactions::InteractionMorse", params); #endif #ifdef BUCKINGHAM - set_member(m_buckingham, "buckingham", - "Interactions::InteractionBuckingham", - params); + set_member(m_buckingham, "buckingham", + "Interactions::InteractionBuckingham", params); #endif #ifdef SOFT_SPHERE - set_member(m_soft_sphere, "soft_sphere", - "Interactions::InteractionSoftSphere", - params); + set_member(m_soft_sphere, "soft_sphere", + "Interactions::InteractionSoftSphere", params); #endif #ifdef HAT - set_member(m_hat, "hat", "Interactions::InteractionHat", - params); + set_member(m_hat, "hat", "Interactions::InteractionHat", params); #endif #ifdef GAY_BERNE - set_member( - m_gay_berne, "gay_berne", "Interactions::InteractionGayBerne", params); + set_member(m_gay_berne, "gay_berne", "Interactions::InteractionGayBerne", + params); #endif #ifdef TABULATED - set_member( - m_tabulated, "tabulated", "Interactions::InteractionTabulated", params); + set_member(m_tabulated, "tabulated", "Interactions::InteractionTabulated", + params); #endif #ifdef DPD - set_member(m_dpd, "dpd", "Interactions::InteractionDPD", - params); + set_member(m_dpd, "dpd", "Interactions::InteractionDPD", params); #endif #ifdef THOLE - set_member(m_thole, "thole", - "Interactions::InteractionThole", params); + set_member(m_thole, "thole", "Interactions::InteractionThole", params); #endif #ifdef SMOOTH_STEP - set_member(m_smooth_step, "smooth_step", - "Interactions::InteractionSmoothStep", - params); + set_member(m_smooth_step, "smooth_step", + "Interactions::InteractionSmoothStep", params); #endif } diff --git a/testsuite/python/collision_detection.py b/testsuite/python/collision_detection.py index 16b4236d332..308dddf28d0 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 48233a9b6df..775669ef745 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 5f7d0fbacd3..f79ac3db0cb 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 37f386bbc8d..4e3a6499909 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 4f3b19a8d10..99af4c509b9 100644 --- a/testsuite/python/dds-and-bh-gpu.py +++ b/testsuite/python/dds-and-bh-gpu.py @@ -68,8 +68,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 b39f9d9466d..3c2993502cd 100644 --- a/testsuite/python/dipolar_direct_summation.py +++ b/testsuite/python/dipolar_direct_summation.py @@ -150,8 +150,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/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index e04c0b509f4..793dac07572 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -28,9 +28,7 @@ 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 @@ -114,9 +112,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)) + f"{interClass.__name__}: value set and value gotten back " + f"differ for particle types {partType1} and {partType2}: " + f"{params} vs. {outParams}") self.parameterKeys(outInter) return func @@ -162,27 +160,42 @@ def func(self): "k2": 5.0, "mu": 2.0, "nu": 1.0}, "gay_berne") - @utx.skipIfMissingFeatures(["LENNARD_JONES", "WCA"]) - def test_set_params(self): + @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(), - {'shift': 0., 'sigma': 0., 'epsilon': 0., - 'cutoff': -1., 'offset': 0., 'min': 0.}) + 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.) + 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.) - wca.set_params(epsilon=3.) - self.assertEqual( - self.system.non_bonded_inter[0, 0].wca.get_params()["epsilon"], 3.) - self.assertEqual(wca.get_params()["epsilon"], 3.) + self.assertEqual(wca.get_params()["sigma"], 3.) self.system.non_bonded_inter.reset() - wca.set_params(epsilon=4.) + 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.) @@ -191,22 +204,16 @@ def test_set_params(self): @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, "LennardJonesInteraction 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, "LennardJonesInteraction 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 a valid LennardJonesInteraction parameter"): 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 a valid LennardJonesInteraction parameter"): 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"): @@ -220,11 +227,14 @@ 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 check_potential_exceptions(self, ia_class, params, check_keys): for key in check_keys: @@ -235,6 +245,7 @@ def check_potential_exceptions(self, ia_class, params, check_keys): @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.}, @@ -245,11 +256,11 @@ def test_lj_exceptions(self): self.check_potential_exceptions( espressomd.interactions.LennardJonesInteraction, {"epsilon": 1., "sigma": 1., "cutoff": 1.5, "shift": 0.2}, - ("epsilon", "sigma")) + ("epsilon", "sigma", "cutoff")) @utx.skipIfMissingFeatures("LENNARD_JONES_GENERIC") def test_ljgen_exceptions(self): - check_keys = ("epsilon", "sigma") + 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"]): @@ -260,7 +271,7 @@ def test_ljgen_exceptions(self): espressomd.interactions.GenericLennardJonesInteraction, params, check_keys) - with self.assertRaisesRegex(ValueError, f"Generic LJ parameter 'shift' has to be 'auto' or a float"): + 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( @@ -271,21 +282,23 @@ 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")) + ("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")) + ("epsilon", "sigma", "width")) @utx.skipIfMissingFeatures("HERTZIAN") def test_hertzian_exceptions(self): self.check_potential_exceptions( espressomd.interactions.HertzianInteraction, {"eps": 1., "sig": 1.}, - ("eps",) + ("eps", "sig") ) @utx.skipIfMissingFeatures("GAUSSIAN") @@ -293,7 +306,7 @@ def test_gaussian_exceptions(self): self.check_potential_exceptions( espressomd.interactions.GaussianInteraction, {"eps": 1., "sig": 1., "cutoff": 1.5}, - ("eps", "sig") + ("eps", "sig", "cutoff") ) @utx.skipIfMissingFeatures("BMHTF_NACL") @@ -301,7 +314,7 @@ 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") + ("a", "c", "d", "cutoff") ) @utx.skipIfMissingFeatures("MORSE") @@ -309,7 +322,7 @@ def test_morse_exceptions(self): self.check_potential_exceptions( espressomd.interactions.MorseInteraction, {"eps": 1., "alpha": 3., "rmin": 0.1, "cutoff": 1.2}, - ("eps",) + ("eps", "cutoff") ) @utx.skipIfMissingFeatures("BUCKINGHAM") @@ -318,7 +331,7 @@ def test_buckingham_exceptions(self): espressomd.interactions.BuckinghamInteraction, {"a": 2., "b": 3., "c": 5., "d": 4., "discont": 1., "cutoff": 2., "shift": 0.1}, - ("a", "b", "c", "d") + ("a", "b", "c", "d", "cutoff") ) @utx.skipIfMissingFeatures("SOFT_SPHERE") @@ -326,7 +339,7 @@ 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") + ("a", "offset", "cutoff") ) @utx.skipIfMissingFeatures("HAT") @@ -334,7 +347,7 @@ def test_hat_exceptions(self): self.check_potential_exceptions( espressomd.interactions.HatInteraction, {"F_max": 10., "cutoff": 1.}, - ("F_max",) + ("F_max", "cutoff") ) @utx.skipIfMissingFeatures("SMOOTH_STEP") @@ -342,7 +355,7 @@ 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",) + ("eps", "sig", "cutoff") ) 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 aef41515bc5..1dd3047feef 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -161,7 +161,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)) @@ -231,12 +231,8 @@ 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) - # add inactive interaction - system.non_bonded_inter[4, 4].lennard_jones.set_params( - epsilon=1.4, sigma=1.2, cutoff=1.5, shift=0.2, offset=0.1, min=0.2) - system.non_bonded_inter[4, 4].lennard_jones.set_params(cutoff=-2.) # bonded interactions harmonic_bond = espressomd.interactions.HarmonicBond(r_0=0.0, k=1.0) diff --git a/testsuite/python/test_checkpoint.py b/testsuite/python/test_checkpoint.py index 0aaba75e277..e9182c9bc7d 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -333,16 +333,12 @@ def test_integrator_SDM(self): def test_non_bonded_inter(self): params1 = system.non_bonded_inter[0, 0].lennard_jones.get_params() params2 = system.non_bonded_inter[3, 0].lennard_jones.get_params() - params3 = system.non_bonded_inter[4, 4].lennard_jones.get_params() reference1 = {'shift': 0.1, 'sigma': 1.3, 'epsilon': 1.2, 'cutoff': 2.0, 'offset': 0.0, 'min': 0.0} reference2 = {'shift': 0.1, 'sigma': 1.7, 'epsilon': 1.2, 'cutoff': 2.0, 'offset': 0.0, 'min': 0.0} - reference3 = {'shift': 0.2, 'sigma': 1.2, 'epsilon': 1.4, - 'cutoff': -2.0, 'offset': 0.1, 'min': 0.2} self.assertEqual(params1, reference1) self.assertEqual(params2, reference2) - self.assertEqual(params3, reference3) def test_bonded_inter(self): # check the ObjectHandle was correctly initialized (including MPI) @@ -630,7 +626,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): From 8078029bbf595ca6e337e4155e0bb18427a18c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 12 Sep 2022 13:22:30 +0200 Subject: [PATCH 21/23] Decythonize non-bonded interactions --- .gitlab-ci.yml | 2 + src/core/nonbonded_interactions/thole.hpp | 2 - src/python/espressomd/interactions.pxd | 6 - src/python/espressomd/interactions.pyx | 1499 ++++++++--------- .../interactions/NonBondedInteraction.hpp | 73 +- .../interactions/NonBondedInteractions.hpp | 13 +- testsuite/python/dpd.py | 2 +- .../python/interactions_bonded_interface.py | 13 + .../interactions_non-bonded_interface.py | 70 +- testsuite/python/save_checkpoint.py | 9 + testsuite/python/tabulated.py | 4 + testsuite/python/test_checkpoint.py | 11 +- 12 files changed, 797 insertions(+), 907 deletions(-) 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/src/core/nonbonded_interactions/thole.hpp b/src/core/nonbonded_interactions/thole.hpp index 9c9f4acdebb..041840b8bb3 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.hpp" diff --git a/src/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd index 6d5ca5f26fc..e8dd0dd7013 100644 --- a/src/python/espressomd/interactions.pxd +++ b/src/python/espressomd/interactions.pxd @@ -23,12 +23,6 @@ from libc cimport stdint from .thermostat cimport thermalized_bond -include "myconfig.pxi" - -# force include of config.hpp -cdef extern from "config.hpp": - pass - cdef extern from "script_interface/interactions/bonded.hpp": int bonded_ia_params_zero_based_type(int bond_id) except + diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 36c2b25d1df..c1510cc96be 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -18,8 +18,8 @@ # cimport cpython.object -include "myconfig.pxi" from . import utils +from . import code_features from .script_interface import ScriptObjectMap, ScriptInterfaceHelper, script_interface_register @@ -36,45 +36,34 @@ class NonBondedInteraction(ScriptInterfaceHelper): _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: - self._validate(kwargs) params = self.default_params() params.update(kwargs) super().__init__(**params) - self._validate_post(params) def __str__(self): return f'{self.__class__.__name__}({self.get_params()})' - def _validate(self, params): - for key in self.required_keys(): - if key not in params: - raise RuntimeError( - f"{self.__class__.__name__} parameter '{key}' is missing") - - def _validate_post(self, params): - valid_parameters = self.valid_keys() - for key in params: - if key != "_types" and key not in valid_parameters: - raise RuntimeError( - f"Parameter '{key}' is not a valid {self.__class__.__name__} parameter") - def set_params(self, **kwargs): """Set new parameters. """ - self._validate(kwargs) params = self.default_params() params.update(kwargs) - self._validate_post(params) err_msg = f"setting {self.__class__.__name__} raised an error" self.call_method("set_params", handle_errors_message=err_msg, **params) - def _serialize(self): - return (self.__class__.__name__, self.get_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) def default_params(self): """Virtual method. @@ -83,709 +72,607 @@ class NonBondedInteraction(ScriptInterfaceHelper): raise Exception( "Subclasses of NonBondedInteraction must define the default_params() method.") - def valid_keys(self): - """All parameters that can be set. - """ - return set(self._valid_parameters()) +@script_interface_register +class LennardJonesInteraction(NonBondedInteraction): + """ + Standard 6-12 Lennard-Jones potential. - def required_keys(self): - """Virtual method. + Methods + ------- + set_params() + Set new parameters for the interaction. - """ - raise Exception( - "Subclasses of NonBondedInteraction must define the required_keys() method.") + 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" -IF LENNARD_JONES == 1: + def default_params(self): + """Python dictionary of default parameters. - @script_interface_register - class LennardJonesInteraction(NonBondedInteraction): """ - Standard 6-12 Lennard-Jones potential. + return {"offset": 0., "min": 0.} - 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. +@script_interface_register +class WCAInteraction(NonBondedInteraction): + """ + Standard 6-12 Weeks-Chandler-Andersen potential. - """ + Methods + ------- + set_params() + Set new parameters for the interaction. - _so_name = "Interactions::InteractionLJ" + Parameters + ---------- + epsilon : :obj:`float` + Magnitude of the interaction. + sigma : :obj:`float` + Interaction length scale. - def default_params(self): - """Python dictionary of default parameters. + """ - """ - return {"offset": 0., "min": 0.} + _so_name = "Interactions::InteractionWCA" + _so_feature = "WCA" - def required_keys(self): - """Parameters that have to be set. + def default_params(self): + """Python dictionary of default parameters. - """ - return {"epsilon", "sigma", "cutoff", "shift"} + """ + return {} -IF WCA == 1: + @property + def cutoff(self): + return self.call_method("get_cutoff") - @script_interface_register - class WCAInteraction(NonBondedInteraction): - """ - Standard 6-12 Weeks-Chandler-Andersen potential. - Methods - ------- - set_params() - Set new parameters for the interaction. +@script_interface_register +class GenericLennardJonesInteraction(NonBondedInteraction): + """ + Generalized Lennard-Jones potential. - Parameters - ---------- - epsilon : :obj:`float` - Magnitude of the interaction. - sigma : :obj:`float` - Interaction length scale. + 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::InteractionWCA" + """ - def default_params(self): - """Python dictionary of default parameters. + _so_name = "Interactions::InteractionLJGen" + _so_feature = "LENNARD_JONES_GENERIC" - """ - return {} + def default_params(self): + """Python dictionary of default parameters. - def required_keys(self): - """Parameters that have to be set. + """ + if code_features.has_features("LJGEN_SOFTCORE"): + return {"delta": 0., "lam": 1.} + return {} - """ - return {"epsilon", "sigma"} - @property - def cutoff(self): - return self.call_method("get_cutoff") +@script_interface_register +class LennardJonesCosInteraction(NonBondedInteraction): + """Lennard-Jones cosine interaction. -IF LENNARD_JONES_GENERIC == 1: + 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. - @script_interface_register - class GenericLennardJonesInteraction(NonBondedInteraction): - """ - Generalized Lennard-Jones potential. + 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. + """ - Methods - ------- - set_params() - Set new parameters for the interaction. + _so_name = "Interactions::InteractionLJcos" + _so_feature = "LJCOS" - 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. + def default_params(self): + """Python dictionary of default parameters. """ + return {"offset": 0.} - _so_name = "Interactions::InteractionLJGen" - def default_params(self): - """Python dictionary of default parameters. +@script_interface_register +class LennardJonesCos2Interaction(NonBondedInteraction): + """Second variant of the Lennard-Jones cosine interaction. - """ - IF LJGEN_SOFTCORE: - return {"delta": 0., "lam": 1.} - ELSE: - return {} + Methods + ------- + set_params() + Set new parameters for the interaction. - def required_keys(self): - """Parameters that have to be set. + 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. + """ - """ - return {"epsilon", "sigma", "cutoff", - "shift", "offset", "e1", "e2", "b1", "b2"} + _so_name = "Interactions::InteractionLJcos2" + _so_feature = "LJCOS2" -IF LJCOS == 1: + def default_params(self): + """Python dictionary of default parameters. - @script_interface_register - class LennardJonesCosInteraction(NonBondedInteraction): - """Lennard-Jones cosine interaction. + """ + return {"offset": 0.} - 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. + @property + def cutoff(self): + return self.call_method("get_cutoff") - 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" - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {"offset": 0.} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"epsilon", "sigma", "cutoff"} - -IF LJCOS2 == 1: - - @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. - """ +@script_interface_register +class HatInteraction(NonBondedInteraction): + """Hat interaction. - _so_name = "Interactions::InteractionLJcos2" + Methods + ------- + set_params() + Set new parameters for the interaction. - def default_params(self): - """Python dictionary of default parameters. + Parameters + ---------- + F_max : :obj:`float` + Magnitude of the interaction. + cutoff : :obj:`float` + Cutoff distance of the interaction. - """ - return {"offset": 0.} + """ - def required_keys(self): - """Parameters that have to be set. + _so_name = "Interactions::InteractionHat" + _so_feature = "HAT" - """ - return {"epsilon", "sigma", "width"} + def default_params(self): + return {} + + +@script_interface_register +class GayBerneInteraction(NonBondedInteraction): + """Gay--Berne interaction. - @property - def cutoff(self): - return self.call_method("get_cutoff") + Methods + ------- + set_params() + Set new parameters for the interaction. -IF HAT == 1: + 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. - @script_interface_register - class HatInteraction(NonBondedInteraction): - """Hat interaction. + """ - Methods - ------- - set_params() - Set new parameters for the interaction. + _so_name = "Interactions::InteractionGayBerne" + _so_feature = "GAY_BERNE" - Parameters - ---------- - F_max : :obj:`float` - Magnitude of the interaction. - cutoff : :obj:`float` - Cutoff distance of the interaction. + def default_params(self): + """Python dictionary of default parameters. """ + return {} - _so_name = "Interactions::InteractionHat" - def default_params(self): - return {} +@script_interface_register +class TabulatedNonBonded(NonBondedInteraction): + """Tabulated interaction. - def required_keys(self): - return {"F_max", "cutoff"} + Methods + ------- + set_params() + Set new parameters for the interaction. -IF GAY_BERNE: + 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. - @script_interface_register - class GayBerneInteraction(NonBondedInteraction): - """Gay--Berne interaction. + """ - Methods - ------- - set_params() - Set new parameters for the interaction. + _so_name = "Interactions::InteractionTabulated" + _so_feature = "TABULATED" - 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. + def default_params(self): + """Python dictionary of default parameters. """ + return {} - _so_name = "Interactions::InteractionGayBerne" + @property + def cutoff(self): + return self.call_method("get_cutoff") - def default_params(self): - """Python dictionary of default parameters. - """ - return {} +@script_interface_register +class DPDInteraction(NonBondedInteraction): + """DPD interaction. - def required_keys(self): - """Parameters that have to be set. + Methods + ------- + set_params() + Set new parameters for the interaction. - """ - return {"eps", "sig", "cut", "k1", "k2", "mu", "nu"} + 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 -IF TABULATED: + """ - @script_interface_register - class TabulatedNonBonded(NonBondedInteraction): - """Tabulated interaction. + _so_name = "Interactions::InteractionDPD" + _so_feature = "DPD" - Methods - ------- - set_params() - Set new parameters for the interaction. + 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} - 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. - """ +@script_interface_register +class SmoothStepInteraction(NonBondedInteraction): + """Smooth step interaction. - _so_name = "Interactions::InteractionTabulated" + Methods + ------- + set_params() + Set new parameters for the interaction. - def default_params(self): - """Python dictionary of default parameters. + 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. - """ - return {} + """ - def required_keys(self): - """Parameters that have to be set. + _so_name = "Interactions::InteractionSmoothStep" + _so_feature = "SMOOTH_STEP" - """ - return {"min", "max", "energy", "force"} + def default_params(self): + """Python dictionary of default parameters. - @property - def cutoff(self): - return self.call_method("get_cutoff") + """ + return {"n": 10, "k0": 0., "sig": 0.} -IF DPD: - @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 +@script_interface_register +class BMHTFInteraction(NonBondedInteraction): + """BMHTF interaction. - """ + Methods + ------- + set_params() + Set new parameters for the interaction. - _so_name = "Interactions::InteractionDPD" + 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. - 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 required_keys(self): - return set() - -IF SMOOTH_STEP == 1: - - @script_interface_register - class SmoothStepInteraction(NonBondedInteraction): - """Smooth step interaction. - - Methods - ------- - set_params() - Set new parameters for the interaction. + _so_name = "Interactions::InteractionBMHTF" + _so_feature = "BMHTF_NACL" - 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. + def default_params(self): + """Python dictionary of default parameters. """ + return {} - _so_name = "Interactions::InteractionSmoothStep" - def default_params(self): - """Python dictionary of default parameters. +@script_interface_register +class MorseInteraction(NonBondedInteraction): + """Morse interaction. - """ - return {"n": 10, "k0": 0., "sig": 0.} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"d", "eps", "cutoff"} - -IF BMHTF_NACL == 1: - - @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" - - def default_params(self): - """Python dictionary of default parameters. + Methods + ------- + set_params() + Set new parameters for the interaction. - """ - return {} + 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. - def required_keys(self): - """Parameters that have to be set. + """ - """ - return {"a", "b", "c", "d", "sig", "cutoff"} + _so_name = "Interactions::InteractionMorse" + _so_feature = "MORSE" -IF MORSE == 1: - - @script_interface_register - class MorseInteraction(NonBondedInteraction): - """Morse interaction. - - Methods - ------- - set_params() - Set new parameters for the interaction. + def default_params(self): + """Python dictionary of default parameters. - 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. """ + return {"cutoff": 0.} - _so_name = "Interactions::InteractionMorse" - - def default_params(self): - """Python dictionary of default parameters. - """ - return {"cutoff": 0.} +@script_interface_register +class BuckinghamInteraction(NonBondedInteraction): + """Buckingham interaction. - def required_keys(self): - """Parameters that have to be set. + Methods + ------- + set_params() + Set new parameters for the interaction. - """ - return {"eps", "alpha", "rmin"} + 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. -IF BUCKINGHAM == 1: + """ - @script_interface_register - class BuckinghamInteraction(NonBondedInteraction): - """Buckingham interaction. + _so_name = "Interactions::InteractionBuckingham" + _so_feature = "BUCKINGHAM" - Methods - ------- - set_params() - Set new parameters for the interaction. + def default_params(self): + """Python dictionary of default parameters. - 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. """ + return {"b": 0., "shift": 0.} - _so_name = "Interactions::InteractionBuckingham" - - def default_params(self): - """Python dictionary of default parameters. - """ - return {"b": 0., "shift": 0.} +@script_interface_register +class SoftSphereInteraction(NonBondedInteraction): + """Soft sphere interaction. - def required_keys(self): - """Parameters that have to be set. + Methods + ------- + set_params() + Set new parameters for the interaction. - """ - return {"a", "c", "d", "discont", "cutoff"} + 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. -IF SOFT_SPHERE == 1: + """ - @script_interface_register - class SoftSphereInteraction(NonBondedInteraction): - """Soft sphere interaction. + _so_name = "Interactions::InteractionSoftSphere" + _so_feature = "SOFT_SPHERE" - Methods - ------- - set_params() - Set new parameters for the interaction. + def default_params(self): + """Python dictionary of default parameters. - 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. """ + return {"offset": 0.} - _so_name = "Interactions::InteractionSoftSphere" - - def default_params(self): - """Python dictionary of default parameters. - """ - return {"offset": 0.} +@script_interface_register +class HertzianInteraction(NonBondedInteraction): + """Hertzian interaction. - def required_keys(self): - """Parameters that have to be set. + Methods + ------- + set_params() + Set new parameters for the interaction. - """ - return {"a", "n", "cutoff"} + Parameters + ---------- + eps : :obj:`float` + Magnitude of the interaction. + sig : :obj:`float` + Interaction length scale. -IF HERTZIAN == 1: + """ - @script_interface_register - class HertzianInteraction(NonBondedInteraction): - """Hertzian interaction. + _so_name = "Interactions::InteractionHertzian" + _so_feature = "HERTZIAN" - Methods - ------- - set_params() - Set new parameters for the interaction. + def default_params(self): + """Python dictionary of default parameters. - Parameters - ---------- - eps : :obj:`float` - Magnitude of the interaction. - sig : :obj:`float` - Interaction length scale. """ + return {} - _so_name = "Interactions::InteractionHertzian" - - def default_params(self): - """Python dictionary of default parameters. - """ - return {} +@script_interface_register +class GaussianInteraction(NonBondedInteraction): + """Gaussian interaction. - def required_keys(self): - """Parameters that have to be set. + Methods + ------- + set_params() + Set new parameters for the interaction. - """ - return {"eps", "sig"} + Parameters + ---------- + eps : :obj:`float` + Overlap energy epsilon. + sig : :obj:`float` + Variance sigma of the Gaussian interaction. + cutoff : :obj:`float` + Cutoff distance of the interaction. -IF GAUSSIAN == 1: + """ - @script_interface_register - class GaussianInteraction(NonBondedInteraction): - """Gaussian interaction. + _so_name = "Interactions::InteractionGaussian" + _so_feature = "GAUSSIAN" - Methods - ------- - set_params() - Set new parameters for the interaction. + def default_params(self): + """Python dictionary of default parameters. - Parameters - ---------- - eps : :obj:`float` - Overlap energy epsilon. - sig : :obj:`float` - Variance sigma of the Gaussian interaction. - cutoff : :obj:`float` - Cutoff distance of the interaction. """ + return {} - _so_name = "Interactions::InteractionGaussian" - - def default_params(self): - """Python dictionary of default parameters. - - """ - return {} - - def required_keys(self): - """Parameters that have to be set. - - """ - return {"eps", "sig", "cutoff"} - -IF THOLE: - @script_interface_register - class TholeInteraction(NonBondedInteraction): - """Thole interaction. +@script_interface_register +class TholeInteraction(NonBondedInteraction): + """Thole interaction. - Methods - ------- - set_params() - Set new parameters for the 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. - """ + 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" + """ - def default_params(self): - return {} + _so_name = "Interactions::InteractionThole" + _so_feature = "THOLE" - def required_keys(self): - return {"scaling_coeff", "q1q2"} + def default_params(self): + return {} @script_interface_register @@ -802,29 +689,27 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): return globals()[obj.__class__.__name__]( _types=self.call_method("get_types"), **obj.get_params()) - def __init__(self, *args, **kwargs): - if "sip" in kwargs: - super().__init__(**kwargs) - return - if len(args): - _type1, _type2 = args - else: - _type1, _type2 = kwargs.pop("_types") - 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.") - super().__init__(_types=[_type1, _type2], **kwargs) - def _serialize(self): serialized = [] for name, obj in self.get_params().items(): - serialized.append((name, *obj._serialize())) + 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): @@ -870,10 +755,7 @@ class NonBondedInteractions(ScriptInterfaceHelper): def __setstate__(self, params): for types, kwargs in params["state"]: - objects = {} - for name, class_name, obj_params in kwargs: - objects[name] = globals()[class_name](**obj_params) - obj = NonBondedInteractionHandle(_types=types, **objects) + obj = NonBondedInteractionHandle._restore_object(types, kwargs) self.call_method("insert", key=types, object=obj) @classmethod @@ -908,6 +790,10 @@ class BondedInteraction(ScriptInterfaceHelper): kwargs = args[0] args = [] + feature = self.__class__.__dict__.get("_so_feature") + if feature is not None: + code_features.assert_features(feature) + 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 @@ -1019,46 +905,6 @@ class BondedInteraction(ScriptInterfaceHelper): 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): @@ -1129,67 +975,65 @@ class HarmonicBond(BondedInteraction): return {"r_cut": 0.} -if ELECTROSTATICS: - - @script_interface_register - class BondedCoulomb(BondedInteraction): - - """ - Bonded Coulomb bond. +@script_interface_register +class BondedCoulomb(BondedInteraction): - Parameters - ---------- + """ + Bonded Coulomb bond. - prefactor : :obj:`float` - Coulomb prefactor of the bonded Coulomb interaction. - """ + Parameters + ---------- - _so_name = "Interactions::BondedCoulomb" + prefactor : :obj:`float` + Coulomb prefactor of the bonded Coulomb interaction. + """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + _so_name = "Interactions::BondedCoulomb" + _so_feature = "ELECTROSTATICS" - def type_number(self): - return BONDED_IA_BONDED_COULOMB + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) - def type_name(self): - return "BONDED_COULOMB" + def type_number(self): + return BONDED_IA_BONDED_COULOMB - def get_default_params(self): - """Gets default values of optional parameters. + def type_name(self): + return "BONDED_COULOMB" - """ - return {} + def get_default_params(self): + """Gets default values of optional parameters. + """ + return {} -if ELECTROSTATICS: - @script_interface_register - class BondedCoulombSRBond(BondedInteraction): +@script_interface_register +class BondedCoulombSRBond(BondedInteraction): - """ - Bonded Coulomb short range bond. Calculates the short range part of - Coulomb interactions. + """ + Bonded Coulomb short range bond. Calculates the short range part of + Coulomb interactions. - Parameters - ---------- + 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. - """ + 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_name = "Interactions::BondedCoulombSR" + _so_feature = "ELECTROSTATICS" - def type_number(self): - return BONDED_IA_BONDED_COULOMB_SR + def type_number(self): + return BONDED_IA_BONDED_COULOMB_SR - def type_name(self): - return "BONDED_COULOMB_SR" + def type_name(self): + return "BONDED_COULOMB_SR" - def get_default_params(self): - return {} + def get_default_params(self): + return {} @script_interface_register @@ -1262,49 +1106,44 @@ class ThermalizedBond(BondedInteraction): return {"r_cut": 0., "seed": None} -IF BOND_CONSTRAINT == 1: - - @script_interface_register - class RigidBond(BondedInteraction): - - """ - Rigid bond. +@script_interface_register +class RigidBond(BondedInteraction): - Parameters - ---------- - r : :obj:`float` - Bond length. - ptol : :obj:`float`, optional - Tolerance for positional deviations. - vtop : :obj:`float`, optional - Tolerance for velocity deviations. + """ + 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) + _so_name = "Interactions::RigidBond" + _so_feature = "BOND_CONSTRAINT" - def type_number(self): - return BONDED_IA_RIGID_BOND + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) - def type_name(self): - """Name of interaction type. + def type_number(self): + return BONDED_IA_RIGID_BOND - """ - return "RIGID" + def type_name(self): + """Name of interaction type. - def get_default_params(self): - """Gets default values of optional parameters. + """ + return "RIGID" - """ - # TODO rationality of Default Parameters has to be checked - return {"ptol": 0.001, "vtol": 0.001} + def get_default_params(self): + """Gets default values of optional parameters. -ELSE: - class RigidBond(BondedInteractionNotDefined): - name = "RIGID" + """ + # TODO rationality of Default Parameters has to be checked + return {"ptol": 0.001, "vtol": 0.001} @script_interface_register @@ -1350,164 +1189,148 @@ class Dihedral(BondedInteraction): return {} -IF TABULATED: - class _TabulatedBase(BondedInteraction): - - """ - Parent class for tabulated bonds. +@script_interface_register +class TabulatedDistance(BondedInteraction): - Parameters - ---------- + """ + Tabulated bond length. - 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. + 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. - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + """ - def type_number(self): - return "BONDED_IA_TABULATED" + _so_name = "Interactions::TabulatedDistanceBond" + _so_feature = "TABULATED" - def type_name(self): - """Name of interaction type. + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) - """ - return "TABULATED_BOND" + def type_number(self): + return BONDED_IA_TABULATED_DISTANCE - def get_default_params(self): - """Gets default values of optional parameters. + def type_name(self): + """Name of interaction type. - """ - return {} + """ + return "TABULATED_DISTANCE" - @script_interface_register - class TabulatedDistance(_TabulatedBase): + def get_default_params(self): + """Gets default values of optional parameters. """ - Tabulated bond length. - - Parameters - ---------- + return {} - 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. - """ +@script_interface_register +class TabulatedAngle(BondedInteraction): - _so_name = "Interactions::TabulatedDistanceBond" + """ + Tabulated bond angle. - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + Parameters + ---------- - def type_number(self): - return BONDED_IA_TABULATED_DISTANCE + 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`. - def type_name(self): - """Name of interaction type. + """ - """ - return "TABULATED_DISTANCE" + _so_name = "Interactions::TabulatedAngleBond" + _so_feature = "TABULATED" - @script_interface_register - class TabulatedAngle(_TabulatedBase): + pi = 3.14159265358979 - """ - Tabulated bond angle. + def __init__(self, *args, **kwargs): + if len(args) == 0: + kwargs.update({"min": 0., "max": self.pi}) + super().__init__(*args, **kwargs) - Parameters - ---------- + def type_number(self): + return BONDED_IA_TABULATED_ANGLE - 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`. + def type_name(self): + """Name of interaction type. """ + return "TABULATED_ANGLE" - _so_name = "Interactions::TabulatedAngleBond" - - pi = 3.14159265358979 + def validate_params(self, params): + """Check that parameters are valid. - def __init__(self, *args, **kwargs): - if len(args) == 0: - kwargs.update({"min": 0., "max": self.pi}) - super().__init__(*args, **kwargs) + """ + 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}") - def type_number(self): - return BONDED_IA_TABULATED_ANGLE + def get_default_params(self): + """Gets default values of optional parameters. - def type_name(self): - """Name of interaction type. + """ + return {} - """ - return "TABULATED_ANGLE" - def validate_params(self, params): - """Check that parameters are valid. +@script_interface_register +class TabulatedDihedral(BondedInteraction): - """ - 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}") + """ + Tabulated bond dihedral. - @script_interface_register - class TabulatedDihedral(_TabulatedBase): + Parameters + ---------- - """ - Tabulated bond dihedral. + 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`. - 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" - """ + pi = 3.14159265358979 - _so_name = "Interactions::TabulatedDihedralBond" + def __init__(self, *args, **kwargs): + if len(args) == 0: + kwargs.update({"min": 0., "max": 2. * self.pi}) + super().__init__(*args, **kwargs) - pi = 3.14159265358979 + def type_number(self): + return BONDED_IA_TABULATED_DIHEDRAL - def __init__(self, *args, **kwargs): - if len(args) == 0: - kwargs.update({"min": 0., "max": 2. * self.pi}) - super().__init__(*args, **kwargs) + def type_name(self): + """Name of interaction type. - def type_number(self): - return BONDED_IA_TABULATED_DIHEDRAL + """ + return "TABULATED_DIHEDRAL" - def type_name(self): - """Name of interaction type. + def validate_params(self, params): + """Check that parameters are valid. - """ - return "TABULATED_DIHEDRAL" + """ + 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}") - def validate_params(self, params): - """Check that parameters are valid. + def get_default_params(self): + """Gets default values of optional parameters. - """ - 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}") + """ + return {} @script_interface_register @@ -1924,8 +1747,13 @@ class QuarticBond(BondedInteraction): bonded_interaction_classes = { int(BONDED_IA_FENE): FeneBond, int(BONDED_IA_HARMONIC): HarmonicBond, + int(BONDED_IA_BONDED_COULOMB): BondedCoulomb, + int(BONDED_IA_BONDED_COULOMB_SR): BondedCoulombSRBond, int(BONDED_IA_RIGID_BOND): RigidBond, int(BONDED_IA_DIHEDRAL): Dihedral, + int(BONDED_IA_TABULATED_DISTANCE): TabulatedDistance, + int(BONDED_IA_TABULATED_ANGLE): TabulatedAngle, + int(BONDED_IA_TABULATED_DIHEDRAL): TabulatedDihedral, int(BONDED_IA_VIRTUAL_BOND): Virtual, int(BONDED_IA_ANGLE_HARMONIC): AngleHarmonic, int(BONDED_IA_ANGLE_COSINE): AngleCosine, @@ -1938,17 +1766,6 @@ bonded_interaction_classes = { 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): diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index effd77a3d26..6ed08ce4906 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -32,10 +32,13 @@ #include "core/event.hpp" #include "core/nonbonded_interactions/nonbonded_interaction_data.hpp" +#include #include #include #include +#include #include +#include #include #include #include @@ -55,6 +58,7 @@ class InteractionPotentialInterface 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. */ @@ -72,12 +76,39 @@ class InteractionPotentialInterface [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]() { make_new_instance(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(); @@ -92,6 +123,9 @@ class InteractionPotentialInterface } 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]) { @@ -124,8 +158,10 @@ class InteractionPotentialInterface inactive_cutoff()) < 1e-9) { m_ia_si = std::make_shared(); } else { - context()->parallel_try_catch( - [this, ¶ms]() { make_new_instance(params); }); + context()->parallel_try_catch([this, ¶ms]() { + check_valid_parameters(params); + make_new_instance(params); + }); } } } @@ -160,6 +196,7 @@ class InteractionWCA : public InteractionPotentialInterface<::WCA_Parameters> { }); } +private: std::string inactive_parameter() const override { return "sigma"; } double inactive_cutoff() const override { return 0.; } @@ -168,6 +205,7 @@ class InteractionWCA : public InteractionPotentialInterface<::WCA_Parameters> { params, "epsilon", "sigma"); } +public: Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { if (name == "get_cutoff") { @@ -198,6 +236,7 @@ class InteractionLJ : public InteractionPotentialInterface<::LJ_Parameters> { }); } +private: void make_new_instance(VariantMap const ¶ms) override { auto new_params = params; auto const *shift_string = boost::get(¶ms.at("shift")); @@ -245,6 +284,7 @@ class InteractionLJGen }); } +private: void make_new_instance(VariantMap const ¶ms) override { auto new_params = params; auto const *shift_string = boost::get(¶ms.at("shift")); @@ -291,6 +331,7 @@ class InteractionLJcos }); } +private: void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( @@ -317,6 +358,7 @@ class InteractionLJcos2 }); } +private: std::string inactive_parameter() const override { return "sigma"; } double inactive_cutoff() const override { return 0.; } @@ -326,6 +368,7 @@ class InteractionLJcos2 params, "epsilon", "sigma", "offset", "width"); } +public: Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { if (name == "get_cutoff") { @@ -353,6 +396,7 @@ class InteractionHertzian }); } +private: std::string inactive_parameter() const override { return "sig"; } void make_new_instance(VariantMap const ¶ms) override { @@ -379,6 +423,7 @@ class InteractionGaussian }); } +private: void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( params, "eps", "sig", "cutoff"); @@ -406,6 +451,7 @@ class InteractionBMHTF }); } +private: void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( @@ -432,6 +478,7 @@ class InteractionMorse }); } +private: void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( @@ -461,6 +508,7 @@ class InteractionBuckingham }); } +private: void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( @@ -487,6 +535,7 @@ class InteractionSoftSphere }); } +private: void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( @@ -510,6 +559,7 @@ class InteractionHat : public InteractionPotentialInterface<::Hat_Parameters> { }); } +private: void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( params, "F_max", "cutoff"); @@ -538,6 +588,7 @@ class InteractionGayBerne }); } +private: std::string inactive_parameter() const override { return "cut"; } void make_new_instance(VariantMap const ¶ms) override { @@ -566,6 +617,7 @@ class InteractionTabulated }); } +private: std::string inactive_parameter() const override { return "max"; } void make_new_instance(VariantMap const ¶ms) override { @@ -574,6 +626,7 @@ class InteractionTabulated params, "min", "max", "force", "energy"); } +public: Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { if (name == "get_cutoff") { @@ -613,6 +666,7 @@ class InteractionDPD : public InteractionPotentialInterface<::DPD_Parameters> { 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 { @@ -620,6 +674,14 @@ class InteractionDPD : public InteractionPotentialInterface<::DPD_Parameters> { double, double, double, double>( 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 @@ -640,6 +702,7 @@ class InteractionThole }); } +private: std::string inactive_parameter() const override { return "scaling_coeff"; } double inactive_cutoff() const override { return 0.; } @@ -669,6 +732,8 @@ class InteractionSmoothStep make_autoparameter(&CoreInteraction::k0, "k0"), }); } + +private: void make_new_instance(VariantMap const ¶ms) override { m_ia_si = make_shared_from_args( diff --git a/src/script_interface/interactions/NonBondedInteractions.hpp b/src/script_interface/interactions/NonBondedInteractions.hpp index 9680d37419d..945ac1be2f1 100644 --- a/src/script_interface/interactions/NonBondedInteractions.hpp +++ b/src/script_interface/interactions/NonBondedInteractions.hpp @@ -69,17 +69,7 @@ class NonBondedInteractions : public ObjectHandle { void do_construct(VariantMap const ¶ms) override { auto const size = ::max_seen_particle_type; - { - // when reloading from a checkpoint file, need to resize IA lists - auto const new_size = ::max_seen_particle_type; - auto const n_pairs = new_size * (new_size + 1) / 2; - ::nonbonded_ia_params.resize(n_pairs); - for (auto &ia_params : ::nonbonded_ia_params) { - if (ia_params == nullptr) { - ia_params = std::make_shared<::IA_parameters>(); - } - } - } + 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); @@ -99,6 +89,7 @@ class NonBondedInteractions : public ObjectHandle { } 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>( 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..240b9d551cc 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 @@ -336,6 +337,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_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 793dac07572..1bdaf1f9a6c 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -22,6 +22,7 @@ import espressomd import espressomd.interactions +import espressomd.code_features class Test(ut.TestCase): @@ -38,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 @@ -115,7 +82,6 @@ def func(self): f"{interClass.__name__}: value set and value gotten back " f"differ for particle types {partType1} and {partType2}: " f"{params} vs. {outParams}") - self.parameterKeys(outInter) return func @@ -204,16 +170,16 @@ def test_set_params(self): @utx.skipIfMissingFeatures("LENNARD_JONES") def test_exceptions(self): - with self.assertRaisesRegex(RuntimeError, "LennardJonesInteraction parameter 'shift' is missing"): + with self.assertRaisesRegex(RuntimeError, "Parameter 'shift' is missing"): espressomd.interactions.LennardJonesInteraction( epsilon=1., sigma=2., cutoff=2.) - with self.assertRaisesRegex(RuntimeError, "LennardJonesInteraction parameter 'shift' is missing"): + with self.assertRaisesRegex(RuntimeError, "Parameter 'shift' is missing"): self.system.non_bonded_inter[0, 0].lennard_jones.set_params( epsilon=1., sigma=2., cutoff=2.) - with self.assertRaisesRegex(RuntimeError, "Parameter 'unknown' is not a valid LennardJonesInteraction parameter"): + 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(RuntimeError, "Parameter 'unknown' is not a valid LennardJonesInteraction parameter"): + 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"): @@ -236,11 +202,23 @@ def test_exceptions(self): self.assertEqual(inter_00_obj.call_method("get_types"), [0, 0]) self.assertIsNone(inter_00_obj.call_method("unknown")) - def check_potential_exceptions(self, ia_class, params, check_keys): + 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] = -0.1 + invalid_params[key] = invalid_value ia_class(**invalid_params) @utx.skipIfMissingFeatures("WCA") @@ -358,6 +336,16 @@ def test_smooth_step_exceptions(self): ("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__": ut.main() diff --git a/testsuite/python/save_checkpoint.py b/testsuite/python/save_checkpoint.py index 1dd3047feef..c6710cac1c1 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -233,6 +233,15 @@ epsilon=1.2, sigma=1.7, cutoff=2.0, shift=0.1) 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) diff --git a/testsuite/python/tabulated.py b/testsuite/python/tabulated.py index 198fdd34cc1..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,8 @@ 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( diff --git a/testsuite/python/test_checkpoint.py b/testsuite/python/test_checkpoint.py index e9182c9bc7d..2d5b874722b 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -330,7 +330,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, @@ -339,6 +341,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) From 283e0c8e412608dc5a11a75f47c94d0d29bad4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 12 Sep 2022 14:54:42 +0200 Subject: [PATCH 22/23] Decythonize bonded interactions --- .../bonded_interaction_data.hpp | 3 + .../bonded_interactions.dox | 32 +- src/python/espressomd/interactions.pxd | 62 --- .../{interactions.pyx => interactions.py} | 515 ++++-------------- .../analysis/ObservableStat.cpp | 5 +- .../interactions/BondedInteraction.hpp | 336 ++++++------ .../interactions/BondedInteractions.hpp | 5 + src/script_interface/interactions/bonded.hpp | 38 -- .../particle_data/ParticleHandle.cpp | 4 +- .../python/interactions_bonded_interface.py | 97 ++-- testsuite/python/save_checkpoint.py | 20 +- testsuite/python/test_checkpoint.py | 17 +- 12 files changed, 380 insertions(+), 754 deletions(-) delete mode 100644 src/python/espressomd/interactions.pxd rename src/python/espressomd/{interactions.pyx => interactions.py} (76%) delete mode 100644 src/script_interface/interactions/bonded.hpp 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/python/espressomd/interactions.pxd b/src/python/espressomd/interactions.pxd deleted file mode 100644 index e8dd0dd7013..00000000000 --- a/src/python/espressomd/interactions.pxd +++ /dev/null @@ -1,62 +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 libc cimport stdint - -from .thermostat cimport thermalized_bond - -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.pyx b/src/python/espressomd/interactions.py similarity index 76% rename from src/python/espressomd/interactions.pyx rename to src/python/espressomd/interactions.py index c1510cc96be..a84c67d1fb6 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -cimport cpython.object +import enum from . import utils from . import code_features from .script_interface import ScriptObjectMap, ScriptInterfaceHelper, script_interface_register @@ -770,6 +770,30 @@ def __reduce__(self): (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): """ @@ -784,74 +808,69 @@ class BondedInteraction(ScriptInterfaceHelper): _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 = [] - + def __init__(self, **kwargs): feature = self.__class__.__dict__.get("_so_feature") if feature is not None: code_features.assert_features(feature) - if not 'sip' in kwargs: - if len(args) == 1 and utils.is_valid_type(args[0], int): + 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 bond_id (checkpointing constructor #1) - bond_id = args[0] + # 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 get_bonded_interaction_type_from_es_core( - bond_id) != self.type_number(): - raise Exception( + 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_name()} bond in the ESPResSo core.") - super().__init__(bond_id=bond_id) + f"{self._type_number.name} bond in the ESPResSo core.") self._bond_id = bond_id - self._ctor_params = self._get_params_from_es_core() + self._ctor_params = self.get_params() else: - # create a bond from bond parameters + # create a new script interface object from bond parameters + # (normal bond creation and checkpointing constructor #2) 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()) + 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 #2 or BondedInteractions getter) + # (checkpointing constructor #3) super().__init__(**kwargs) self._bond_id = -1 - self._ctor_params = self._get_params_from_es_core() + self._ctor_params = self.get_params() def __reduce__(self): if self._bond_id != -1: # checkpointing constructor #1 - return (self.__class__, (self._bond_id,)) + return (BondedInteraction._restore_object, + (self.__class__, {"bond_id": self._bond_id})) else: # checkpointing constructor #2 - return (self.__class__, (self._ctor_params,)) + 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_from_es_core() + return self.get_params() @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})' @@ -862,47 +881,15 @@ def get_default_params(self): 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) + return self.__class__ == other.__class__ and self.call_method( + "is_same_bond", bond=other) 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") + return not self.__eq__(other) @script_interface_register @@ -923,15 +910,7 @@ class FeneBond(BondedInteraction): """ _so_name = "Interactions::FeneBond" - - def type_number(self): - return BONDED_IA_FENE - - def type_name(self): - """Name of interaction type. - - """ - return "FENE" + _type_number = BONDED_IA.FENE def get_default_params(self): """Gets default values of optional parameters. @@ -958,15 +937,7 @@ class HarmonicBond(BondedInteraction): """ _so_name = "Interactions::HarmonicBond" - - def type_number(self): - return BONDED_IA_HARMONIC - - def type_name(self): - """Name of interaction type. - - """ - return "HARMONIC" + _type_number = BONDED_IA.HARMONIC def get_default_params(self): """Gets default values of optional parameters. @@ -990,15 +961,7 @@ class BondedCoulomb(BondedInteraction): _so_name = "Interactions::BondedCoulomb" _so_feature = "ELECTROSTATICS" - - 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" + _type_number = BONDED_IA.BONDED_COULOMB def get_default_params(self): """Gets default values of optional parameters. @@ -1025,12 +988,7 @@ class BondedCoulombSRBond(BondedInteraction): _so_name = "Interactions::BondedCoulombSR" _so_feature = "ELECTROSTATICS" - - def type_number(self): - return BONDED_IA_BONDED_COULOMB_SR - - def type_name(self): - return "BONDED_COULOMB_SR" + _type_number = BONDED_IA.BONDED_COULOMB_SR def get_default_params(self): return {} @@ -1060,47 +1018,26 @@ class ThermalizedBond(BondedInteraction): 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. + 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): - counter = None - # Interaction id as argument - if len(args) == 2 and isinstance(args[0], (dict, int)): - counter = args[1] - args = (args[0],) + if kwargs and "sip" not in kwargs: + kwargs["rng_state"] = kwargs.get("rng_state") 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 _serialize(self): + params = self._ctor_params.copy() + params["rng_state"] = self.call_method("get_rng_state") + return params def get_default_params(self): return {"r_cut": 0., "seed": None} @@ -1125,18 +1062,7 @@ class RigidBond(BondedInteraction): _so_name = "Interactions::RigidBond" _so_feature = "BOND_CONSTRAINT" - - 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" + _type_number = BONDED_IA.RIGID_BOND def get_default_params(self): """Gets default values of optional parameters. @@ -1164,23 +1090,7 @@ class Dihedral(BondedInteraction): """ _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") + _type_number = BONDED_IA.DIHEDRAL def get_default_params(self): """Gets default values of optional parameters. @@ -1211,18 +1121,7 @@ class TabulatedDistance(BondedInteraction): _so_name = "Interactions::TabulatedDistanceBond" _so_feature = "TABULATED" - - 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" + _type_number = BONDED_IA.TABULATED_DISTANCE def get_default_params(self): """Gets default values of optional parameters. @@ -1249,32 +1148,15 @@ class TabulatedAngle(BondedInteraction): _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: + if len(args) == 0 and "sip" not in kwargs: 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}") - def get_default_params(self): """Gets default values of optional parameters. @@ -1300,32 +1182,15 @@ class TabulatedDihedral(BondedInteraction): _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: + if len(args) == 0 and "sip" not in kwargs: 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}") - def get_default_params(self): """Gets default values of optional parameters. @@ -1341,18 +1206,7 @@ class Virtual(BondedInteraction): """ _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" + _type_number = BONDED_IA.VIRTUAL_BOND def get_default_params(self): """Gets default values of optional parameters. @@ -1377,15 +1231,7 @@ class AngleHarmonic(BondedInteraction): """ _so_name = "Interactions::AngleHarmonicBond" - - def type_number(self): - return BONDED_IA_ANGLE_HARMONIC - - def type_name(self): - """Name of interaction type. - - """ - return "ANGLE_HARMONIC" + _type_number = BONDED_IA.ANGLE_HARMONIC def get_default_params(self): """Gets default values of optional parameters. @@ -1410,15 +1256,7 @@ class AngleCosine(BondedInteraction): """ _so_name = "Interactions::AngleCosineBond" - - def type_number(self): - return BONDED_IA_ANGLE_COSINE - - def type_name(self): - """Name of interaction type. - - """ - return "ANGLE_COSINE" + _type_number = BONDED_IA.ANGLE_COSINE def get_default_params(self): """Gets default values of optional parameters. @@ -1443,15 +1281,7 @@ class AngleCossquare(BondedInteraction): """ _so_name = "Interactions::AngleCossquareBond" - - def type_number(self): - return BONDED_IA_ANGLE_COSSQUARE - - def type_name(self): - """Name of interaction type. - - """ - return "ANGLE_COSSQUARE" + _type_number = BONDED_IA.ANGLE_COSSQUARE def get_default_params(self): """Gets default values of optional parameters. @@ -1485,25 +1315,7 @@ class IBM_Triel(BondedInteraction): """ _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']}'") + _type_number = BONDED_IA.IBM_TRIEL def get_default_params(self): """Gets default values of optional parameters. @@ -1511,13 +1323,6 @@ def get_default_params(self): """ 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): @@ -1540,25 +1345,7 @@ class IBM_Tribend(BondedInteraction): """ _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']}'") + _type_number = BONDED_IA.IBM_TRIBEND def get_default_params(self): """Gets default values of optional parameters. @@ -1566,10 +1353,6 @@ def get_default_params(self): """ 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): @@ -1588,18 +1371,18 @@ class IBM_VolCons(BondedInteraction): 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" - - 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" + _so_bind_methods = ("current_volume",) + _type_number = BONDED_IA.IBM_VOLUME_CONSERVATION def get_default_params(self): """Gets default values of optional parameters. @@ -1607,14 +1390,6 @@ def get_default_params(self): """ 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): @@ -1639,15 +1414,7 @@ class OifGlobalForces(BondedInteraction): """ _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" + _type_number = BONDED_IA.OIF_GLOBAL_FORCES def get_default_params(self): """Gets default values of optional parameters. @@ -1688,15 +1455,7 @@ class OifLocalForces(BondedInteraction): """ _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" + _type_number = BONDED_IA.OIF_LOCAL_FORCES def get_default_params(self): """Gets default values of optional parameters. @@ -1724,18 +1483,7 @@ class QuarticBond(BondedInteraction): """ _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" + _type_number = BONDED_IA.QUARTIC def get_default_params(self): """Gets default values of optional parameters. @@ -1744,34 +1492,6 @@ def get_default_params(self): return {} -bonded_interaction_classes = { - int(BONDED_IA_FENE): FeneBond, - int(BONDED_IA_HARMONIC): HarmonicBond, - int(BONDED_IA_BONDED_COULOMB): BondedCoulomb, - int(BONDED_IA_BONDED_COULOMB_SR): BondedCoulombSRBond, - int(BONDED_IA_RIGID_BOND): RigidBond, - int(BONDED_IA_DIHEDRAL): Dihedral, - int(BONDED_IA_TABULATED_DISTANCE): TabulatedDistance, - int(BONDED_IA_TABULATED_ANGLE): TabulatedAngle, - int(BONDED_IA_TABULATED_DIHEDRAL): TabulatedDihedral, - 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, -} - - -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): @@ -1798,8 +1518,12 @@ class BondedInteractions(ScriptObjectMap): _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, **kwargs): + def add(self, *args): """ Add a bond to the list. @@ -1807,9 +1531,6 @@ def add(self, *args, **kwargs): ---------- bond: :class:`espressomd.interactions.BondedInteraction` Either a bond object... - \*\*kwargs : any - ... or parameters to construct a - :class:`~espressomd.interactions.BondedInteraction` object """ @@ -1829,18 +1550,18 @@ def __getitem__(self, 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) + 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: + 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] + 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) + return bond_class(bond_id=bond_id) def __setitem__(self, bond_id, bond_obj): self._insert_bond(bond_id, bond_obj) @@ -1867,9 +1588,9 @@ def _insert_bond(self, bond_id, bond_obj): # 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: + 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) @@ -1885,20 +1606,18 @@ def __len__(self): # 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): + 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 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()) + 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, (bond_params, bond_type) in params.items(): - self[bond_id] = bonded_interaction_classes[bond_type]( - **bond_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/script_interface/analysis/ObservableStat.cpp b/src/script_interface/analysis/ObservableStat.cpp index 1d0c59036a8..db055e27ee4 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/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/particle_data/ParticleHandle.cpp b/src/script_interface/particle_data/ParticleHandle.cpp index f327b52e63e..aa6c8843991 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/testsuite/python/interactions_bonded_interface.py b/testsuite/python/interactions_bonded_interface.py index 240b9d551cc..e8c27588611 100644 --- a/testsuite/python/interactions_bonded_interface.py +++ b/testsuite/python/interactions_bonded_interface.py @@ -44,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. @@ -145,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')) @@ -176,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, @@ -231,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) @@ -252,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"): @@ -260,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() diff --git a/testsuite/python/save_checkpoint.py b/testsuite/python/save_checkpoint.py index c6710cac1c1..0f9986ca0c9 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -248,14 +248,22 @@ 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/test_checkpoint.py b/testsuite/python/test_checkpoint.py index 2d5b874722b..fe0a97f580a 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -355,18 +355,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}) From 2d279f5c0cbc6823e765d81248a630a6bae282f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 19 Sep 2022 18:36:47 +0200 Subject: [PATCH 23/23] Apply code review suggestions Co-authored-by: Alexander Reinauer --- .../nonbonded_interaction_data.cpp | 2 +- src/python/espressomd/interactions.py | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 6673ee62feb..8b1925ba306 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -73,7 +73,7 @@ void mpi_realloc_ia_params_local(int new_size) { } ::max_seen_particle_type = new_size; - std::swap(::nonbonded_ia_params, new_params); + ::nonbonded_ia_params = std::move(new_params); } REGISTER_CALLBACK(mpi_realloc_ia_params_local) diff --git a/src/python/espressomd/interactions.py b/src/python/espressomd/interactions.py index a84c67d1fb6..7ffe575bd62 100644 --- a/src/python/espressomd/interactions.py +++ b/src/python/espressomd/interactions.py @@ -17,13 +17,14 @@ # 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): +class NonBondedInteraction(ScriptInterfaceHelper, metaclass=abc.ABCMeta): """ Represents an instance of a non-bonded interaction, such as Lennard-Jones. @@ -65,12 +66,9 @@ def __reduce__(self): def _restore_object(cls, derived_class, kwargs): return derived_class(**kwargs) + @abc.abstractmethod def default_params(self): - """Virtual method. - - """ - raise Exception( - "Subclasses of NonBondedInteraction must define the default_params() method.") + pass @script_interface_register @@ -794,7 +792,7 @@ class BONDED_IA(enum.IntEnum): VIRTUAL_BOND = enum.auto() -class BondedInteraction(ScriptInterfaceHelper): +class BondedInteraction(ScriptInterfaceHelper, metaclass=abc.ABCMeta): """ Base class for bonded interactions. @@ -874,12 +872,12 @@ def params(self, p): def __str__(self): return f'{self.__class__.__name__}({self._ctor_params})' + @abc.abstractmethod def get_default_params(self): """Gets default values of optional parameters. """ - raise Exception( - "Subclasses of BondedInteraction must define the get_default_params() method.") + pass def __repr__(self): return f'<{self}>' @@ -991,6 +989,9 @@ class BondedCoulombSRBond(BondedInteraction): _type_number = BONDED_IA.BONDED_COULOMB_SR def get_default_params(self): + """Gets default values of optional parameters. + + """ return {} @@ -1040,6 +1041,9 @@ def _serialize(self): return params def get_default_params(self): + """Gets default values of optional parameters. + + """ return {"r_cut": 0., "seed": None}