diff --git a/doc/sphinx/analysis.rst b/doc/sphinx/analysis.rst index 2fdb4576f1c..e30e32f8687 100644 --- a/doc/sphinx/analysis.rst +++ b/doc/sphinx/analysis.rst @@ -787,7 +787,7 @@ To obtain the cluster structure of a system, an instance of To to create a cluster structure with above criterion:: import espressomd.cluster_analysis - cs = espressomd.cluster_analysis.ClusterStructure(distance_criterion=dc) + cs = espressomd.cluster_analysis.ClusterStructure(distance_criterion=dc, system=system) In most cases, the cluster analysis is carried out by calling the :any:`espressomd.cluster_analysis.ClusterStructure.run_for_all_pairs` method. diff --git a/doc/sphinx/io.rst b/doc/sphinx/io.rst index 1e8a0fbb11a..e47388753c8 100644 --- a/doc/sphinx/io.rst +++ b/doc/sphinx/io.rst @@ -337,7 +337,7 @@ capabilities. The usage is quite simple: import espressomd.io system = espressomd.System(box_l=[1, 1, 1]) # ... add particles here - mpiio = espressomd.io.mpiio.Mpiio() + mpiio = espressomd.io.mpiio.Mpiio(system=system) mpiio.write("/tmp/mydata", positions=True, velocities=True, types=True, bonds=True) Here, :file:`/tmp/mydata` is the prefix used to generate several files. diff --git a/doc/tutorials/ferrofluid/ferrofluid_part1.ipynb b/doc/tutorials/ferrofluid/ferrofluid_part1.ipynb index 82b8012552a..d65111cce28 100644 --- a/doc/tutorials/ferrofluid/ferrofluid_part1.ipynb +++ b/doc/tutorials/ferrofluid/ferrofluid_part1.ipynb @@ -802,7 +802,9 @@ "source": [ "# SOLUTION CELL\n", "# Setup cluster analysis\n", - "cluster_structure = espressomd.cluster_analysis.ClusterStructure(pair_criterion=espressomd.pair_criteria.DistanceCriterion(cut_off=1.3 * LJ_SIGMA))" + "cluster_structure = espressomd.cluster_analysis.ClusterStructure(\n", + " system=system,\n", + " pair_criterion=espressomd.pair_criteria.DistanceCriterion(cut_off=1.3 * LJ_SIGMA))" ] }, { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0e7b82024d1..ce441674492 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -21,18 +21,15 @@ add_library( espresso_core SHARED - accumulators.cpp bond_error.cpp cells.cpp collision.cpp communication.cpp - constraints.cpp dpd.cpp energy.cpp errorhandling.cpp forces.cpp ghosts.cpp - immersed_boundaries.cpp integrate.cpp npt.cpp particle_node.cpp diff --git a/src/core/accumulators.cpp b/src/core/accumulators.cpp deleted file mode 100644 index db1bcee9a5f..00000000000 --- a/src/core/accumulators.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2016-2022 The ESPResSo project - * - * This file is part of ESPResSo. - * - * ESPResSo is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ESPResSo is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "accumulators.hpp" - -#include - -#include -#include -#include -#include - -namespace Accumulators { -namespace { -struct AutoUpdateAccumulator { - explicit AutoUpdateAccumulator(AccumulatorBase *acc) - : frequency(acc->delta_N()), counter(1), acc(acc) {} - int frequency; - int counter; - AccumulatorBase *acc; -}; - -std::vector auto_update_accumulators; -} // namespace - -void auto_update(boost::mpi::communicator const &comm, int steps) { - for (auto &acc : auto_update_accumulators) { - assert(steps <= acc.frequency); - acc.counter -= steps; - if (acc.counter <= 0) { - acc.acc->update(comm); - acc.counter = acc.frequency; - } - - assert(acc.counter > 0); - } -} - -int auto_update_next_update() { - return boost::accumulate(auto_update_accumulators, - std::numeric_limits::max(), - [](int a, AutoUpdateAccumulator const &acc) { - return std::min(a, acc.counter); - }); -} - -namespace detail { -struct MatchPredicate { - AccumulatorBase const *m_acc; - template bool operator()(T const &a) const { - return a.acc == m_acc; - } -}; -} // namespace detail - -void auto_update_add(AccumulatorBase *acc) { - assert(not auto_update_contains(acc)); - auto_update_accumulators.emplace_back(acc); -} - -void auto_update_remove(AccumulatorBase *acc) { - assert(auto_update_contains(acc)); - std::erase_if(auto_update_accumulators, detail::MatchPredicate{acc}); -} - -bool auto_update_contains(AccumulatorBase const *acc) noexcept { - assert(acc); - return std::ranges::any_of(auto_update_accumulators, - detail::MatchPredicate{acc}); -} - -} // namespace Accumulators diff --git a/src/core/accumulators.hpp b/src/core/accumulators.hpp deleted file mode 100644 index bc8199b05d6..00000000000 --- a/src/core/accumulators.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2016-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 ESPRESSO_ACCUMULATORS_HPP -#define ESPRESSO_ACCUMULATORS_HPP - -#include "accumulators/AccumulatorBase.hpp" - -namespace Accumulators { -/** - * @brief Update accumulators. - * - * Checks for all auto update accumulators if - * they need to be updated and if so does. - * - */ -void auto_update(boost::mpi::communicator const &comm, int steps); -int auto_update_next_update(); -bool auto_update_contains(AccumulatorBase const *) noexcept; -void auto_update_add(AccumulatorBase *); -void auto_update_remove(AccumulatorBase *); - -} // namespace Accumulators - -#endif // ESPRESSO_ACCUMULATORS_HPP diff --git a/src/core/accumulators/AccumulatorBase.hpp b/src/core/accumulators/AccumulatorBase.hpp index 7bf17d4a799..98a01deff82 100644 --- a/src/core/accumulators/AccumulatorBase.hpp +++ b/src/core/accumulators/AccumulatorBase.hpp @@ -16,34 +16,50 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_ACCUMULATORS_ACCUMULATOR_BASE_HPP -#define CORE_ACCUMULATORS_ACCUMULATOR_BASE_HPP +#pragma once + +#include #include +#include #include -// Forward declaration +// Forward declarations namespace boost::mpi { class communicator; } +namespace System { +class System; +} namespace Accumulators { class AccumulatorBase { public: - explicit AccumulatorBase(int delta_N = 1) : m_delta_N(delta_N) {} + AccumulatorBase(::System::System const *system, int delta_N) + : m_system(reinterpret_cast(system)), m_delta_N(delta_N) {} virtual ~AccumulatorBase() = default; int &delta_N() { return m_delta_N; } + bool has_same_system_handle(::System::System const *system) const { + return reinterpret_cast(system) == m_system; + } + void override_system_handle(::System::System const *system) { + assert(m_system == nullptr); + m_system = reinterpret_cast(system); + } virtual void update(boost::mpi::communicator const &comm) = 0; /** Dimensions needed to reshape the flat array returned by the accumulator */ virtual std::vector shape() const = 0; + /** Serialization of private members. */ + virtual std::string get_internal_state() const = 0; + virtual void set_internal_state(std::string const &) = 0; +protected: + void const *m_system; ///< for bookkeeping purposes private: - // Number of timesteps between automatic updates. + /// Number of time steps between automatic updates. int m_delta_N; }; } // namespace Accumulators - -#endif diff --git a/src/core/accumulators/AutoUpdateAccumulators.cpp b/src/core/accumulators/AutoUpdateAccumulators.cpp new file mode 100644 index 00000000000..8151b25e161 --- /dev/null +++ b/src/core/accumulators/AutoUpdateAccumulators.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016-2024 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "AutoUpdateAccumulators.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace Accumulators { + +void AutoUpdateAccumulators::operator()(boost::mpi::communicator const &comm, + int steps) { + for (auto &acc : m_accumulators) { + assert(steps <= acc.frequency); + acc.counter -= steps; + if (acc.counter <= 0) { + acc.acc->update(comm); + acc.counter = acc.frequency; + } + + assert(acc.counter > 0); + } +} + +int AutoUpdateAccumulators::next_update() const { + return std::accumulate(m_accumulators.begin(), m_accumulators.end(), + std::numeric_limits::max(), + [](int a, AutoUpdateAccumulator const &acc) { + return std::min(a, acc.counter); + }); +} + +namespace detail { +struct MatchPredicate { + AccumulatorBase const *m_acc; + template bool operator()(T const &a) const { + return a.acc == m_acc; + } +}; +} // namespace detail + +void AutoUpdateAccumulators::add(AccumulatorBase *acc) { + assert(not contains(acc)); + auto const *this_system = &get_system(); + if (acc->has_same_system_handle(nullptr)) { + acc->override_system_handle(this_system); + } else if (not acc->has_same_system_handle(this_system)) { + throw std::runtime_error("This accumulator is bound to another system"); + } + m_accumulators.emplace_back(acc); +} + +void AutoUpdateAccumulators::remove(AccumulatorBase *acc) { + assert(contains(acc)); + std::erase_if(m_accumulators, detail::MatchPredicate{acc}); +} + +bool AutoUpdateAccumulators::contains(AccumulatorBase const *acc) const { + assert(acc); + return std::ranges::any_of(m_accumulators, detail::MatchPredicate{acc}); +} + +} // namespace Accumulators diff --git a/src/core/accumulators/AutoUpdateAccumulators.hpp b/src/core/accumulators/AutoUpdateAccumulators.hpp new file mode 100644 index 00000000000..f41556def9b --- /dev/null +++ b/src/core/accumulators/AutoUpdateAccumulators.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016-2024 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "AccumulatorBase.hpp" + +#include "system/Leaf.hpp" + +namespace Accumulators { + +class AutoUpdateAccumulators : public System::Leaf { +public: + /** + * @brief Update accumulators. + * + * Checks for all auto update accumulators if + * they need to be updated and if so does. + * + */ + void operator()(boost::mpi::communicator const &comm, int steps); + int next_update() const; + bool contains(AccumulatorBase const *) const; + void add(AccumulatorBase *); + void remove(AccumulatorBase *); + +private: + struct AutoUpdateAccumulator { + explicit AutoUpdateAccumulator(AccumulatorBase *acc) + : frequency(acc->delta_N()), counter(1), acc(acc) {} + int frequency; + int counter; + AccumulatorBase *acc; + }; + + std::vector m_accumulators; +}; + +} // namespace Accumulators diff --git a/src/core/accumulators/CMakeLists.txt b/src/core/accumulators/CMakeLists.txt index 8dee6ffcc1f..2fe79a3e220 100644 --- a/src/core/accumulators/CMakeLists.txt +++ b/src/core/accumulators/CMakeLists.txt @@ -20,5 +20,6 @@ target_sources( espresso_core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Correlator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/AutoUpdateAccumulators.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MeanVarianceCalculator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TimeSeries.cpp) diff --git a/src/core/accumulators/Correlator.cpp b/src/core/accumulators/Correlator.cpp index 430b8656805..1be32a7d31d 100644 --- a/src/core/accumulators/Correlator.cpp +++ b/src/core/accumulators/Correlator.cpp @@ -163,13 +163,12 @@ std::vector fcs_acf(std::vector const &A, return C; } -void Correlator::initialize() { +void Correlator::initialize_operations() { // Class members are assigned via the initializer list if (m_tau_lin == 1) { // use the default - m_tau_lin = static_cast(ceil(m_tau_max / m_dt)); - if (m_tau_lin % 2) - m_tau_lin += 1; + m_tau_lin = static_cast(std::ceil(m_tau_max / m_dt)); + m_tau_lin += m_tau_lin % 2; } if (m_tau_lin < 2) { @@ -187,8 +186,9 @@ void Correlator::initialize() { if ((m_tau_max / m_dt) < m_tau_lin) { m_hierarchy_depth = 1; } else { - m_hierarchy_depth = static_cast( - ceil(1 + log((m_tau_max / m_dt) / (m_tau_lin - 1)) / log(2.0))); + auto const operand = (m_tau_max / m_dt) / double(m_tau_lin - 1); + assert(operand > 0.); + m_hierarchy_depth = static_cast(std::ceil(1. + std::log2(operand))); } assert(A_obs); @@ -224,12 +224,11 @@ void Correlator::initialize() { } else if (corr_operation_name == "fcs_acf") { // note: user provides w=(wx,wy,wz) but we want to use // wsquare=(wx^2,wy^2,wz^2) - if (m_correlation_args[0] <= 0 || m_correlation_args[1] <= 0 || - m_correlation_args[2] <= 0) { + if (not(m_correlation_args_input > Utils::Vector3d::broadcast(0.))) { throw std::runtime_error("missing parameter for fcs_acf: w_x w_y w_z"); } - m_correlation_args = - Utils::hadamard_product(m_correlation_args, m_correlation_args); + m_correlation_args = Utils::hadamard_product(m_correlation_args_input, + m_correlation_args_input); if (dim_A % 3u) throw std::runtime_error("dimA must be divisible by 3 for fcs_acf"); m_dim_corr = dim_A / 3u; @@ -272,7 +271,9 @@ void Correlator::initialize() { throw std::invalid_argument("unknown compression method '" + compressB_name + "' for second observable"); } +} +void Correlator::initialize_buffers() { using index_type = decltype(result)::index; A.resize(std::array{{m_hierarchy_depth, m_tau_lin + 1}}); @@ -536,7 +537,9 @@ std::string Correlator::get_internal_state() const { boost::archive::binary_oarchive oa(ss); oa << t; + oa << m_dt; oa << m_shape; + oa << m_correlation_args_input; oa << A; oa << B; oa << result; @@ -557,7 +560,9 @@ void Correlator::set_internal_state(std::string const &state) { boost::archive::binary_iarchive ia(ss); ia >> t; + ia >> m_dt; ia >> m_shape; + ia >> m_correlation_args_input; ia >> A; ia >> B; ia >> result; @@ -567,6 +572,8 @@ void Correlator::set_internal_state(std::string const &state) { ia >> A_accumulated_average; ia >> B_accumulated_average; ia >> n_data; + initialize_operations(); + m_system = nullptr; } } // namespace Accumulators diff --git a/src/core/accumulators/Correlator.hpp b/src/core/accumulators/Correlator.hpp index e60fa27d40b..6d0047c65f2 100644 --- a/src/core/accumulators/Correlator.hpp +++ b/src/core/accumulators/Correlator.hpp @@ -16,8 +16,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_ACCUMULATORS_CORRELATOR_HPP -#define CORE_ACCUMULATORS_CORRELATOR_HPP + +#pragma once + /** @file * * This module computes correlations (and other two time averages) on @@ -128,6 +129,12 @@ namespace Accumulators { */ class Correlator : public AccumulatorBase { using obs_ptr = std::shared_ptr; + void initialize_operations(); + void initialize_buffers(); + void initialize() { + initialize_operations(); + initialize_buffers(); + } public: /** The initialization procedure for the correlation object. All important @@ -135,6 +142,7 @@ class Correlator : public AccumulatorBase { * later, so every instance of the correlation class has to be fed with * correct data from the very beginning. * + * @param system The system attached to this correlator * @param delta_N The number of time steps between subsequent updates * @param tau_lin The linear part of the correlation function. * @param tau_max maximal time delay tau to sample @@ -150,23 +158,21 @@ class Correlator : public AccumulatorBase { * (currently only used when @p corr_operation is "fcs_acf") * */ - Correlator(int tau_lin, double tau_max, int delta_N, std::string compress1_, - std::string compress2_, std::string corr_operation, obs_ptr obs1, - obs_ptr obs2, Utils::Vector3d correlation_args_ = {}) - : AccumulatorBase(delta_N), finalized(false), t(0), + Correlator(::System::System const *system, int delta_N, int tau_lin, + double tau_max, std::string compress1_, std::string compress2_, + std::string corr_operation, obs_ptr obs1, obs_ptr obs2, + Utils::Vector3d correlation_args_ = {}) + : AccumulatorBase(system, delta_N), finalized(false), t(0), + m_correlation_args_input(correlation_args_), m_correlation_args(correlation_args_), m_tau_lin(tau_lin), - m_dt(::System::get_system().get_time_step()), m_tau_max(tau_max), - compressA_name(std::move(compress1_)), + m_dt(system->get_time_step() * static_cast(delta_N)), + m_tau_max(tau_max), compressA_name(std::move(compress1_)), compressB_name(std::move(compress2_)), corr_operation_name(std::move(corr_operation)), A_obs(std::move(obs1)), B_obs(std::move(obs2)) { - m_dt *= static_cast(delta_N); initialize(); } -private: - void initialize(); - public: /** The function to process a new datapoint of A and B * @@ -215,15 +221,14 @@ class Correlator : public AccumulatorBase { return corr_operation_name; } - /** Partial serialization of state that is not accessible via the interface. - */ - std::string get_internal_state() const; - void set_internal_state(std::string const &); + std::string get_internal_state() const final; + void set_internal_state(std::string const &) final; private: bool finalized; ///< whether the correlation is finalized unsigned int t; ///< global time in number of frames + Utils::Vector3d m_correlation_args_input; Utils::Vector3d m_correlation_args; ///< additional arguments, which the ///< correlation may need (currently ///< only used by fcs_acf) @@ -280,4 +285,3 @@ class Correlator : public AccumulatorBase { }; } // namespace Accumulators -#endif diff --git a/src/core/accumulators/MeanVarianceCalculator.cpp b/src/core/accumulators/MeanVarianceCalculator.cpp index 06373928ed7..0e67276be11 100644 --- a/src/core/accumulators/MeanVarianceCalculator.cpp +++ b/src/core/accumulators/MeanVarianceCalculator.cpp @@ -66,5 +66,6 @@ void MeanVarianceCalculator::set_internal_state(std::string const &state) { boost::archive::binary_iarchive ia(ss); ia >> m_acc; + m_system = nullptr; } } // namespace Accumulators diff --git a/src/core/accumulators/MeanVarianceCalculator.hpp b/src/core/accumulators/MeanVarianceCalculator.hpp index f2aa5ec6852..f8fa39f0664 100644 --- a/src/core/accumulators/MeanVarianceCalculator.hpp +++ b/src/core/accumulators/MeanVarianceCalculator.hpp @@ -16,11 +16,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_ACCUMULATORS_MEAN_VARIANCE_CALCULATOR_HPP -#define CORE_ACCUMULATORS_MEAN_VARIANCE_CALCULATOR_HPP + +#pragma once #include "AccumulatorBase.hpp" #include "observables/Observable.hpp" + #include #include @@ -34,25 +35,21 @@ class MeanVarianceCalculator : public AccumulatorBase { public: // The accumulator struct has to be initialized with the correct vector size, // therefore the order of init is important. - MeanVarianceCalculator(std::shared_ptr obs, - int delta_N) - : AccumulatorBase(delta_N), m_obs(obs), m_acc(obs->n_values()) {} + MeanVarianceCalculator(::System::System const *system, int delta_N, + std::shared_ptr obs) + : AccumulatorBase(system, delta_N), m_obs(obs), m_acc(obs->n_values()) {} void update(boost::mpi::communicator const &comm) override; std::vector mean(); std::vector variance(); std::vector std_error(); - /* Partial serialization of state that is not accessible - via the interface. */ - std::string get_internal_state() const; - void set_internal_state(std::string const &); + std::string get_internal_state() const final; + void set_internal_state(std::string const &) final; std::vector shape() const override { return m_obs->shape(); } private: std::shared_ptr m_obs; - ::Utils::Accumulator m_acc; + Utils::Accumulator m_acc; }; } // namespace Accumulators - -#endif diff --git a/src/core/accumulators/TimeSeries.cpp b/src/core/accumulators/TimeSeries.cpp index 206b6a0160d..14d32ff9794 100644 --- a/src/core/accumulators/TimeSeries.cpp +++ b/src/core/accumulators/TimeSeries.cpp @@ -52,5 +52,6 @@ void TimeSeries::set_internal_state(std::string const &state) { boost::archive::binary_iarchive ia(ss); ia >> m_data; + m_system = nullptr; } } // namespace Accumulators diff --git a/src/core/accumulators/TimeSeries.hpp b/src/core/accumulators/TimeSeries.hpp index e7d78c49de6..ce1906fb23f 100644 --- a/src/core/accumulators/TimeSeries.hpp +++ b/src/core/accumulators/TimeSeries.hpp @@ -16,8 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_ACCUMULATORS_TIMESERIES_HPP -#define CORE_ACCUMULATORS_TIMESERIES_HPP + +#pragma once #include "AccumulatorBase.hpp" #include "observables/Observable.hpp" @@ -40,12 +40,13 @@ namespace Accumulators { */ class TimeSeries : public AccumulatorBase { public: - TimeSeries(std::shared_ptr obs, int delta_N) - : AccumulatorBase(delta_N), m_obs(std::move(obs)) {} + TimeSeries(::System::System const *system, int delta_N, + std::shared_ptr obs) + : AccumulatorBase(system, delta_N), m_obs(std::move(obs)) {} void update(boost::mpi::communicator const &comm) override; - std::string get_internal_state() const; - void set_internal_state(std::string const &); + std::string get_internal_state() const final; + void set_internal_state(std::string const &) final; const std::vector> &time_series() const { return m_data; } std::vector shape() const override { @@ -62,5 +63,3 @@ class TimeSeries : public AccumulatorBase { }; } // namespace Accumulators - -#endif diff --git a/src/core/bonded_interactions/angle_cosine.hpp b/src/core/bonded_interactions/angle_cosine.hpp index 681e7388a94..7df19c90afa 100644 --- a/src/core/bonded_interactions/angle_cosine.hpp +++ b/src/core/bonded_interactions/angle_cosine.hpp @@ -61,16 +61,6 @@ struct AngleCosineBond { std::tuple forces(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; double energy(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & bend; - ar & phi0; - ar & cos_phi0; - ar & sin_phi0; - } }; /** Compute the three-body angle interaction force. diff --git a/src/core/bonded_interactions/angle_cossquare.hpp b/src/core/bonded_interactions/angle_cossquare.hpp index 0b1a45136d4..5cf0b1f666b 100644 --- a/src/core/bonded_interactions/angle_cossquare.hpp +++ b/src/core/bonded_interactions/angle_cossquare.hpp @@ -57,15 +57,6 @@ struct AngleCossquareBond { std::tuple forces(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; double energy(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & bend; - ar & phi0; - ar & cos_phi0; - } }; /** Compute the three-body angle interaction force. diff --git a/src/core/bonded_interactions/angle_harmonic.hpp b/src/core/bonded_interactions/angle_harmonic.hpp index 5aa1d77e04d..fd97b0ccf14 100644 --- a/src/core/bonded_interactions/angle_harmonic.hpp +++ b/src/core/bonded_interactions/angle_harmonic.hpp @@ -54,14 +54,6 @@ struct AngleHarmonicBond { std::tuple forces(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; double energy(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & bend; - ar & phi0; - } }; /** Compute the three-body angle interaction force. diff --git a/src/core/bonded_interactions/bonded_coulomb.hpp b/src/core/bonded_interactions/bonded_coulomb.hpp index bb51b8483c1..bb854ab4e63 100644 --- a/src/core/bonded_interactions/bonded_coulomb.hpp +++ b/src/core/bonded_interactions/bonded_coulomb.hpp @@ -47,13 +47,6 @@ struct BondedCoulomb { std::optional force(double q1q2, Utils::Vector3d const &dx) const; std::optional energy(double q1q2, Utils::Vector3d const &dx) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & prefactor; - } }; /** Compute the bonded Coulomb pair force. diff --git a/src/core/bonded_interactions/bonded_coulomb_sr.hpp b/src/core/bonded_interactions/bonded_coulomb_sr.hpp index fa7fbb8e3b2..11fdd606b28 100644 --- a/src/core/bonded_interactions/bonded_coulomb_sr.hpp +++ b/src/core/bonded_interactions/bonded_coulomb_sr.hpp @@ -57,13 +57,6 @@ struct BondedCoulombSR { std::function const &kernel) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & q1q2; - } }; /** Compute the short-range bonded Coulomb pair force. diff --git a/src/core/bonded_interactions/bonded_interaction_data.cpp b/src/core/bonded_interactions/bonded_interaction_data.cpp index cbb4441f4d9..8d5c2a631ae 100644 --- a/src/core/bonded_interactions/bonded_interaction_data.cpp +++ b/src/core/bonded_interactions/bonded_interaction_data.cpp @@ -16,18 +16,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + #include "bonded_interaction_data.hpp" + +#include "BoxGeometry.hpp" +#include "cell_system/CellStructure.hpp" +#include "immersed_boundary/ImmersedBoundaries.hpp" #include "rigid_bond.hpp" #include "system/System.hpp" #include "thermalized_bond.hpp" +#include "thermostat.hpp" #include #include -#include -#include - -BondedInteractionsMap bonded_ia_params; +#include /** Visitor to get the bond cutoff from the bond parameter variant */ class BondCutoff : public boost::static_visitor { @@ -37,20 +40,18 @@ class BondCutoff : public boost::static_visitor { } }; -double maximal_cutoff_bonded() { +double BondedInteractionsMap::maximal_cutoff() const { auto const max_cut_bonded = std::accumulate( - bonded_ia_params.begin(), bonded_ia_params.end(), BONDED_INACTIVE_CUTOFF, - [](auto max_cut, auto const &kv) { + begin(), end(), BONDED_INACTIVE_CUTOFF, [](auto max_cut, auto const &kv) { return std::max(max_cut, boost::apply_visitor(BondCutoff(), *kv.second)); }); /* Check if there are dihedrals */ - auto const any_dihedrals = std::any_of( - bonded_ia_params.begin(), bonded_ia_params.end(), [](auto const &kv) { - return (boost::get(&(*kv.second)) || - boost::get(&(*kv.second))); - }); + auto const any_dihedrals = std::any_of(begin(), end(), [](auto const &kv) { + return (boost::get(&(*kv.second)) || + boost::get(&(*kv.second))); + }); /* dihedrals: the central particle is indirectly connected to the fourth * particle via the third particle, so we have to double the cutoff */ @@ -72,9 +73,33 @@ void BondedInteractionsMap::on_ia_change() { } #endif } - if (System::is_system_set()) { - auto &system = System::get_system(); - system.on_short_range_ia_change(); - system.on_thermostat_param_change(); // thermalized bonds + if (auto system = m_system.lock()) { + system->on_short_range_ia_change(); + system->on_thermostat_param_change(); // thermalized bonds + } +} + +void BondedInteractionsMap::activate_bond(mapped_type const &ptr) { + auto &system = get_system(); + if (auto bond = boost::get(ptr.get())) { + bond->set_thermostat_view(system.thermostat); + } + if (auto bond = boost::get(ptr.get())) { + system.immersed_boundaries->register_softID(*bond); + } + if (auto bond = boost::get(ptr.get())) { + bond->initialize(*system.box_geo, *system.cell_structure); + } + if (auto bond = boost::get(ptr.get())) { + bond->initialize(*system.box_geo, *system.cell_structure); + } +} + +void BondedInteractionsMap::deactivate_bond(mapped_type const &ptr) { + if (auto bond = boost::get(ptr.get())) { + bond->unset_thermostat_view(); + } + if (auto bond = boost::get(ptr.get())) { + bond->unset_volumes_view(); } } diff --git a/src/core/bonded_interactions/bonded_interaction_data.hpp b/src/core/bonded_interactions/bonded_interaction_data.hpp index 77e3ccd2f0c..c2e877b8876 100644 --- a/src/core/bonded_interactions/bonded_interaction_data.hpp +++ b/src/core/bonded_interactions/bonded_interaction_data.hpp @@ -43,15 +43,16 @@ #include "rigid_bond.hpp" #include "thermalized_bond.hpp" +#include "BondList.hpp" #include "TabulatedPotential.hpp" +#include "system/Leaf.hpp" -#include -#include #include #include #include #include +#include #include #include #include @@ -65,20 +66,12 @@ static constexpr double BONDED_INACTIVE_CUTOFF = -1.; struct NoneBond { static constexpr int num = 0; double cutoff() const { return BONDED_INACTIVE_CUTOFF; } - -private: - friend boost::serialization::access; - template void serialize(Archive &, long int) {} }; /** Interaction type for virtual bonds */ struct VirtualBond { static constexpr int num = 1; double cutoff() const { return BONDED_INACTIVE_CUTOFF; } - -private: - friend boost::serialization::access; - template void serialize(Archive &, long int) {} }; /** Visitor to get the number of bound partners from the bond parameter @@ -100,7 +93,10 @@ using Bonded_IA_Parameters = RigidBond, IBMTriel, IBMVolCons, IBMTribend, OifGlobalForcesBond, OifLocalForcesBond, VirtualBond>; -class BondedInteractionsMap { +/** + * @brief container for bonded interactions. + */ +class BondedInteractionsMap : public System::Leaf { using container_type = std::unordered_map>; @@ -111,7 +107,8 @@ class BondedInteractionsMap { using iterator = typename container_type::iterator; using const_iterator = typename container_type::const_iterator; - explicit BondedInteractionsMap() = default; + BondedInteractionsMap() = default; + virtual ~BondedInteractionsMap() = default; iterator begin() { return m_params.begin(); } iterator end() { return m_params.end(); } @@ -119,24 +116,34 @@ class BondedInteractionsMap { const_iterator end() const { return m_params.end(); } void insert(key_type const &key, mapped_type const &ptr) { + if (m_params.contains(key)) { + deactivate_bond(m_params.at(key)); + } + activate_bond(ptr); next_key = std::max(next_key, key + 1); m_params[key] = ptr; on_ia_change(); } key_type insert(mapped_type const &ptr) { + activate_bond(ptr); auto const key = next_key++; m_params[key] = ptr; on_ia_change(); return key; } auto erase(key_type const &key) { + if (m_params.contains(key)) { + deactivate_bond(m_params.at(key)); + } auto &&obj = m_params.erase(key); on_ia_change(); return obj; } + virtual void activate_bond(mapped_type const &ptr); + virtual void deactivate_bond(mapped_type const &ptr); mapped_type at(key_type const &key) const { return m_params.at(key); } auto count(key_type const &key) const { return m_params.count(key); } - bool contains(key_type const &key) const { return m_params.count(key); } + bool contains(key_type const &key) const { return m_params.contains(key); } bool empty() const { return m_params.empty(); } auto size() const { return m_params.size(); } auto get_next_key() const { return next_key; } @@ -147,6 +154,69 @@ class BondedInteractionsMap { #ifdef BOND_CONSTRAINT auto get_n_rigid_bonds() const { return n_rigid_bonds; } #endif + std::optional find_bond_id(mapped_type const &bond) const { + for (auto const &kv : m_params) { + if (kv.second == bond) { + return kv.first; + } + } + return std::nullopt; + } + + /** + * @brief Calculate the maximal cutoff of bonded interactions, required to + * determine the cell size for communication. + * + * Bond angle and dihedral potentials do not contain a cutoff intrinsically. + * The cutoff for these potentials depends on the bond length potentials + * (it is assumed that particles participating in a bond angle or dihedral + * potential are bound to each other by some bond length potential). For bond + * angle potentials nothing has to be done. For dihedral potentials the cutoff + * is set to twice the maximal cutoff because the particle in which the bond + * is stored is only bonded to the first two partners, one of which has an + * additional bond to the third partner. + */ + double maximal_cutoff() const; + + /** + * @brief Checks both particles for a specific bond, even on ghost particles. + * + * @param p particle to check for the bond + * @param p_partner possible bond partner + * @tparam BondType Bond type to check for. Must be of one of the types in + * @ref Bonded_IA_Parameters. + */ + template + bool pair_bond_exists_on(Particle const &p, Particle const &p_partner) const { + auto const &bonds = p.bonds(); + return std::any_of( + bonds.begin(), bonds.end(), + [this, partner_id = p_partner.id()](BondView const &bond) { + auto const &bond_ptr = at(bond.bond_id()); + return (boost::get(bond_ptr.get()) != nullptr) and + (bond.partner_ids()[0] == partner_id); + }); + } + + /** + * @brief Checks both particles for a specific bond, even on ghost particles. + * + * @param p1 particle on which the bond may be stored + * @param p2 particle on which the bond may be stored + * @tparam BondType Bond type to check for. + */ + template + bool pair_bond_exists_between(Particle const &p1, Particle const &p2) const { + if (&p1 == &p2) + return false; + + // Check if particles have bonds and search for the bond of interest. + // Could be saved on either particle, so we need to check both. + return pair_bond_exists_on(p1, p2) or + pair_bond_exists_on(p2, p1); + } + + void on_ia_change(); private: container_type m_params = {}; @@ -155,27 +225,9 @@ class BondedInteractionsMap { #ifdef BOND_CONSTRAINT int n_rigid_bonds = 0; #endif - void on_ia_change(); }; -/** Field containing the parameters of the bonded ia types */ -extern BondedInteractionsMap bonded_ia_params; - -/** Calculate the maximal cutoff of bonded interactions, required to - * determine the cell size for communication. - * - * Bond angle and dihedral potentials do not contain a cutoff intrinsically. - * The cutoff for these potentials depends on the bond length potentials - * (it is assumed that particles participating in a bond angle or dihedral - * potential are bound to each other by some bond length potential). For bond - * angle potentials nothing has to be done. For dihedral potentials the cutoff - * is set to twice the maximal cutoff because the particle in which the bond - * is stored is only bonded to the first two partners, one of which has an - * additional bond to the third partner. - */ -double maximal_cutoff_bonded(); - -/** Return the number of bonded partners for the specified bond */ +/** @brief Get the number of bonded partners for the specified bond. */ inline int number_of_partners(Bonded_IA_Parameters const &iaparams) { return boost::apply_visitor(BondNumPartners(), iaparams); } diff --git a/src/core/bonded_interactions/bonded_interaction_utils.hpp b/src/core/bonded_interactions/bonded_interaction_utils.hpp deleted file mode 100644 index 9f0f60c9a30..00000000000 --- a/src/core/bonded_interactions/bonded_interaction_utils.hpp +++ /dev/null @@ -1,69 +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 . - */ - -#pragma once - -#include "bonded_interaction_data.hpp" - -#include "BondList.hpp" -#include "Particle.hpp" - -#include - -#include - -/** @brief Checks both particles for a specific bond, even on ghost particles. - * - * @param p particle to check for the bond - * @param p_partner possible bond partner - * @tparam BondType Bond type to check for. Must be of one of the types in - * @ref Bonded_IA_Parameters. - */ -template -inline bool pair_bond_enum_exists_on(Particle const &p, - Particle const &p_partner) { - auto const &bonds = p.bonds(); - return std::any_of( - bonds.begin(), bonds.end(), - [partner_id = p_partner.id()](BondView const &bond) { - auto const &bond_ptr = bonded_ia_params.at(bond.bond_id()); - return (boost::get(bond_ptr.get()) != nullptr) and - (bond.partner_ids()[0] == partner_id); - }); -} - -/** @brief Checks both particles for a specific bond, even on ghost particles. - * - * @param p1 particle on which the bond may be stored - * @param p2 particle on which the bond may be stored - * @tparam BondType Bond type to check for. Must be of one of the types in - * @ref Bonded_IA_Parameters. - */ -template -inline bool pair_bond_enum_exists_between(Particle const &p1, - Particle const &p2) { - if (&p1 == &p2) - return false; - - // Check if particles have bonds and search for the bond of interest. - // Could be saved on both sides (and both could have other bonds), so - // we need to check both. - return pair_bond_enum_exists_on(p1, p2) or - pair_bond_enum_exists_on(p2, p1); -} diff --git a/src/core/bonded_interactions/bonded_interactions.dox b/src/core/bonded_interactions/bonded_interactions.dox index 251d7711e2f..7883f96be6a 100644 --- a/src/core/bonded_interactions/bonded_interactions.dox +++ b/src/core/bonded_interactions/bonded_interactions.dox @@ -95,24 +95,6 @@ * @endcode * All values the bond needs to function properly should be passed as * arguments to this constructor. - * * A template function for serialization called @c serialize. This is for - * communication between nodes in parallel computations. - * The following function can serve as a starting point. - * @code{.cpp} - * private: - * friend boost::serialization::access; - * template - * void serialize(Archive &ar, long int) { - * ar &k; - * ar &drmax; - * ar &r0; - * ar &drmax2; - * ar &drmax2i; - * } - * @endcode - * Replace all the ar& commands with the new bond's parameters. - * Every data member of your struct needs to be transmitted. This template - * function is private. * * @subsection bondedIA_new_integration Integrating the new bond type into the C++ core * diff --git a/src/core/bonded_interactions/bonded_tab.hpp b/src/core/bonded_interactions/bonded_tab.hpp index 346f004dd9d..64238e84c58 100644 --- a/src/core/bonded_interactions/bonded_tab.hpp +++ b/src/core/bonded_interactions/bonded_tab.hpp @@ -35,8 +35,6 @@ #include #include -#include - #include #include #include @@ -61,13 +59,6 @@ struct TabulatedBond { std::vector const &force) { pot = std::make_shared(min, max, force, energy); } - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & pot; - } }; /** Parameters for 2-body tabulated potential. */ diff --git a/src/core/bonded_interactions/dihedral.hpp b/src/core/bonded_interactions/dihedral.hpp index ff57332c912..434172f2a3a 100644 --- a/src/core/bonded_interactions/dihedral.hpp +++ b/src/core/bonded_interactions/dihedral.hpp @@ -61,15 +61,6 @@ struct DihedralBond { std::optional energy(Utils::Vector3d const &v12, Utils::Vector3d const &v23, Utils::Vector3d const &v34) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & mult; - ar & bend; - ar & phase; - } }; /** diff --git a/src/core/bonded_interactions/fene.hpp b/src/core/bonded_interactions/fene.hpp index 82d2ec8c43c..0156399a088 100644 --- a/src/core/bonded_interactions/fene.hpp +++ b/src/core/bonded_interactions/fene.hpp @@ -62,17 +62,6 @@ struct FeneBond { std::optional force(Utils::Vector3d const &dx) const; std::optional energy(Utils::Vector3d const &dx) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & k; - ar & drmax; - ar & r0; - ar & drmax2; - ar & drmax2i; - } }; /** Compute the FENE bond force. diff --git a/src/core/bonded_interactions/harmonic.hpp b/src/core/bonded_interactions/harmonic.hpp index 1c9cde00bf8..bb8c7e2ee50 100644 --- a/src/core/bonded_interactions/harmonic.hpp +++ b/src/core/bonded_interactions/harmonic.hpp @@ -54,15 +54,6 @@ struct HarmonicBond { std::optional force(Utils::Vector3d const &dx) const; std::optional energy(Utils::Vector3d const &dx) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & k; - ar & r; - ar & r_cut; - } }; /** Compute the harmonic bond force. diff --git a/src/core/bonded_interactions/quartic.hpp b/src/core/bonded_interactions/quartic.hpp index eb37ccce7ad..68231c0e3ff 100644 --- a/src/core/bonded_interactions/quartic.hpp +++ b/src/core/bonded_interactions/quartic.hpp @@ -51,16 +51,6 @@ struct QuarticBond { std::optional force(Utils::Vector3d const &dx) const; std::optional energy(Utils::Vector3d const &dx) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & k0; - ar & k1; - ar & r; - ar & r_cut; - } }; /** Compute the quartic bond force. diff --git a/src/core/bonded_interactions/rigid_bond.hpp b/src/core/bonded_interactions/rigid_bond.hpp index 3b0fbb7d4cc..75597610117 100644 --- a/src/core/bonded_interactions/rigid_bond.hpp +++ b/src/core/bonded_interactions/rigid_bond.hpp @@ -51,13 +51,4 @@ struct RigidBond { this->p_tol = 2.0 * p_tol; this->v_tol = v_tol; } - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & d2; - ar & p_tol; - ar & v_tol; - } }; diff --git a/src/core/bonded_interactions/thermalized_bond.hpp b/src/core/bonded_interactions/thermalized_bond.hpp index 7cf9e9a0bf2..badd981d086 100644 --- a/src/core/bonded_interactions/thermalized_bond.hpp +++ b/src/core/bonded_interactions/thermalized_bond.hpp @@ -29,10 +29,16 @@ #include +#include #include +#include #include #include +namespace Thermostat { +class Thermostat; +} + /** Parameters for Thermalized bond */ struct ThermalizedBond { double temp_com; @@ -70,22 +76,17 @@ struct ThermalizedBond { pref2_dist = std::sqrt(24. * gamma_distance / time_step * temp_distance); } + void set_thermostat_view( + std::weak_ptr const &thermostat) { + m_thermostat = thermostat; + } + + void unset_thermostat_view() { m_thermostat.reset(); } + std::optional> forces(Particle const &p1, Particle const &p2, Utils::Vector3d const &dx) const; private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & temp_com; - ar & gamma_com; - ar & temp_distance; - ar & gamma_distance; - ar & r_cut; - ar & pref1_com; - ar & pref2_com; - ar & pref1_dist; - ar & pref2_dist; - } + std::weak_ptr m_thermostat; }; diff --git a/src/core/bonded_interactions/thermalized_bond_kernel.hpp b/src/core/bonded_interactions/thermalized_bond_kernel.hpp index 2b13b08a8d6..90b378dbd1d 100644 --- a/src/core/bonded_interactions/thermalized_bond_kernel.hpp +++ b/src/core/bonded_interactions/thermalized_bond_kernel.hpp @@ -25,7 +25,6 @@ #include "Particle.hpp" #include "random.hpp" -#include "system/System.hpp" #include "thermostat.hpp" #include @@ -54,8 +53,9 @@ ThermalizedBond::forces(Particle const &p1, Particle const &p2, auto const sqrt_mass_red = sqrt(p1.mass() * p2.mass() / mass_tot); auto const com_vel = mass_tot_inv * (p1.mass() * p1.v() + p2.mass() * p2.v()); auto const dist_vel = p2.v() - p1.v(); - auto const &thermalized_bond = - *::System::get_system().thermostat->thermalized_bond; + auto const thermostat_view = m_thermostat.lock(); + assert(thermostat_view); + auto const &thermalized_bond = *thermostat_view->thermalized_bond; Utils::Vector3d force1{}; Utils::Vector3d force2{}; diff --git a/src/core/cluster_analysis/Cluster.cpp b/src/core/cluster_analysis/Cluster.cpp index 841b056f1d2..36665e96aa3 100644 --- a/src/core/cluster_analysis/Cluster.cpp +++ b/src/core/cluster_analysis/Cluster.cpp @@ -29,7 +29,6 @@ #include "Particle.hpp" #include "errorhandling.hpp" #include "particle_node.hpp" -#include "system/System.hpp" #include @@ -52,7 +51,8 @@ Utils::Vector3d Cluster::center_of_mass() { Utils::Vector3d Cluster::center_of_mass_subcluster(std::vector const &particle_ids) { sanity_checks(); - auto const &box_geo = *System::get_system().box_geo; + auto const box_geo_handle = get_box_geo(); + auto const &box_geo = *box_geo_handle; Utils::Vector3d com{}; // The distances between the particles are "folded", such that all distances @@ -83,7 +83,8 @@ Cluster::center_of_mass_subcluster(std::vector const &particle_ids) { double Cluster::longest_distance() { sanity_checks(); - auto const &box_geo = *System::get_system().box_geo; + auto const box_geo_handle = get_box_geo(); + auto const &box_geo = *box_geo_handle; double ld = 0.; for (auto a = particles.begin(); a != particles.end(); a++) { for (auto b = a; ++b != particles.end();) { @@ -107,7 +108,8 @@ double Cluster::radius_of_gyration() { double Cluster::radius_of_gyration_subcluster(std::vector const &particle_ids) { sanity_checks(); - auto const &box_geo = *System::get_system().box_geo; + auto const box_geo_handle = get_box_geo(); + auto const &box_geo = *box_geo_handle; // Center of mass Utils::Vector3d com = center_of_mass_subcluster(particle_ids); double sum_sq_dist = 0.; @@ -136,7 +138,8 @@ std::vector sort_indices(const std::vector &v) { std::pair Cluster::fractal_dimension(double dr) { #ifdef GSL sanity_checks(); - auto const &box_geo = *System::get_system().box_geo; + auto const box_geo_handle = get_box_geo(); + auto const &box_geo = *box_geo_handle; Utils::Vector3d com = center_of_mass(); // calculate Df using linear regression on the logarithms of the radii of // gyration against the number of particles in sub-clusters. Particles are @@ -186,7 +189,8 @@ std::pair Cluster::fractal_dimension(double dr) { } void Cluster::sanity_checks() const { - auto const &box_geo = *System::get_system().box_geo; + auto const box_geo_handle = get_box_geo(); + auto const &box_geo = *box_geo_handle; if (box_geo.type() != BoxType::CUBOID) { throw std::runtime_error( "Cluster analysis is not compatible with non-cuboid box types"); diff --git a/src/core/cluster_analysis/Cluster.hpp b/src/core/cluster_analysis/Cluster.hpp index 85bed84cd8d..b76d279c60b 100644 --- a/src/core/cluster_analysis/Cluster.hpp +++ b/src/core/cluster_analysis/Cluster.hpp @@ -16,14 +16,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CLUSTER_ANALYSIS_CLUSTER_HPP -#define CLUSTER_ANALYSIS_CLUSTER_HPP +#pragma once + +#include "BoxGeometry.hpp" #include "Particle.hpp" #include #include +#include +#include #include #include @@ -32,6 +35,8 @@ namespace ClusterAnalysis { /** @brief Represents a single cluster of particles */ class Cluster { public: + explicit Cluster(std::weak_ptr const &box_geo) + : m_box_geo{box_geo} {} /** @brief Ids of the particles in the cluster */ std::vector particles; /** @brief add a particle to the cluster */ @@ -56,8 +61,12 @@ class Cluster { private: void sanity_checks() const; + auto get_box_geo() const { + auto ptr = m_box_geo.lock(); + assert(ptr); + return ptr; + } + mutable std::weak_ptr m_box_geo; }; } // namespace ClusterAnalysis - -#endif diff --git a/src/core/cluster_analysis/ClusterStructure.cpp b/src/core/cluster_analysis/ClusterStructure.cpp index e53acae9dc1..b8b0f833845 100644 --- a/src/core/cluster_analysis/ClusterStructure.cpp +++ b/src/core/cluster_analysis/ClusterStructure.cpp @@ -23,7 +23,6 @@ #include "PartCfg.hpp" #include "errorhandling.hpp" #include "particle_node.hpp" -#include "system/System.hpp" #include @@ -54,7 +53,9 @@ void ClusterStructure::run_for_all_pairs() { sanity_checks(); // Iterate over pairs - PartCfg partCfg{*System::get_system().box_geo}; + auto const box_geo_handle = get_box_geo(); + auto const &box_geo = *box_geo_handle; + PartCfg partCfg{box_geo}; Utils::for_each_pair(partCfg.begin(), partCfg.end(), [this](const Particle &p1, const Particle &p2) { this->add_pair(p1, p2); @@ -65,7 +66,9 @@ void ClusterStructure::run_for_all_pairs() { void ClusterStructure::run_for_bonded_particles() { clear(); sanity_checks(); - PartCfg partCfg{*System::get_system().box_geo}; + auto const box_geo_handle = get_box_geo(); + auto const &box_geo = *box_geo_handle; + PartCfg partCfg{box_geo}; for (const auto &p : partCfg) { for (auto const bond : p.bonds()) { if (bond.partner_ids().size() == 1) { @@ -148,7 +151,7 @@ void ClusterStructure::merge_clusters() { to_be_changed.emplace_back(it.first, cid); // Empty cluster object if (clusters.find(cid) == clusters.end()) { - clusters[cid] = std::make_shared(); + clusters[cid] = std::make_shared(m_box_geo); } } @@ -164,7 +167,7 @@ void ClusterStructure::merge_clusters() { // If this is the first particle in this cluster, instance a new cluster // object if (clusters.find(it.second) == clusters.end()) { - clusters[it.second] = std::make_shared(); + clusters[it.second] = std::make_shared(m_box_geo); } clusters[it.second]->particles.push_back(it.first); } @@ -196,7 +199,7 @@ int ClusterStructure::get_next_free_cluster_id() { } void ClusterStructure::sanity_checks() const { - if (System::get_system().box_geo->type() != BoxType::CUBOID) { + if (get_box_geo()->type() != BoxType::CUBOID) { throw std::runtime_error( "Cluster analysis is not compatible with non-cuboid box types"); } diff --git a/src/core/cluster_analysis/ClusterStructure.hpp b/src/core/cluster_analysis/ClusterStructure.hpp index a0b18fb0704..20d0e379d5a 100644 --- a/src/core/cluster_analysis/ClusterStructure.hpp +++ b/src/core/cluster_analysis/ClusterStructure.hpp @@ -17,9 +17,9 @@ * along with this program. If not, see . */ -#ifndef CLUSTER_ANALYSIS_CLUSTER_STRUCTURE_HPP -#define CLUSTER_ANALYSIS_CLUSTER_STRUCTURE_HPP +#pragma once +#include "BoxGeometry.hpp" #include "pair_criteria/PairCriterion.hpp" #include "Cluster.hpp" @@ -58,6 +58,10 @@ class ClusterStructure { return *m_pair_criterion; } + void attach(std::weak_ptr const &box_geo) { + m_box_geo = box_geo; + } + private: /** @brief Clusters that turn out to be the same during the analysis process * (i.e., if two particles are neighbors that already belong to different @@ -77,7 +81,12 @@ class ClusterStructure { /** @brief Get next free cluster id */ inline int get_next_free_cluster_id(); void sanity_checks() const; + auto get_box_geo() const { + auto ptr = m_box_geo.lock(); + assert(ptr); + return ptr; + } + mutable std::weak_ptr m_box_geo; }; } // namespace ClusterAnalysis -#endif diff --git a/src/core/collision.cpp b/src/core/collision.cpp index 62b4d6b3a4f..0070d90100b 100644 --- a/src/core/collision.cpp +++ b/src/core/collision.cpp @@ -17,9 +17,12 @@ * along with this program. If not, see . */ -#include "collision.hpp" +#include "config/config.hpp" #ifdef COLLISION_DETECTION + +#include "collision.hpp" + #include "BoxGeometry.hpp" #include "Particle.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" @@ -41,6 +44,7 @@ #include #include +#include #include #include #include @@ -48,12 +52,6 @@ #include #include -/// Data type holding the info about a single collision -struct CollisionPair { - int pp1; // 1st particle id - int pp2; // 2nd particle id -}; - namespace boost { namespace serialization { template @@ -64,14 +62,6 @@ void serialize(Archive &ar, CollisionPair &c, const unsigned int) { } // namespace serialization } // namespace boost -/// During force calculation, colliding particles are recorded in the queue. -/// The queue is processed after force calculation, when it is safe to add -/// particles. -static std::vector local_collision_queue; - -/// Parameters for collision detection -Collision_parameters collision_params; - namespace { Particle &get_part(CellStructure &cell_structure, int id) { auto const p = cell_structure.get_local_particle(id); @@ -85,43 +75,27 @@ Particle &get_part(CellStructure &cell_structure, int id) { } } // namespace -/** @brief Return true if a bond between the centers of the colliding - * particles needs to be placed. At this point, all modes need this. - */ -static bool bind_centers() { - // Note that the glue to surface mode adds bonds between the centers - // but does so later in the process. This is needed to guarantee that - // a particle can only be glued once, even if queued twice in a single - // time step - return collision_params.mode != CollisionModeType::OFF and - collision_params.mode != CollisionModeType::GLUE_TO_SURF; -} - -static int get_bond_num_partners(int bond_id) { - return number_of_partners(*bonded_ia_params.at(bond_id)); -} - -void Collision_parameters::initialize() { +void CollisionDetection::initialize() { // If mode is OFF, no further checks - if (collision_params.mode == CollisionModeType::OFF) { + if (mode == CollisionModeType::OFF) { return; } // Validate distance - if (collision_params.mode != CollisionModeType::OFF) { - if (collision_params.distance <= 0.) { + if (mode != CollisionModeType::OFF) { + if (distance <= 0.) { throw std::domain_error("Parameter 'distance' must be > 0"); } // Cache square of cutoff - collision_params.distance2 = Utils::sqr(collision_params.distance); + distance_sq = Utils::sqr(distance); } #ifndef VIRTUAL_SITES_RELATIVE // The collision modes involving virtual sites also require the creation of a // bond between the colliding // If we don't have virtual sites, virtual site binding isn't possible. - if ((collision_params.mode == CollisionModeType::BIND_VS) || - (collision_params.mode == CollisionModeType::GLUE_TO_SURF)) { + if ((mode == CollisionModeType::BIND_VS) or + (mode == CollisionModeType::GLUE_TO_SURF)) { throw std::runtime_error("collision modes based on virtual sites require " "the VIRTUAL_SITES_RELATIVE feature"); } @@ -129,140 +103,119 @@ void Collision_parameters::initialize() { #ifdef VIRTUAL_SITES // Check vs placement parameter - if (collision_params.mode == CollisionModeType::BIND_VS) { - if ((collision_params.vs_placement < 0.) or - (collision_params.vs_placement > 1.)) { + if (mode == CollisionModeType::BIND_VS) { + if (vs_placement < 0. or vs_placement > 1.) { throw std::domain_error( "Parameter 'vs_placement' must be between 0 and 1"); } } #endif - auto &system = System::get_system(); + auto &system = get_system(); + auto &bonded_ias = *system.bonded_ias; auto &nonbonded_ias = *system.nonbonded_ias; - // Check if bonded ia exist - if ((collision_params.mode == CollisionModeType::BIND_CENTERS) and - !bonded_ia_params.contains(collision_params.bond_centers)) { - throw std::runtime_error( - "Bond in parameter 'bond_centers' was not added to the system"); - } - - if ((collision_params.mode == CollisionModeType::BIND_VS) and - !bonded_ia_params.contains(collision_params.bond_vs)) { - throw std::runtime_error( - "Bond in parameter 'bond_vs' was not added to the system"); - } + // Check if bond exists + assert(mode != CollisionModeType::BIND_CENTERS or + bonded_ias.contains(bond_centers)); + assert(mode != CollisionModeType::BIND_VS or bonded_ias.contains(bond_vs)); // If the bond type to bind particle centers is not a pair bond... // Check that the bonds have the right number of partners - if ((collision_params.mode == CollisionModeType::BIND_CENTERS) and - (get_bond_num_partners(collision_params.bond_centers) != 1)) { + if ((mode == CollisionModeType::BIND_CENTERS) and + (number_of_partners(*bonded_ias.at(bond_centers)) != 1)) { throw std::runtime_error("The bond type to be used for binding particle " "centers needs to be a pair bond"); } // The bond between the virtual sites can be pair or triple - if ((collision_params.mode == CollisionModeType::BIND_VS) and - (get_bond_num_partners(collision_params.bond_vs) != 1 and - get_bond_num_partners(collision_params.bond_vs) != 2)) { + if ((mode == CollisionModeType::BIND_VS) and + (number_of_partners(*bonded_ias.at(bond_vs)) != 1 and + number_of_partners(*bonded_ias.at(bond_vs)) != 2)) { throw std::runtime_error("The bond type to be used for binding virtual " "sites needs to be a pair bond"); } // Create particle types - if (collision_params.mode == CollisionModeType::BIND_VS) { - if (collision_params.vs_particle_type < 0) { + if (mode == CollisionModeType::BIND_VS) { + if (vs_particle_type < 0) { throw std::domain_error("Collision detection particle type for virtual " "sites needs to be >=0"); } - nonbonded_ias.make_particle_type_exist(collision_params.vs_particle_type); + nonbonded_ias.make_particle_type_exist(vs_particle_type); } - if (collision_params.mode == CollisionModeType::GLUE_TO_SURF) { - if (collision_params.vs_particle_type < 0) { + if (mode == CollisionModeType::GLUE_TO_SURF) { + if (vs_particle_type < 0) { throw std::domain_error("Collision detection particle type for virtual " "sites needs to be >=0"); } - nonbonded_ias.make_particle_type_exist(collision_params.vs_particle_type); + nonbonded_ias.make_particle_type_exist(vs_particle_type); - if (collision_params.part_type_to_be_glued < 0) { + if (part_type_to_be_glued < 0) { throw std::domain_error("Collision detection particle type to be glued " "needs to be >=0"); } - nonbonded_ias.make_particle_type_exist( - collision_params.part_type_to_be_glued); + nonbonded_ias.make_particle_type_exist(part_type_to_be_glued); - if (collision_params.part_type_to_attach_vs_to < 0) { + if (part_type_to_attach_vs_to < 0) { throw std::domain_error("Collision detection particle type to attach " "the virtual site to needs to be >=0"); } - nonbonded_ias.make_particle_type_exist( - collision_params.part_type_to_attach_vs_to); + nonbonded_ias.make_particle_type_exist(part_type_to_attach_vs_to); - if (collision_params.part_type_after_glueing < 0) { + if (part_type_after_glueing < 0) { throw std::domain_error("Collision detection particle type after gluing " "needs to be >=0"); } - nonbonded_ias.make_particle_type_exist( - collision_params.part_type_after_glueing); + nonbonded_ias.make_particle_type_exist(part_type_after_glueing); } system.on_short_range_ia_change(); } -void prepare_local_collision_queue() { local_collision_queue.clear(); } - -void queue_collision(const int part1, const int part2) { - local_collision_queue.push_back({part1, part2}); -} - /** @brief Calculate position of vs for GLUE_TO_SURFACE mode. * Returns id of particle to bind vs to. */ -static auto const &glue_to_surface_calc_vs_pos(Particle const &p1, - Particle const &p2, - BoxGeometry const &box_geo, - Utils::Vector3d &pos) { - double c; +static auto const &glue_to_surface_calc_vs_pos( + Particle const &p1, Particle const &p2, BoxGeometry const &box_geo, + CollisionDetection const &collision_params, Utils::Vector3d &pos) { + double ratio; auto const vec21 = box_geo.get_mi_vector(p1.pos(), p2.pos()); auto const dist = vec21.norm(); // Find out, which is the particle to be glued. if ((p1.type() == collision_params.part_type_to_be_glued) and (p2.type() == collision_params.part_type_to_attach_vs_to)) { - c = 1. - collision_params.dist_glued_part_to_vs / dist; + ratio = 1. - collision_params.dist_glued_part_to_vs / dist; } else if ((p2.type() == collision_params.part_type_to_be_glued) and (p1.type() == collision_params.part_type_to_attach_vs_to)) { - c = collision_params.dist_glued_part_to_vs / dist; + ratio = collision_params.dist_glued_part_to_vs / dist; } else { throw std::runtime_error("This should never be thrown. Bug."); } - pos = p2.pos() + vec21 * c; + pos = p2.pos() + vec21 * ratio; if (p1.type() == collision_params.part_type_to_attach_vs_to) return p1; return p2; } -static void bind_at_point_of_collision_calc_vs_pos(Particle const &p1, - Particle const &p2, - BoxGeometry const &box_geo, - Utils::Vector3d &pos1, - Utils::Vector3d &pos2) { +static void bind_at_point_of_collision_calc_vs_pos( + Particle const &p1, Particle const &p2, BoxGeometry const &box_geo, + CollisionDetection const &collision_params, Utils::Vector3d &pos1, + Utils::Vector3d &pos2) { auto const vec21 = box_geo.get_mi_vector(p1.pos(), p2.pos()); pos1 = p1.pos() - vec21 * collision_params.vs_placement; pos2 = p1.pos() - vec21 * (1. - collision_params.vs_placement); } #ifdef VIRTUAL_SITES_RELATIVE -static void place_vs_and_relate_to_particle(CellStructure &cell_structure, - BoxGeometry const &box_geo, - double const min_global_cut, - int const current_vs_pid, - Utils::Vector3d const &pos, - int const relate_to) { +static void place_vs_and_relate_to_particle( + CellStructure &cell_structure, BoxGeometry const &box_geo, + CollisionDetection const &collision_params, double const min_global_cut, + int const current_vs_pid, Utils::Vector3d const &pos, int const relate_to) { Particle new_part; new_part.id() = current_vs_pid; new_part.pos() = pos; @@ -272,10 +225,11 @@ static void place_vs_and_relate_to_particle(CellStructure &cell_structure, p_vs->type() = collision_params.vs_particle_type; } -static void bind_at_poc_create_bond_between_vs(CellStructure &cell_structure, - int const current_vs_pid, - CollisionPair const &c) { - switch (get_bond_num_partners(collision_params.bond_vs)) { +static void bind_at_poc_create_bond_between_vs( + CellStructure &cell_structure, BondedInteractionsMap const &bonded_ias, + CollisionDetection const &collision_params, int const current_vs_pid, + CollisionPair const &c) { + switch (number_of_partners(*bonded_ias.at(collision_params.bond_vs))) { case 1: { // Create bond between the virtual particles const int bondG[] = {current_vs_pid - 2}; @@ -297,11 +251,10 @@ static void bind_at_poc_create_bond_between_vs(CellStructure &cell_structure, } } -static void glue_to_surface_bind_part_to_vs(Particle const *const p1, - Particle const *const p2, - int const vs_pid_plus_one, - CollisionPair const &, - CellStructure &cell_structure) { +static void glue_to_surface_bind_part_to_vs( + Particle const *const p1, Particle const *const p2, + int const vs_pid_plus_one, CollisionDetection const &collision_params, + CellStructure &cell_structure) { // Create bond between the virtual particles const int bondG[] = {vs_pid_plus_one - 1}; @@ -318,22 +271,23 @@ static void glue_to_surface_bind_part_to_vs(Particle const *const p1, #endif // VIRTUAL_SITES_RELATIVE -std::vector gather_global_collision_queue() { - std::vector res = local_collision_queue; +static auto gather_collision_queue(std::vector const &local) { + auto global = local; if (comm_cart.size() > 1) { - Utils::Mpi::gather_buffer(res, comm_cart); - boost::mpi::broadcast(comm_cart, res, 0); + Utils::Mpi::gather_buffer(global, ::comm_cart); + boost::mpi::broadcast(::comm_cart, global, 0); } - return res; + return global; } -// Handle the collisions stored in the queue -void handle_collisions(CellStructure &cell_structure) { +void CollisionDetection::handle_collisions(CellStructure &cell_structure) { // Note that the glue to surface mode adds bonds between the centers // but does so later in the process. This is needed to guarantee that // a particle can only be glued once, even if queued twice in a single // time step - if (bind_centers()) { + auto const bind_centers = mode != CollisionModeType::OFF and + mode != CollisionModeType::GLUE_TO_SURF; + if (bind_centers) { for (auto &c : local_collision_queue) { // put the bond to the non-ghost particle; at least one partner always is if (cell_structure.get_local_particle(c.pp1)->is_ghost()) { @@ -342,34 +296,31 @@ void handle_collisions(CellStructure &cell_structure) { const int bondG[] = {c.pp2}; - get_part(cell_structure, c.pp1) - .bonds() - .insert({collision_params.bond_centers, bondG}); + get_part(cell_structure, c.pp1).bonds().insert({bond_centers, bondG}); } } -// Virtual sites based collision schemes #ifdef VIRTUAL_SITES_RELATIVE - auto &system = System::get_system(); + auto &system = get_system(); auto const &box_geo = *system.box_geo; auto const min_global_cut = system.get_min_global_cut(); - if ((collision_params.mode == CollisionModeType::BIND_VS) || - (collision_params.mode == CollisionModeType::GLUE_TO_SURF)) { + if ((mode == CollisionModeType::BIND_VS) || + (mode == CollisionModeType::GLUE_TO_SURF)) { // Gather the global collision queue, because only one node has a collision // across node boundaries in its queue. // The other node might still have to change particle properties on its // non-ghost particle - auto gathered_queue = gather_global_collision_queue(); + auto global_collision_queue = gather_collision_queue(local_collision_queue); // Sync max_seen_part auto const global_max_seen_particle = boost::mpi::all_reduce( - comm_cart, cell_structure.get_max_local_particle_id(), + ::comm_cart, cell_structure.get_max_local_particle_id(), boost::mpi::maximum()); int current_vs_pid = global_max_seen_particle + 1; // Iterate over global collision queue - for (auto &c : gathered_queue) { + for (auto &c : global_collision_queue) { // Get particle pointers Particle *p1 = cell_structure.get_local_particle(c.pp1); @@ -384,27 +335,25 @@ void handle_collisions(CellStructure &cell_structure) { // number of particles created by other nodes if (((!p1 or p1->is_ghost()) and (!p2 or p2->is_ghost())) or !p1 or !p2) { // Increase local counters - if (collision_params.mode == CollisionModeType::BIND_VS) { + if (mode == CollisionModeType::BIND_VS) { current_vs_pid++; } // For glue to surface, we have only one vs current_vs_pid++; - if (collision_params.mode == CollisionModeType::GLUE_TO_SURF) { - if (p1) - if (p1->type() == collision_params.part_type_to_be_glued) { - p1->type() = collision_params.part_type_after_glueing; - } - if (p2) - if (p2->type() == collision_params.part_type_to_be_glued) { - p2->type() = collision_params.part_type_after_glueing; - } + if (mode == CollisionModeType::GLUE_TO_SURF) { + if (p1 and p1->type() == part_type_to_be_glued) { + p1->type() = part_type_after_glueing; + } + if (p2 and p2->type() == part_type_to_be_glued) { + p2->type() = part_type_after_glueing; + } } // mode glue to surface } else { // We consider the pair because one particle // is local to the node and the other is local or ghost // If we are in the two vs mode // Virtual site related to first particle in the collision - if (collision_params.mode == CollisionModeType::BIND_VS) { + if (mode == CollisionModeType::BIND_VS) { Utils::Vector3d pos1, pos2; // Enable rotation on the particles to which vs will be attached @@ -412,11 +361,12 @@ void handle_collisions(CellStructure &cell_structure) { p2->set_can_rotate_all_axes(); // Positions of the virtual sites - bind_at_point_of_collision_calc_vs_pos(*p1, *p2, box_geo, pos1, pos2); + bind_at_point_of_collision_calc_vs_pos(*p1, *p2, box_geo, *this, pos1, + pos2); auto handle_particle = [&](Particle *p, Utils::Vector3d const &pos) { if (not p->is_ghost()) { - place_vs_and_relate_to_particle(cell_structure, box_geo, + place_vs_and_relate_to_particle(cell_structure, box_geo, *this, min_global_cut, current_vs_pid, pos, p->id()); // Particle storage locations may have changed due to @@ -437,18 +387,18 @@ void handle_collisions(CellStructure &cell_structure) { current_vs_pid++; // Create bonds between the vs. - bind_at_poc_create_bond_between_vs(cell_structure, current_vs_pid, c); + bind_at_poc_create_bond_between_vs(cell_structure, *system.bonded_ias, + *this, current_vs_pid, c); } // mode VS - if (collision_params.mode == CollisionModeType::GLUE_TO_SURF) { + if (mode == CollisionModeType::GLUE_TO_SURF) { // If particles are made inert by a type change on collision: // We skip the pair if one of the particles has already reacted // but we still increase the particle counters, as other nodes // can not always know whether or not a vs is placed - if (collision_params.part_type_after_glueing != - collision_params.part_type_to_be_glued) { - if ((p1->type() == collision_params.part_type_after_glueing) || - (p2->type() == collision_params.part_type_after_glueing)) { + if (part_type_after_glueing != part_type_to_be_glued) { + if ((p1->type() == part_type_after_glueing) || + (p2->type() == part_type_after_glueing)) { current_vs_pid++; continue; } @@ -456,7 +406,7 @@ void handle_collisions(CellStructure &cell_structure) { Utils::Vector3d pos; Particle const &attach_vs_to = - glue_to_surface_calc_vs_pos(*p1, *p2, box_geo, pos); + glue_to_surface_calc_vs_pos(*p1, *p2, box_geo, *this, pos); // Add a bond between the centers of the colliding particles // The bond is placed on the node that has p1 @@ -464,20 +414,20 @@ void handle_collisions(CellStructure &cell_structure) { const int bondG[] = {c.pp2}; get_part(cell_structure, c.pp1) .bonds() - .insert({collision_params.bond_centers, bondG}); + .insert({bond_centers, bondG}); } // Change type of particle being attached, to make it inert - if (p1->type() == collision_params.part_type_to_be_glued) { - p1->type() = collision_params.part_type_after_glueing; + if (p1->type() == part_type_to_be_glued) { + p1->type() = part_type_after_glueing; } - if (p2->type() == collision_params.part_type_to_be_glued) { - p2->type() = collision_params.part_type_after_glueing; + if (p2->type() == part_type_to_be_glued) { + p2->type() = part_type_after_glueing; } // Vs placement happens on the node that has p1 if (!attach_vs_to.is_ghost()) { - place_vs_and_relate_to_particle(cell_structure, box_geo, + place_vs_and_relate_to_particle(cell_structure, box_geo, *this, min_global_cut, current_vs_pid, pos, attach_vs_to.id()); // Particle storage locations may have changed due to @@ -488,7 +438,7 @@ void handle_collisions(CellStructure &cell_structure) { } else { // Just update the books current_vs_pid++; } - glue_to_surface_bind_part_to_vs(p1, p2, current_vs_pid, c, + glue_to_surface_bind_part_to_vs(p1, p2, current_vs_pid, *this, cell_structure); } } // we considered the pair @@ -500,7 +450,7 @@ void handle_collisions(CellStructure &cell_structure) { #endif // If any node had a collision, all nodes need to resort - if (!gathered_queue.empty()) { + if (not global_collision_queue.empty()) { cell_structure.set_resort_particles(Cells::RESORT_GLOBAL); cell_structure.update_ghosts_and_resort_particle( Cells::DATA_PART_PROPERTIES | Cells::DATA_PART_BONDS); @@ -509,7 +459,7 @@ void handle_collisions(CellStructure &cell_structure) { } // are we in one of the vs_based methods #endif // defined VIRTUAL_SITES_RELATIVE - local_collision_queue.clear(); + clear_queue(); } #endif // COLLISION_DETECTION diff --git a/src/core/collision.hpp b/src/core/collision.hpp index 29c0bfe9bd1..0a5545c16e5 100644 --- a/src/core/collision.hpp +++ b/src/core/collision.hpp @@ -21,10 +21,11 @@ #include "config/config.hpp" -#include "cell_system/CellStructure.hpp" - #include "BondList.hpp" #include "Particle.hpp" +#include "cell_system/CellStructure.hpp" +#include "nonbonded_interactions/nonbonded_interaction_data.hpp" +#include "system/Leaf.hpp" /** @brief Protocols for collision handling. */ enum class CollisionModeType : int { @@ -43,10 +44,16 @@ enum class CollisionModeType : int { GLUE_TO_SURF = 3, }; -class Collision_parameters { +/// Data type holding the info about a single collision +struct CollisionPair { + int pp1; // 1st particle id + int pp2; // 2nd particle id +}; + +class CollisionDetection : public System::Leaf { public: - Collision_parameters() - : mode(CollisionModeType::OFF), distance(0.), distance2(0.), + CollisionDetection() + : mode(CollisionModeType::OFF), distance(0.), distance_sq(0.), bond_centers(-1), bond_vs(-1) {} /// collision protocol @@ -54,7 +61,7 @@ class Collision_parameters { /// distance at which particles are bound double distance; // Square of distance at which particle are bound - double distance2; + double distance_sq; /// bond type used between centers of colliding particles int bond_centers; @@ -82,70 +89,63 @@ class Collision_parameters { /** @brief Validates parameters and creates particle types if needed. */ void initialize(); -}; -/// Parameters for collision detection -extern Collision_parameters collision_params; + auto cutoff() const { + if (mode != CollisionModeType::OFF) { + return distance; + } + return INACTIVE_CUTOFF; + } -#ifdef COLLISION_DETECTION + /// Handle queued collisions + void handle_collisions(CellStructure &cell_structure); -void prepare_local_collision_queue(); + void clear_queue() { local_collision_queue.clear(); } -/// Handle the collisions recorded in the queue -void handle_collisions(CellStructure &cell_structure); + /** @brief Detect (and queue) a collision between the given particles. */ + void detect_collision(Particle const &p1, Particle const &p2, + double const dist_sq) { + if (dist_sq > distance_sq) + return; -/** @brief Add the collision between the given particle ids to the collision - * queue - */ -void queue_collision(int part1, int part2); - -/** @brief Check additional criteria for the glue_to_surface collision mode */ -inline bool glue_to_surface_criterion(Particle const &p1, Particle const &p2) { - return (((p1.type() == collision_params.part_type_to_be_glued) && - (p2.type() == collision_params.part_type_to_attach_vs_to)) || - ((p2.type() == collision_params.part_type_to_be_glued) && - (p1.type() == collision_params.part_type_to_attach_vs_to))); -} - -/** @brief Detect (and queue) a collision between the given particles. */ -inline void detect_collision(Particle const &p1, Particle const &p2, - double const dist2) { - if (dist2 > collision_params.distance2) - return; - - // If we are in the glue to surface mode, check that the particles - // are of the right type - if (collision_params.mode == CollisionModeType::GLUE_TO_SURF) - if (!glue_to_surface_criterion(p1, p2)) + // If we are in the glue to surface mode, check that the particles + // are of the right type + if (mode == CollisionModeType::GLUE_TO_SURF) + if (!glue_to_surface_criterion(p1, p2)) + return; + + // Ignore virtual particles + if (p1.is_virtual() or p2.is_virtual()) return; - // Ignore virtual particles - if (p1.is_virtual() or p2.is_virtual()) - return; + // Check, if there's already a bond between the particles + if (pair_bond_exists_on(p1.bonds(), p2.id(), bond_centers)) + return; - // Check, if there's already a bond between the particles - if (pair_bond_exists_on(p1.bonds(), p2.id(), collision_params.bond_centers)) - return; + if (pair_bond_exists_on(p2.bonds(), p1.id(), bond_centers)) + return; - if (pair_bond_exists_on(p2.bonds(), p1.id(), collision_params.bond_centers)) - return; + /* If we're still here, there is no previous bond between the particles, + we have a new collision */ - /* If we're still here, there is no previous bond between the particles, - we have a new collision */ + // do not create bond between ghost particles + if (p1.is_ghost() and p2.is_ghost()) { + return; + } + local_collision_queue.push_back({p1.id(), p2.id()}); + } - // do not create bond between ghost particles - if (p1.is_ghost() and p2.is_ghost()) { - return; + // private: + /// During force calculation, colliding particles are recorded in the queue. + /// The queue is processed after force calculation, when it is safe to add + /// particles. + std::vector local_collision_queue; + + /** @brief Check additional criteria for the glue_to_surface collision mode */ + bool glue_to_surface_criterion(Particle const &p1, Particle const &p2) const { + return (((p1.type() == part_type_to_be_glued) and + (p2.type() == part_type_to_attach_vs_to)) or + ((p2.type() == part_type_to_be_glued) and + (p1.type() == part_type_to_attach_vs_to))); } - queue_collision(p1.id(), p2.id()); -} - -#endif // COLLISION_DETECTION - -inline double collision_detection_cutoff() { -#ifdef COLLISION_DETECTION - if (collision_params.mode != CollisionModeType::OFF) - return collision_params.distance; -#endif - return -1.; -} +}; diff --git a/src/core/constraints.cpp b/src/core/constraints.cpp deleted file mode 100644 index d5bbe12dd35..00000000000 --- a/src/core/constraints.cpp +++ /dev/null @@ -1,23 +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 . - */ -#include "constraints.hpp" - -namespace Constraints { -Constraints constraints; -} diff --git a/src/core/constraints.hpp b/src/core/constraints.hpp deleted file mode 100644 index 6e74937d60f..00000000000 --- a/src/core/constraints.hpp +++ /dev/null @@ -1,29 +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 CONSTRAINTS_HPP -#define CONSTRAINTS_HPP - -#include "ParticleRange.hpp" -#include "constraints/Constraint.hpp" -#include "constraints/Constraints.hpp" - -namespace Constraints { -extern Constraints constraints; -} -#endif diff --git a/src/core/constraints/CMakeLists.txt b/src/core/constraints/CMakeLists.txt index f9648321f48..9f9752d31f6 100644 --- a/src/core/constraints/CMakeLists.txt +++ b/src/core/constraints/CMakeLists.txt @@ -18,5 +18,7 @@ # target_sources( - espresso_core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/HomogeneousMagneticField.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ShapeBasedConstraint.cpp) + espresso_core + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/HomogeneousMagneticField.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Constraints.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ShapeBasedConstraint.cpp) diff --git a/src/core/constraints/Constraint.hpp b/src/core/constraints/Constraint.hpp index 875834b636f..9aa13a23d1e 100644 --- a/src/core/constraints/Constraint.hpp +++ b/src/core/constraints/Constraint.hpp @@ -16,14 +16,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CONSTRAINTS_CONSTRAINT_HPP -#define CONSTRAINTS_CONSTRAINT_HPP + +#pragma once #include "Observable_stat.hpp" -#include "Particle.hpp" #include +struct Particle; +struct ParticleForce; + namespace Constraints { class Constraint { public: @@ -58,6 +60,4 @@ class Constraint { virtual ~Constraint() = default; }; -} /* namespace Constraints */ - -#endif +} // namespace Constraints diff --git a/src/core/constraints/Constraints.cpp b/src/core/constraints/Constraints.cpp new file mode 100644 index 00000000000..ef1eb35f633 --- /dev/null +++ b/src/core/constraints/Constraints.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2010-2024 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Constraints.hpp" +#include "BoxGeometry.hpp" +#include "Constraint.hpp" +#include "Observable_stat.hpp" +#include "system/System.hpp" + +#include +#include +#include +#include + +namespace Constraints { + +void Constraints::add(std::shared_ptr const &constraint) { + auto &system = get_system(); + if (not constraint->fits_in_box(system.box_geo->length())) { + throw std::runtime_error("Constraint not compatible with box size."); + } + assert(not contains(constraint)); + m_constraints.emplace_back(constraint); + system.on_constraint_change(); +} +void Constraints::remove(std::shared_ptr const &constraint) { + auto &system = get_system(); + assert(contains(constraint)); + std::erase(m_constraints, constraint); + system.on_constraint_change(); +} +void Constraints::add_forces(ParticleRange &particles, double time) const { + if (m_constraints.empty()) + return; + + reset_forces(); + auto const &box_geo = *get_system().box_geo; + + for (auto &p : particles) { + auto const pos = box_geo.folded_position(p.pos()); + ParticleForce force{}; + for (auto const &constraint : *this) { + force += constraint->force(p, pos, time); + } + + p.force_and_torque() += force; + } +} + +void Constraints::add_energy(ParticleRange const &particles, double time, + Observable_stat &obs_energy) const { + if (m_constraints.empty()) + return; + + auto const &box_geo = *get_system().box_geo; + + for (auto const &p : particles) { + auto const pos = box_geo.folded_position(p.pos()); + + for (auto const &constraint : *this) { + constraint->add_energy(p, pos, time, obs_energy); + } + } +} + +} // namespace Constraints diff --git a/src/core/constraints/Constraints.hpp b/src/core/constraints/Constraints.hpp index 89076a26b68..0609023553b 100644 --- a/src/core/constraints/Constraints.hpp +++ b/src/core/constraints/Constraints.hpp @@ -16,21 +16,22 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_CONSTRAINTS_CONSTRAINTS_HPP -#define CORE_CONSTRAINTS_CONSTRAINTS_HPP -#include "BoxGeometry.hpp" -#include "Observable_stat.hpp" -#include "system/System.hpp" +#pragma once + +#include "Constraint.hpp" +#include "system/Leaf.hpp" #include -#include #include #include #include +class ParticleRange; +class Observable_stat; + namespace Constraints { -template class Constraints { +class Constraints : public System::Leaf { using container_type = std::vector>; public: @@ -51,59 +52,18 @@ template class Constraints { bool contains(std::shared_ptr const &constraint) const noexcept { return std::find(begin(), end(), constraint) != end(); } - void add(std::shared_ptr const &constraint) { - auto &system = System::get_system(); - auto const &box_geo = *system.box_geo; - if (not constraint->fits_in_box(box_geo.length())) { - throw std::runtime_error("Constraint not compatible with box size."); - } - assert(not contains(constraint)); - m_constraints.emplace_back(constraint); - system.on_constraint_change(); - } - void remove(std::shared_ptr const &constraint) { - auto &system = System::get_system(); - assert(contains(constraint)); - std::erase(m_constraints, constraint); - system.on_constraint_change(); - } + void add(std::shared_ptr const &constraint); + void remove(std::shared_ptr const &constraint); iterator begin() { return m_constraints.begin(); } iterator end() { return m_constraints.end(); } const_iterator begin() const { return m_constraints.begin(); } const_iterator end() const { return m_constraints.end(); } - void add_forces(BoxGeometry const &box_geo, ParticleRange &particles, - double time) const { - if (m_constraints.empty()) - return; - - reset_forces(); + void add_forces(ParticleRange &particles, double time) const; - for (auto &p : particles) { - auto const pos = box_geo.folded_position(p.pos()); - ParticleForce force{}; - for (auto const &constraint : *this) { - force += constraint->force(p, pos, time); - } - - p.force_and_torque() += force; - } - } - - void add_energy(BoxGeometry const &box_geo, ParticleRange const &particles, - double time, Observable_stat &obs_energy) const { - if (m_constraints.empty()) - return; - - for (auto const &p : particles) { - auto const pos = box_geo.folded_position(p.pos()); - - for (auto const &constraint : *this) { - constraint->add_energy(p, pos, time, obs_energy); - } - } - } + void add_energy(ParticleRange const &particles, double time, + Observable_stat &obs_energy) const; void veto_boxl_change() const { if (not m_constraints.empty()) { @@ -115,5 +75,3 @@ template class Constraints { void on_boxl_change() const { veto_boxl_change(); } }; } // namespace Constraints - -#endif diff --git a/src/core/constraints/ExternalField.hpp b/src/core/constraints/ExternalField.hpp index f8a85a2c02d..f5c1c07ed53 100644 --- a/src/core/constraints/ExternalField.hpp +++ b/src/core/constraints/ExternalField.hpp @@ -16,10 +16,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CONSTRAINTS_EXTERNAL_FIELD_HPP -#define CONSTRAINTS_EXTERNAL_FIELD_HPP + +#pragma once #include "Constraint.hpp" +#include "Particle.hpp" #include "field_coupling/ForceField.hpp" namespace Constraints { @@ -49,6 +50,4 @@ class ExternalField : public Constraint { return impl.field().fits_in_box(box); } }; -} /* namespace Constraints */ - -#endif +} // namespace Constraints diff --git a/src/core/constraints/ExternalPotential.hpp b/src/core/constraints/ExternalPotential.hpp index f2a68b00e89..6f0b659dfe0 100644 --- a/src/core/constraints/ExternalPotential.hpp +++ b/src/core/constraints/ExternalPotential.hpp @@ -16,10 +16,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CONSTRAINTS_EXTERNAL_POTENTIAL_HPP -#define CONSTRAINTS_EXTERNAL_POTENTIAL_HPP + +#pragma once #include "Constraint.hpp" +#include "Particle.hpp" #include "field_coupling/PotentialField.hpp" namespace Constraints { @@ -51,5 +52,4 @@ class ExternalPotential : public Constraint { return impl.field().fits_in_box(box); } }; -} /* namespace Constraints */ -#endif +} // namespace Constraints diff --git a/src/core/constraints/HomogeneousMagneticField.hpp b/src/core/constraints/HomogeneousMagneticField.hpp index 3ac8940419c..dc8ba2ba043 100644 --- a/src/core/constraints/HomogeneousMagneticField.hpp +++ b/src/core/constraints/HomogeneousMagneticField.hpp @@ -16,8 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CONSTRAINTS_HOMOGENEOUSMAGNETICFIELD_HPP -#define CONSTRAINTS_HOMOGENEOUSMAGNETICFIELD_HPP + +#pragma once #include "Constraint.hpp" #include "Observable_stat.hpp" @@ -47,6 +47,4 @@ class HomogeneousMagneticField : public Constraint { Utils::Vector3d m_field; }; -} /* namespace Constraints */ - -#endif +} // namespace Constraints diff --git a/src/core/constraints/ShapeBasedConstraint.cpp b/src/core/constraints/ShapeBasedConstraint.cpp index ad25f7b5924..e97237725d7 100644 --- a/src/core/constraints/ShapeBasedConstraint.cpp +++ b/src/core/constraints/ShapeBasedConstraint.cpp @@ -21,6 +21,7 @@ #include "BoxGeometry.hpp" #include "Observable_stat.hpp" +#include "bonded_interactions/bonded_interaction_data.hpp" #include "communication.hpp" #include "config/config.hpp" #include "dpd.hpp" @@ -46,6 +47,15 @@ static bool is_active(IA_parameters const &data) { return data.max_cut != INACTIVE_CUTOFF; } +void ShapeBasedConstraint::set_type(int type) { + part_rep.type() = type; + m_system.lock()->nonbonded_ias->make_particle_type_exist(type); +} + +IA_parameters const &ShapeBasedConstraint::get_ia_param(int type) const { + return m_system.lock()->nonbonded_ias->get_ia_param(type, part_rep.type()); +} + Utils::Vector3d ShapeBasedConstraint::total_force() const { return all_reduce(comm_cart, m_local_force, std::plus<>()); } @@ -86,7 +96,8 @@ ParticleForce ShapeBasedConstraint::force(Particle const &p, double dist = 0.; Utils::Vector3d dist_vec; m_shape->calculate_dist(folded_pos, dist, dist_vec); - auto const coulomb_kernel = m_system.coulomb.pair_force_kernel(); + auto &system = *m_system.lock(); + auto const coulomb_kernel = system.coulomb.pair_force_kernel(); #ifdef DPD Utils::Vector3d dpd_force{}; @@ -96,33 +107,37 @@ ParticleForce ShapeBasedConstraint::force(Particle const &p, if (dist > 0) { outer_normal_vec = -dist_vec / dist; pf = calc_central_radial_force(ia_params, dist_vec, dist) + - calc_central_radial_charge_force(p, part_rep, ia_params, dist_vec, - dist, get_ptr(coulomb_kernel)) + +#ifdef THOLE + thole_pair_force(p, part_rep, ia_params, dist_vec, dist, + *system.bonded_ias, get_ptr(coulomb_kernel)) + +#endif calc_non_central_force(p, part_rep, ia_params, dist_vec, dist); #ifdef DPD - if (m_system.thermostat->thermo_switch & THERMO_DPD) { - dpd_force = dpd_pair_force(p, part_rep, *m_system.thermostat->dpd, - *m_system.box_geo, ia_params, dist_vec, dist, + if (system.thermostat->thermo_switch & THERMO_DPD) { + dpd_force = dpd_pair_force(p, part_rep, *system.thermostat->dpd, + *system.box_geo, ia_params, dist_vec, dist, dist * dist); // Additional use of DPD here requires counter increase - m_system.thermostat->dpd->rng_increment(); + system.thermostat->dpd->rng_increment(); } #endif } else if (m_penetrable && (dist <= 0)) { if ((!m_only_positive) && (dist < 0)) { pf = calc_central_radial_force(ia_params, dist_vec, -dist) + - calc_central_radial_charge_force(p, part_rep, ia_params, dist_vec, - -dist, get_ptr(coulomb_kernel)) + +#ifdef THOLE + thole_pair_force(p, part_rep, ia_params, dist_vec, -dist, + *system.bonded_ias, get_ptr(coulomb_kernel)) + +#endif calc_non_central_force(p, part_rep, ia_params, dist_vec, -dist); #ifdef DPD - if (m_system.thermostat->thermo_switch & THERMO_DPD) { - dpd_force = dpd_pair_force(p, part_rep, *m_system.thermostat->dpd, - *m_system.box_geo, ia_params, dist_vec, - dist, dist * dist); + if (system.thermostat->thermo_switch & THERMO_DPD) { + dpd_force = dpd_pair_force(p, part_rep, *system.thermostat->dpd, + *system.box_geo, ia_params, dist_vec, dist, + dist * dist); // Additional use of DPD here requires counter increase - m_system.thermostat->dpd->rng_increment(); + system.thermostat->dpd->rng_increment(); } #endif } @@ -150,16 +165,19 @@ void ShapeBasedConstraint::add_energy(const Particle &p, auto const &ia_params = get_ia_param(p.type()); if (is_active(ia_params)) { - auto const coulomb_kernel = m_system.coulomb.pair_energy_kernel(); + auto &system = *m_system.lock(); + auto const coulomb_kernel = system.coulomb.pair_energy_kernel(); double dist = 0.0; Utils::Vector3d vec; m_shape->calculate_dist(folded_pos, dist, vec); - if (dist > 0) { + if (dist > 0.) { energy = calc_non_bonded_pair_energy(p, part_rep, ia_params, vec, dist, + *system.bonded_ias, get_ptr(coulomb_kernel)); - } else if ((dist <= 0) && m_penetrable) { - if (!m_only_positive && (dist < 0)) { + } else if (dist <= 0. and m_penetrable) { + if (!m_only_positive and dist < 0.) { energy = calc_non_bonded_pair_energy(p, part_rep, ia_params, vec, -dist, + *system.bonded_ias, get_ptr(coulomb_kernel)); } } else { diff --git a/src/core/constraints/ShapeBasedConstraint.hpp b/src/core/constraints/ShapeBasedConstraint.hpp index 13b87500e2a..7bae22055b5 100644 --- a/src/core/constraints/ShapeBasedConstraint.hpp +++ b/src/core/constraints/ShapeBasedConstraint.hpp @@ -25,7 +25,6 @@ #include "Particle.hpp" #include "ParticleRange.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" -#include "system/System.hpp" #include #include @@ -35,14 +34,18 @@ #include #include +namespace System { +class System; +} + namespace Constraints { class ShapeBasedConstraint : public Constraint { public: - ShapeBasedConstraint(System::System const &system) + ShapeBasedConstraint() : part_rep{}, m_shape{std::make_shared()}, m_penetrable{false}, m_only_positive{false}, m_local_force{}, - m_outer_normal_force{}, m_system{system} {} + m_outer_normal_force{}, m_system{} {} void add_energy(const Particle &p, const Utils::Vector3d &folded_pos, double time, Observable_stat &energy) const override; @@ -78,13 +81,13 @@ class ShapeBasedConstraint : public Constraint { int &type() { return part_rep.type(); } Utils::Vector3d &velocity() { return part_rep.v(); } - void set_type(int type) { - part_rep.type() = type; - m_system.nonbonded_ias->make_particle_type_exist(type); - } + void set_type(int type); Utils::Vector3d total_force() const; double total_normal_force() const; + void bind_system(std::shared_ptr const &system) { + m_system = system; + } private: Particle part_rep; @@ -93,11 +96,9 @@ class ShapeBasedConstraint : public Constraint { bool m_only_positive; Utils::Vector3d m_local_force; double m_outer_normal_force; - System::System const &m_system; + std::weak_ptr m_system; - auto const &get_ia_param(int type) const { - return m_system.nonbonded_ias->get_ia_param(type, part_rep.type()); - } + IA_parameters const &get_ia_param(int type) const; }; } // namespace Constraints diff --git a/src/core/electrostatics/elc.cpp b/src/core/electrostatics/elc.cpp index 6c2c3f05313..8ef7d5366af 100644 --- a/src/core/electrostatics/elc.cpp +++ b/src/core/electrostatics/elc.cpp @@ -26,7 +26,6 @@ #include "electrostatics/elc.hpp" #include "electrostatics/coulomb.hpp" -#include "electrostatics/mmm-common.hpp" #include "electrostatics/p3m.hpp" #include "electrostatics/p3m_gpu.hpp" diff --git a/src/core/electrostatics/mmm-common.hpp b/src/core/electrostatics/mmm-common.hpp deleted file mode 100644 index c630406e546..00000000000 --- a/src/core/electrostatics/mmm-common.hpp +++ /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 - * Common parts of the MMM family of methods for the electrostatic - * interaction: MMM1D and ELC. This file contains the code for the - * polygamma expansions used for the near formulas of MMM1D. - * - * The expansion of the polygamma functions is fairly easy and follows - * directly from @cite abramowitz65a. For details, see @cite arnold02a. - */ - -#pragma once - -#include "mmm-modpsi.hpp" - -#include "specfunc.hpp" - -#include - -/** Modified polygamma for even order 2*n, n >= 0 */ -inline double mod_psi_even(int n, double x) { - return evaluateAsTaylorSeriesAt(modPsi[2 * n], x * x); -} - -/** Modified polygamma for odd order 2*n+1, n>= 0 */ -inline double mod_psi_odd(int n, double x) { - return x * evaluateAsTaylorSeriesAt(modPsi[2 * n + 1], x * x); -} diff --git a/src/core/electrostatics/mmm-modpsi.cpp b/src/core/electrostatics/mmm-modpsi.cpp index c19bc985cf4..972d359c1d3 100644 --- a/src/core/electrostatics/mmm-modpsi.cpp +++ b/src/core/electrostatics/mmm-modpsi.cpp @@ -21,15 +21,15 @@ #include "config/config.hpp" -#include "mmm-modpsi.hpp" +#ifdef ELECTROSTATICS + +#include "mmm1d.hpp" #include "specfunc.hpp" #include #include #include -std::vector> modPsi; - static void preparePolygammaEven(int n, double binom, std::vector &series) { /* (-0.5 n) psi^2n/2n! (-0.5 n) and psi^(2n+1)/(2n)! series expansions @@ -92,7 +92,7 @@ static void preparePolygammaOdd(int n, double binom, } } -void create_mod_psi_up_to(int new_n) { +void CoulombMMM1D::create_mod_psi_up_to(int new_n) { auto const old_n = static_cast(modPsi.size() >> 1); if (new_n > old_n) { modPsi.resize(2 * new_n); @@ -108,3 +108,5 @@ void create_mod_psi_up_to(int new_n) { } } } + +#endif // ELECTROSTATICS diff --git a/src/core/electrostatics/mmm-modpsi.hpp b/src/core/electrostatics/mmm-modpsi.hpp deleted file mode 100644 index a129acdf5a6..00000000000 --- a/src/core/electrostatics/mmm-modpsi.hpp +++ /dev/null @@ -1,38 +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 - * Common parts of the MMM family of methods for the electrostatic - * interaction: MMM1D and ELC. This file contains the code for the - * CPU and GPU implementations. - */ - -#pragma once - -#include - -/** Table of the Taylor expansions of the modified polygamma functions */ -extern std::vector> modPsi; - -/** Create both the even and odd polygamma functions up to order - * 2*new_n - */ -void create_mod_psi_up_to(int new_n); diff --git a/src/core/electrostatics/mmm1d.cpp b/src/core/electrostatics/mmm1d.cpp index 18a5ace1ebc..c92ce68b7ca 100644 --- a/src/core/electrostatics/mmm1d.cpp +++ b/src/core/electrostatics/mmm1d.cpp @@ -26,8 +26,6 @@ #include "electrostatics/mmm1d.hpp" #include "electrostatics/coulomb.hpp" -#include "electrostatics/mmm-common.hpp" -#include "electrostatics/mmm-modpsi.hpp" #include "BoxGeometry.hpp" #include "LocalBox.hpp" @@ -95,6 +93,16 @@ static auto determine_minrad(double maxPWerror, int P, return 0.5 * (rmin + rmax); } +/** Modified polygamma for even order 2*n, n >= 0 */ +static double mod_psi_even(auto const &modPsi, int n, double x) { + return evaluateAsTaylorSeriesAt(modPsi[2 * n], x * x); +} + +/** Modified polygamma for odd order 2*n+1, n>= 0 */ +static double mod_psi_odd(auto const &modPsi, int n, double x) { + return x * evaluateAsTaylorSeriesAt(modPsi[2 * n + 1], x * x); +} + void CoulombMMM1D::determine_bessel_radii() { auto const &box_geo = *get_system().box_geo; auto const &box_l = box_geo.length(); @@ -115,7 +123,8 @@ void CoulombMMM1D::prepare_polygamma_series() { create_mod_psi_up_to(n + 1); /* |uz*z| <= 0.5 */ - err = 2. * static_cast(n) * fabs(mod_psi_even(n, 0.5)) * rhomax2nm2; + err = 2. * static_cast(n) * fabs(mod_psi_even(modPsi, n, 0.5)) * + rhomax2nm2; rhomax2nm2 *= rhomax2; n++; } while (err > 0.1 * maxPWerror); @@ -183,12 +192,12 @@ Utils::Vector3d CoulombMMM1D::pair_force(double q1q2, Utils::Vector3d const &d, if (rxy2 <= far_switch_radius_sq) { /* polygamma summation */ auto sr = 0.; - auto sz = mod_psi_odd(0, z_d); + auto sz = mod_psi_odd(modPsi, 0, z_d); auto r2nm1 = 1.; for (int n = 1; n < n_modPsi; n++) { auto const deriv = static_cast(2 * n); - auto const mpe = mod_psi_even(n, z_d); - auto const mpo = mod_psi_odd(n, z_d); + auto const mpe = mod_psi_even(modPsi, n, z_d); + auto const mpo = mod_psi_odd(modPsi, n, z_d); auto const r2n = r2nm1 * rxy2_d; sz += r2n * mpo; @@ -281,7 +290,7 @@ double CoulombMMM1D::pair_energy(double const q1q2, Utils::Vector3d const &d, /* polygamma summation */ double r2n = 1.; for (int n = 0; n < n_modPsi; n++) { - auto const add = mod_psi_even(n, z_d) * r2n; + auto const add = mod_psi_even(modPsi, n, z_d) * r2n; energy -= add; if (fabs(add) < maxPWerror) diff --git a/src/core/electrostatics/mmm1d.hpp b/src/core/electrostatics/mmm1d.hpp index 24cada32aaf..28cc510d8c6 100644 --- a/src/core/electrostatics/mmm1d.hpp +++ b/src/core/electrostatics/mmm1d.hpp @@ -22,10 +22,15 @@ /** * @file * MMM1D algorithm for long-range Coulomb interactions on the CPU. + * * Implementation of the MMM1D method for the calculation of the electrostatic * interaction in one-dimensionally periodic systems. For details on the * method see MMM in general. The MMM1D method works only with the N-squared * cell system since neither the near nor far formula can be decomposed. + * + * MMM1D uses polygamma expansions for the near formula. + * The expansion of the polygamma functions is fairly easy and follows + * directly from @cite abramowitz65a. For details, see @cite arnold02a. */ #pragma once @@ -113,7 +118,11 @@ struct CoulombMMM1D : public Coulomb::Actor { static constexpr auto MAXIMAL_B_CUT = 30; /** @brief From which distance a certain Bessel cutoff is valid. */ std::array bessel_radii; + /** @brief Table of Taylor expansions of the modified polygamma functions. */ + std::vector> modPsi; + /** @brief Create even and odd polygamma functions up to order `2 * new_n`. */ + void create_mod_psi_up_to(int new_n); void determine_bessel_radii(); void prepare_polygamma_series(); void recalc_boxl_parameters(); diff --git a/src/core/electrostatics/specfunc.hpp b/src/core/electrostatics/specfunc.hpp index f0f2f6aefc6..225b3d33dc0 100644 --- a/src/core/electrostatics/specfunc.hpp +++ b/src/core/electrostatics/specfunc.hpp @@ -30,8 +30,7 @@ * formula, the Bessel functions are evaluated using several different * Chebychev expansions. Both achieve a precision of nearly machine precision, * which is no problem for the Hurwitz zeta function, which is only used when - * determining the coefficients for the modified polygamma functions (see @ref - * mmm-common.hpp). + * determining the coefficients for the modified polygamma functions. */ #pragma once diff --git a/src/core/energy.cpp b/src/core/energy.cpp index 6bbbf823253..c09c340b277 100644 --- a/src/core/energy.cpp +++ b/src/core/energy.cpp @@ -22,7 +22,7 @@ #include "BoxGeometry.hpp" #include "Observable_stat.hpp" #include "cell_system/CellStructure.hpp" -#include "constraints.hpp" +#include "constraints/Constraints.hpp" #include "energy_inline.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include "short_range_loop.hpp" @@ -39,7 +39,7 @@ namespace System { std::shared_ptr System::calculate_energy() { auto obs_energy_ptr = std::make_shared( - 1ul, static_cast(::bonded_ia_params.get_next_key()), + 1ul, static_cast(bonded_ias->get_next_key()), nonbonded_ias->get_max_seen_particle_type()); if (long_range_interactions_sanity_checks()) { @@ -65,7 +65,7 @@ std::shared_ptr System::calculate_energy() { short_range_loop( [this, coulomb_kernel_ptr = get_ptr(coulomb_kernel), &obs_energy]( Particle const &p1, int bond_id, std::span partners) { - auto const &iaparams = *bonded_ia_params.at(bond_id); + auto const &iaparams = *bonded_ias->at(bond_id); auto const result = calc_bonded_energy(iaparams, p1, partners, *box_geo, coulomb_kernel_ptr); if (result) { @@ -80,10 +80,10 @@ std::shared_ptr System::calculate_energy() { auto const &ia_params = nonbonded_ias->get_ia_param(p1.type(), p2.type()); add_non_bonded_pair_energy(p1, p2, d.vec21, sqrt(d.dist2), d.dist2, - ia_params, coulomb_kernel_ptr, + ia_params, *bonded_ias, coulomb_kernel_ptr, dipoles_kernel_ptr, obs_energy); }, - *cell_structure, maximal_cutoff(), maximal_cutoff_bonded()); + *cell_structure, maximal_cutoff(), bonded_ias->maximal_cutoff()); #ifdef ELECTROSTATICS /* calculate k-space part of electrostatic interaction. */ @@ -95,8 +95,7 @@ std::shared_ptr System::calculate_energy() { obs_energy.dipolar[1] = dipoles.calc_energy_long_range(local_parts); #endif - Constraints::constraints.add_energy(*box_geo, local_parts, get_sim_time(), - obs_energy); + constraints->add_energy(local_parts, get_sim_time(), obs_energy); #if defined(CUDA) and (defined(ELECTROSTATICS) or defined(DIPOLES)) auto const energy_host = gpu.copy_energy_to_host(); @@ -129,7 +128,7 @@ double System::particle_short_range_energy_contribution(int pid) { auto const &ia_params = nonbonded_ias->get_ia_param(p.type(), p1.type()); // Add energy for current particle pair to result ret += calc_non_bonded_pair_energy(p, p1, ia_params, vec, vec.norm(), - coulomb_kernel_ptr); + *bonded_ias, coulomb_kernel_ptr); }; cell_structure->run_on_particle_short_range_neighbors(*p, kernel); } diff --git a/src/core/energy_inline.hpp b/src/core/energy_inline.hpp index 5736118405d..8dc554338ae 100644 --- a/src/core/energy_inline.hpp +++ b/src/core/energy_inline.hpp @@ -69,12 +69,14 @@ * @param ia_params the interaction parameters between the two particles * @param d vector between p1 and p2. * @param dist distance between p1 and p2. + * @param bonded_ias bonded interaction kernels. * @param coulomb_kernel Coulomb energy kernel. * @return the short-range interaction energy between the two particles */ inline double calc_non_bonded_pair_energy( Particle const &p1, Particle const &p2, IA_parameters const &ia_params, Utils::Vector3d const &d, double const dist, + [[maybe_unused]] BondedInteractionsMap const &bonded_ias, Coulomb::ShortRangeEnergyKernel::kernel_type const *coulomb_kernel) { double ret = 0; @@ -83,6 +85,7 @@ inline double calc_non_bonded_pair_energy( /* Lennard-Jones */ ret += lj_pair_energy(ia_params, dist); #endif + #ifdef WCA /* WCA */ ret += wca_pair_energy(ia_params, dist); @@ -140,7 +143,8 @@ inline double calc_non_bonded_pair_energy( #ifdef THOLE /* Thole damping */ - ret += thole_pair_energy(p1, p2, ia_params, d, dist, coulomb_kernel); + ret += + thole_pair_energy(p1, p2, ia_params, d, dist, bonded_ias, coulomb_kernel); #endif #ifdef TABULATED @@ -163,12 +167,13 @@ inline double calc_non_bonded_pair_energy( /** Add non-bonded and short-range Coulomb energies between a pair of particles * to the energy observable. - * @param p1 particle 1. - * @param p2 particle 2. - * @param d vector between p1 and p2. - * @param dist distance between p1 and p2. - * @param dist2 distance squared between p1 and p2. + * @param[in] p1 particle 1. + * @param[in] p2 particle 2. + * @param[in] d vector between p1 and p2. + * @param[in] dist distance between p1 and p2. + * @param[in] dist2 distance squared between p1 and p2. * @param[in] ia_params non-bonded interaction kernels. + * @param[in] bonded_ias bonded interaction kernels. * @param[in] coulomb_kernel Coulomb energy kernel. * @param[in] dipoles_kernel Dipolar energy kernel. * @param[in,out] obs_energy energy observable. @@ -176,6 +181,7 @@ inline double calc_non_bonded_pair_energy( inline void add_non_bonded_pair_energy( Particle const &p1, Particle const &p2, Utils::Vector3d const &d, double const dist, double const dist2, IA_parameters const &ia_params, + [[maybe_unused]] BondedInteractionsMap const &bonded_ias, Coulomb::ShortRangeEnergyKernel::kernel_type const *coulomb_kernel, Dipoles::ShortRangeEnergyKernel::kernel_type const *dipoles_kernel, Observable_stat &obs_energy) { @@ -185,7 +191,7 @@ inline void add_non_bonded_pair_energy( #endif obs_energy.add_non_bonded_contribution( p1.type(), p2.type(), p1.mol_id(), p2.mol_id(), - calc_non_bonded_pair_energy(p1, p2, ia_params, d, dist, + calc_non_bonded_pair_energy(p1, p2, ia_params, d, dist, bonded_ias, coulomb_kernel)); #ifdef ELECTROSTATICS diff --git a/src/core/forces.cpp b/src/core/forces.cpp index bc317eb119b..d669b87deaf 100644 --- a/src/core/forces.cpp +++ b/src/core/forces.cpp @@ -33,12 +33,12 @@ #include "cells.hpp" #include "collision.hpp" #include "communication.hpp" -#include "constraints.hpp" +#include "constraints/Constraints.hpp" #include "electrostatics/icc.hpp" #include "electrostatics/p3m_gpu.hpp" #include "forces_inline.hpp" #include "galilei/ComFixed.hpp" -#include "immersed_boundaries.hpp" +#include "immersed_boundary/ImmersedBoundaries.hpp" #include "integrators/Propagation.hpp" #include "lb/particle_coupling.hpp" #include "magnetostatics/dipoles.hpp" @@ -134,7 +134,7 @@ void System::System::calculate_forces() { #endif // CUDA #ifdef COLLISION_DETECTION - prepare_local_collision_queue(); + collision_detection->clear_queue(); #endif bond_breakage->clear_queue(); auto particles = cell_structure->local_particles(); @@ -170,51 +170,49 @@ void System::System::calculate_forces() { #else auto const dipole_cutoff = INACTIVE_CUTOFF; #endif +#ifdef COLLISION_DETECTION + auto const collision_detection_cutoff = collision_detection->cutoff(); +#else + auto const collision_detection_cutoff = INACTIVE_CUTOFF; +#endif short_range_loop( - [coulomb_kernel_ptr = get_ptr(coulomb_kernel), + [coulomb_kernel_ptr = get_ptr(coulomb_kernel), &bonded_ias = *bonded_ias, &bond_breakage = *bond_breakage, &box_geo = *box_geo]( Particle &p1, int bond_id, std::span partners) { - return add_bonded_force(p1, bond_id, partners, bond_breakage, box_geo, - coulomb_kernel_ptr); + return add_bonded_force(p1, bond_id, partners, bonded_ias, + bond_breakage, box_geo, coulomb_kernel_ptr); }, [coulomb_kernel_ptr = get_ptr(coulomb_kernel), dipoles_kernel_ptr = get_ptr(dipoles_kernel), elc_kernel_ptr = get_ptr(elc_kernel), &nonbonded_ias = *nonbonded_ias, - &thermostat = *thermostat, + &thermostat = *thermostat, &bonded_ias = *bonded_ias, +#ifdef COLLISION_DETECTION + &collision_detection = *collision_detection, +#endif &box_geo = *box_geo](Particle &p1, Particle &p2, Distance const &d) { auto const &ia_params = nonbonded_ias.get_ia_param(p1.type(), p2.type()); - add_non_bonded_pair_force( - p1, p2, d.vec21, sqrt(d.dist2), d.dist2, ia_params, thermostat, - box_geo, coulomb_kernel_ptr, dipoles_kernel_ptr, elc_kernel_ptr); + add_non_bonded_pair_force(p1, p2, d.vec21, sqrt(d.dist2), d.dist2, + ia_params, thermostat, box_geo, bonded_ias, + coulomb_kernel_ptr, dipoles_kernel_ptr, + elc_kernel_ptr); #ifdef COLLISION_DETECTION - if (collision_params.mode != CollisionModeType::OFF) - detect_collision(p1, p2, d.dist2); + if (collision_detection.mode != CollisionModeType::OFF) { + collision_detection.detect_collision(p1, p2, d.dist2); + } #endif }, - *cell_structure, maximal_cutoff(), maximal_cutoff_bonded(), + *cell_structure, maximal_cutoff(), bonded_ias->maximal_cutoff(), VerletCriterion<>{*this, cell_structure->get_verlet_skin(), get_interaction_range(), coulomb_cutoff, dipole_cutoff, - collision_detection_cutoff()}); - - Constraints::constraints.add_forces(*box_geo, particles, get_sim_time()); - - for (int i = 0; i < max_oif_objects; i++) { - // There are two global quantities that need to be evaluated: - // object's surface and object's volume. - auto const area_volume = boost::mpi::all_reduce( - comm_cart, calc_oif_global(i, *box_geo, *cell_structure), std::plus()); - auto const oif_part_area = std::abs(area_volume[0]); - auto const oif_part_vol = std::abs(area_volume[1]); - if (oif_part_area < 1e-100 and oif_part_vol < 1e-100) { - break; - } - add_oif_global_forces(area_volume, i, *box_geo, *cell_structure); - } + collision_detection_cutoff}); + + constraints->add_forces(particles, get_sim_time()); + oif_global->calculate_forces(); // Must be done here. Forces need to be ghost-communicated - immersed_boundaries.volume_conservation(*cell_structure); + immersed_boundaries->volume_conservation(*cell_structure); if (thermostat->lb and (propagation->used_propagations & PropagationMode::TRANS_LB_MOMENTUM_EXCHANGE)) { diff --git a/src/core/forces_inline.hpp b/src/core/forces_inline.hpp index 5f73826b4a3..161340dfa36 100644 --- a/src/core/forces_inline.hpp +++ b/src/core/forces_inline.hpp @@ -142,20 +142,6 @@ inline ParticleForce calc_central_radial_force(IA_parameters const &ia_params, return pf; } -inline ParticleForce calc_central_radial_charge_force( - Particle const &p1, Particle const &p2, IA_parameters const &ia_params, - Utils::Vector3d const &d, double const dist, - Coulomb::ShortRangeForceKernel::kernel_type const *coulomb_kernel) { - - ParticleForce pf{}; -/* Thole damping */ -#ifdef THOLE - pf.f += thole_pair_force(p1, p2, ia_params, d, dist, coulomb_kernel); -#endif - - return pf; -} - inline ParticleForce calc_non_central_force(Particle const &p1, Particle const &p2, IA_parameters const &ia_params, @@ -194,6 +180,7 @@ inline ParticleForce calc_opposing_force(ParticleForce const &pf, * @param[in] ia_params non-bonded interaction kernels. * @param[in] thermostat thermostat. * @param[in] box_geo box geometry. + * @param[in] bonded_ias bonded interaction kernels. * @param[in] coulomb_kernel Coulomb force kernel. * @param[in] dipoles_kernel Dipolar force kernel. * @param[in] elc_kernel ELC force correction kernel. @@ -202,6 +189,7 @@ inline void add_non_bonded_pair_force( Particle &p1, Particle &p2, Utils::Vector3d const &d, double dist, double dist2, IA_parameters const &ia_params, Thermostat::Thermostat const &thermostat, BoxGeometry const &box_geo, + [[maybe_unused]] BondedInteractionsMap const &bonded_ias, Coulomb::ShortRangeForceKernel::kernel_type const *coulomb_kernel, Dipoles::ShortRangeForceKernel::kernel_type const *dipoles_kernel, Coulomb::ShortRangeForceCorrectionsKernel::kernel_type const *elc_kernel) { @@ -217,8 +205,10 @@ inline void add_non_bonded_pair_force( if (do_nonbonded(p1, p2)) { #endif pf += calc_central_radial_force(ia_params, d, dist); - pf += calc_central_radial_charge_force(p1, p2, ia_params, d, dist, - coulomb_kernel); +#ifdef THOLE + pf.f += thole_pair_force(p1, p2, ia_params, d, dist, bonded_ias, + coulomb_kernel); +#endif pf += calc_non_central_force(p1, p2, ia_params, d, dist); #ifdef EXCLUSIONS } @@ -455,6 +445,7 @@ inline bool add_bonded_four_body_force(Bonded_IA_Parameters const &iaparams, inline bool add_bonded_force(Particle &p1, int bond_id, std::span partners, + BondedInteractionsMap const &bonded_ia_params, BondBreakage::BondBreakage &bond_breakage, BoxGeometry const &box_geo, Coulomb::ShortRangeForceKernel::kernel_type const *kernel) { diff --git a/src/core/immersed_boundaries.cpp b/src/core/immersed_boundaries.cpp deleted file mode 100644 index a1cac55a679..00000000000 --- a/src/core/immersed_boundaries.cpp +++ /dev/null @@ -1,21 +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 . - */ -#include "immersed_boundaries.hpp" - -ImmersedBoundaries immersed_boundaries; diff --git a/src/core/immersed_boundaries.hpp b/src/core/immersed_boundaries.hpp deleted file mode 100644 index 441952b9d0a..00000000000 --- a/src/core/immersed_boundaries.hpp +++ /dev/null @@ -1,26 +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 IMMERSED_BOUNDARIES_HPP -#define IMMERSED_BOUNDARIES_HPP -#include "config/config.hpp" -#include "immersed_boundary/ImmersedBoundaries.hpp" - -extern ImmersedBoundaries immersed_boundaries; - -#endif diff --git a/src/core/immersed_boundary/CMakeLists.txt b/src/core/immersed_boundary/CMakeLists.txt index 52243163e7e..47b0c01b3db 100644 --- a/src/core/immersed_boundary/CMakeLists.txt +++ b/src/core/immersed_boundary/CMakeLists.txt @@ -21,6 +21,5 @@ target_sources( espresso_core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ibm_tribend.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ibm_triel.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ibm_volcons.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ibm_common.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImmersedBoundaries.cpp) diff --git a/src/core/immersed_boundary/ImmersedBoundaries.cpp b/src/core/immersed_boundary/ImmersedBoundaries.cpp index 0beaef4bb25..56b351c94c0 100644 --- a/src/core/immersed_boundary/ImmersedBoundaries.cpp +++ b/src/core/immersed_boundary/ImmersedBoundaries.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -47,13 +48,13 @@ void ImmersedBoundaries::volume_conservation(CellStructure &cs) { /** Initialize volume conservation */ void ImmersedBoundaries::init_volume_conservation(CellStructure &cs) { + auto const &bonded_ias = *get_system().bonded_ias; // Check since this function is called at the start of every integrate loop // Also check if volume has been set due to reading of a checkpoint if (not BoundariesFound) { - BoundariesFound = std::any_of( - bonded_ia_params.begin(), bonded_ia_params.end(), [](auto const &kv) { - return (boost::get(&(*kv.second)) != nullptr); - }); + BoundariesFound = std::ranges::any_of(bonded_ias, [](auto const &kv) { + return (boost::get(&(*kv.second)) != nullptr); + }); } if (!VolumeInitDone && BoundariesFound) { @@ -62,14 +63,14 @@ void ImmersedBoundaries::init_volume_conservation(CellStructure &cs) { // Loop through all bonded interactions and check if we need to set the // reference volume - for (auto &kv : bonded_ia_params) { - if (auto *v = boost::get(&(*kv.second))) { + for (auto &kv : bonded_ias) { + if (auto *v = boost::get(kv.second.get())) { // This check is important because InitVolumeConservation may be called // accidentally during the integration. Then we must not reset the // reference BoundariesFound = true; if (v->volRef == 0.) { - v->volRef = VolumesCurrent[static_cast(v->softID)]; + v->volRef = VolumesCurrent[v->softID]; } } } @@ -78,13 +79,15 @@ void ImmersedBoundaries::init_volume_conservation(CellStructure &cs) { } } -static const IBMVolCons *vol_cons_parameters(Particle const &p1) { - auto const it = boost::find_if(p1.bonds(), [](auto const &bond) -> bool { - return boost::get(bonded_ia_params.at(bond.bond_id()).get()); +static IBMVolCons const * +vol_cons_parameters(BondedInteractionsMap const &bonded_ias, + Particle const &p1) { + auto const it = boost::find_if(p1.bonds(), [&](auto const &bond) -> bool { + return boost::get(bonded_ias.at(bond.bond_id()).get()); }); return (it != p1.bonds().end()) - ? boost::get(bonded_ia_params.at(it->bond_id()).get()) + ? boost::get(bonded_ias.at(it->bond_id()).get()) : nullptr; } @@ -96,18 +99,19 @@ void ImmersedBoundaries::calc_volumes(CellStructure &cs) { if (!BoundariesFound) return; - auto const &box_geo = *System::get_system().box_geo; + auto const &box_geo = *get_system().box_geo; + auto const &bonded_ias = *get_system().bonded_ias; // Partial volumes for each soft particle, to be summed up std::vector tempVol(VolumesCurrent.size()); // Loop over all particles on local node - cs.bond_loop([&tempVol, &box_geo](Particle &p1, int bond_id, - std::span partners) { - auto const vol_cons_params = vol_cons_parameters(p1); + cs.bond_loop([&tempVol, &box_geo, &bonded_ias]( + Particle &p1, int bond_id, std::span partners) { + auto const vol_cons_params = vol_cons_parameters(bonded_ias, p1); if (vol_cons_params && - boost::get(bonded_ia_params.at(bond_id).get()) != nullptr) { + boost::get(bonded_ias.at(bond_id).get()) != nullptr) { // Our particle is the leading particle of a triel // Get second and third particle of the triangle Particle &p2 = *partners[0]; @@ -137,7 +141,7 @@ void ImmersedBoundaries::calc_volumes(CellStructure &cs) { const double v213 = x2[0] * x1[1] * x3[2]; const double v123 = x1[0] * x2[1] * x3[2]; - tempVol[static_cast(vol_cons_params->softID)] += + tempVol[vol_cons_params->softID] += 1.0 / 6.0 * (-v321 + v231 + v312 - v132 - v213 + v123); } return false; @@ -154,16 +158,17 @@ void ImmersedBoundaries::calc_volume_force(CellStructure &cs) { if (!BoundariesFound) return; - auto const &box_geo = *System::get_system().box_geo; + auto const &box_geo = *get_system().box_geo; + auto const &bonded_ias = *get_system().bonded_ias; - cs.bond_loop([this, &box_geo](Particle &p1, int bond_id, - std::span partners) { - if (boost::get(bonded_ia_params.at(bond_id).get()) != nullptr) { + cs.bond_loop([this, &box_geo, &bonded_ias](Particle &p1, int bond_id, + std::span partners) { + if (boost::get(bonded_ias.at(bond_id).get()) != nullptr) { // Check if particle has an IBM Triel bonded interaction and an // IBM VolCons bonded interaction. Basically this loops over all // triangles, not all particles. First round to check for volume // conservation. - auto const vol_cons_params = vol_cons_parameters(p1); + auto const vol_cons_params = vol_cons_parameters(bonded_ias, p1); if (not vol_cons_params) return false; @@ -204,3 +209,11 @@ void ImmersedBoundaries::calc_volume_force(CellStructure &cs) { return false; }); } + +void ImmersedBoundaries::register_softID(IBMVolCons &bond) { + auto const new_size = bond.softID + 1u; + if (new_size > VolumesCurrent.size()) { + VolumesCurrent.resize(new_size); + } + bond.set_volumes_view(VolumesCurrent); +} diff --git a/src/core/immersed_boundary/ImmersedBoundaries.hpp b/src/core/immersed_boundary/ImmersedBoundaries.hpp index bf3a2e02c2a..b06aca60f0d 100644 --- a/src/core/immersed_boundary/ImmersedBoundaries.hpp +++ b/src/core/immersed_boundary/ImmersedBoundaries.hpp @@ -22,30 +22,22 @@ #include "config/config.hpp" #include "cell_system/CellStructure.hpp" +#include "system/Leaf.hpp" #include #include #include -class ImmersedBoundaries { +struct IBMVolCons; + +class ImmersedBoundaries : public System::Leaf { public: ImmersedBoundaries() : VolumeInitDone(false), BoundariesFound(false) { VolumesCurrent.resize(10); } void init_volume_conservation(CellStructure &cs); void volume_conservation(CellStructure &cs); - void register_softID(int softID) { - assert(softID >= 0); - auto const new_size = static_cast(softID) + 1; - if (new_size > VolumesCurrent.size()) { - VolumesCurrent.resize(new_size); - } - } - double get_current_volume(int softID) const { - assert(softID >= 0); - assert(static_cast(softID) < VolumesCurrent.size()); - return VolumesCurrent[static_cast(softID)]; - } + void register_softID(IBMVolCons &bond); private: void calc_volumes(CellStructure &cs); diff --git a/src/core/immersed_boundary/ibm_common.cpp b/src/core/immersed_boundary/ibm_common.cpp index 34e1da702a2..d6e8ac878e0 100644 --- a/src/core/immersed_boundary/ibm_common.cpp +++ b/src/core/immersed_boundary/ibm_common.cpp @@ -31,8 +31,8 @@ #include #include -Utils::Vector3d get_ibm_particle_position(int pid) { - auto &cell_structure = *System::get_system().cell_structure; +Utils::Vector3d get_ibm_particle_position(CellStructure const &cell_structure, + int pid) { auto *p = cell_structure.get_local_particle(pid); std::optional opt_part{std::nullopt}; diff --git a/src/core/immersed_boundary/ibm_common.hpp b/src/core/immersed_boundary/ibm_common.hpp index 62262130dc8..851f19f97a7 100644 --- a/src/core/immersed_boundary/ibm_common.hpp +++ b/src/core/immersed_boundary/ibm_common.hpp @@ -17,17 +17,18 @@ * along with this program. If not, see . */ -#ifndef IMMERSED_BOUNDARY_IBM_COMMON_HPP -#define IMMERSED_BOUNDARY_IBM_COMMON_HPP +#pragma once + +#include "cell_system/CellStructure.hpp" #include /** * @brief Returns the position of a given particle. * + * @param cell_structure Cell structure to search. * @param pid Particle id. * @return position of the particle. */ -Utils::Vector3d get_ibm_particle_position(int pid); - -#endif +Utils::Vector3d get_ibm_particle_position(CellStructure const &cell_structure, + int pid); diff --git a/src/core/immersed_boundary/ibm_tribend.cpp b/src/core/immersed_boundary/ibm_tribend.cpp index 49585855af5..9815d2df963 100644 --- a/src/core/immersed_boundary/ibm_tribend.cpp +++ b/src/core/immersed_boundary/ibm_tribend.cpp @@ -20,8 +20,8 @@ #include "immersed_boundary/ibm_tribend.hpp" #include "BoxGeometry.hpp" +#include "cell_system/CellStructure.hpp" #include "ibm_common.hpp" -#include "system/System.hpp" #include @@ -93,20 +93,21 @@ IBMTribend::calc_forces(BoxGeometry const &box_geo, Particle const &p1, return std::make_tuple(force1, force2, force3, force4); } -IBMTribend::IBMTribend(const int ind1, const int ind2, const int ind3, - const int ind4, const double kb, const bool flat) { - - auto const &box_geo = *System::get_system().box_geo; - +void IBMTribend::initialize(BoxGeometry const &box_geo, + CellStructure const &cell_structure) { + if (is_initialized) { + return; + } // Compute theta0 if (flat) { theta0 = 0.; } else { // Get particles - auto const pos1 = get_ibm_particle_position(ind1); - auto const pos2 = get_ibm_particle_position(ind2); - auto const pos3 = get_ibm_particle_position(ind3); - auto const pos4 = get_ibm_particle_position(ind4); + auto const [ind1, ind2, ind3, ind4] = p_ids; + auto const pos1 = get_ibm_particle_position(cell_structure, ind1); + auto const pos2 = get_ibm_particle_position(cell_structure, ind2); + auto const pos3 = get_ibm_particle_position(cell_structure, ind3); + auto const pos4 = get_ibm_particle_position(cell_structure, ind4); // Get vectors of triangles auto const dx1 = box_geo.get_mi_vector(pos1, pos3); @@ -130,11 +131,5 @@ IBMTribend::IBMTribend(const int ind1, const int ind2, const int ind3, if (desc < 0.) theta0 = 2. * std::numbers::pi - theta0; } - - // NOTE: This is the bare bending modulus used by the program. - // If triangle pairs appear only once, the total bending force should get a - // factor 2. For the numerical model, a factor sqrt(3) should be added, see - // @cite gompper96a and @cite kruger12a. This is an approximation, - // it holds strictly only for a sphere - this->kb = kb; + is_initialized = true; } diff --git a/src/core/immersed_boundary/ibm_tribend.hpp b/src/core/immersed_boundary/ibm_tribend.hpp index 3ac3068ccfd..7a77b72712c 100644 --- a/src/core/immersed_boundary/ibm_tribend.hpp +++ b/src/core/immersed_boundary/ibm_tribend.hpp @@ -17,13 +17,13 @@ * along with this program. If not, see . */ -#ifndef IBM_TRIBEND_H -#define IBM_TRIBEND_H +#pragma once #include "config/config.hpp" #include "BoxGeometry.hpp" #include "Particle.hpp" +#include "cell_system/CellStructure.hpp" #include @@ -31,22 +31,39 @@ /** Parameters for IBM tribend */ struct IBMTribend { - /** Interaction data */ + /** + * @brief Bare bending modulus. + * + * If triangle pairs appear only once, the total bending force should get a + * factor 2. For the numerical model, a factor @f$ \sqrt(3) @f$ should be + * added, see @cite gompper96a and @cite kruger12a. This is an approximation, + * it holds strictly only for a sphere + */ double kb; /** Reference angle */ double theta0; + /** Particle ids */ + std::tuple p_ids; + + bool flat; + bool is_initialized; + double cutoff() const { return 0.; } - // Krüger always has three partners static constexpr int num = 3; /** Set the IBM Tribend parameters. * Also calculate and store the reference state. * See details in @cite gompper96a and @cite kruger12a. */ - IBMTribend(int ind1, int ind2, int ind3, int ind4, double kb, bool flat); + void initialize(BoxGeometry const &box_geo, + CellStructure const &cell_structure); + + IBMTribend(int ind1, int ind2, int ind3, int ind4, double kb, bool flat) + : kb{kb}, theta0{0.}, p_ids{ind1, ind2, ind3, ind4}, flat{flat}, + is_initialized{false} {} /** Calculate the forces * The equations can be found in Appendix C of @cite kruger12a. @@ -55,14 +72,4 @@ struct IBMTribend { std::tuple calc_forces(BoxGeometry const &box_geo, Particle const &p1, Particle const &p2, Particle const &p3, Particle const &p4) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & kb; - ar & theta0; - } }; - -#endif diff --git a/src/core/immersed_boundary/ibm_triel.cpp b/src/core/immersed_boundary/ibm_triel.cpp index 14f3fa9ccf2..df19cc1a7a4 100644 --- a/src/core/immersed_boundary/ibm_triel.cpp +++ b/src/core/immersed_boundary/ibm_triel.cpp @@ -20,8 +20,8 @@ #include "immersed_boundary/ibm_triel.hpp" #include "BoxGeometry.hpp" +#include "cell_system/CellStructure.hpp" #include "ibm_common.hpp" -#include "system/System.hpp" #include #include @@ -191,16 +191,16 @@ IBMTriel::calc_forces(Utils::Vector3d const &vec1, return {forces}; } -IBMTriel::IBMTriel(const int ind1, const int ind2, const int ind3, - const double maxDist, const tElasticLaw elasticLaw, - const double k1, const double k2) { - - auto const &box_geo = *System::get_system().box_geo; - +void IBMTriel::initialize(BoxGeometry const &box_geo, + CellStructure const &cell_structure) { + if (is_initialized) { + return; + } // collect particles from nodes - auto const pos1 = get_ibm_particle_position(ind1); - auto const pos2 = get_ibm_particle_position(ind2); - auto const pos3 = get_ibm_particle_position(ind3); + auto const [ind1, ind2, ind3] = p_ids; + auto const pos1 = get_ibm_particle_position(cell_structure, ind1); + auto const pos2 = get_ibm_particle_position(cell_structure, ind2); + auto const pos3 = get_ibm_particle_position(cell_structure, ind3); // Calculate equilibrium lengths and angle; Note the sequence of the points! // l0 = length between 1 and 3 @@ -224,9 +224,5 @@ IBMTriel::IBMTriel(const int ind1, const int ind2, const int ind3, b1 = (l0 * cosPhi0 - lp0) / area2; b2 = -(l0 * cosPhi0) / area2; area0 = 0.5 * area2; - this->maxDist = maxDist; - this->elasticLaw = elasticLaw; - // Always store two constants, for NeoHookean only k1 is used - this->k1 = k1; - this->k2 = k2; + is_initialized = true; } diff --git a/src/core/immersed_boundary/ibm_triel.hpp b/src/core/immersed_boundary/ibm_triel.hpp index b4cd1c0620b..98450544f29 100644 --- a/src/core/immersed_boundary/ibm_triel.hpp +++ b/src/core/immersed_boundary/ibm_triel.hpp @@ -17,11 +17,13 @@ * along with this program. If not, see . */ -#ifndef IBM_TRIEL_H -#define IBM_TRIEL_H +#pragma once #include "config/config.hpp" +#include "BoxGeometry.hpp" +#include "cell_system/CellStructure.hpp" + #include #include @@ -52,6 +54,10 @@ struct IBMTriel { double k1; double k2; + /** Particle ids */ + std::tuple p_ids; + bool is_initialized; + double cutoff() const { return maxDist; } static constexpr int num = 2; @@ -59,8 +65,14 @@ struct IBMTriel { /** Set the IBM Triel parameters. * Also calculate and store the reference state. */ + void initialize(BoxGeometry const &box_geo, + CellStructure const &cell_structure); + IBMTriel(int ind1, int ind2, int ind3, double maxDist, tElasticLaw elasticLaw, - double k1, double k2); + double k1, double k2) + : l0{0.}, lp0{0.}, sinPhi0{0.}, cosPhi0{0.}, area0{0.}, a1{0.}, a2{0.}, + b1{0.}, b2{0.}, maxDist{maxDist}, elasticLaw{elasticLaw}, k1{k1}, + k2{k2}, p_ids{ind1, ind2, ind3}, is_initialized{false} {} /** Calculate the forces. * The equations can be found in Appendix C of @cite kruger12a. @@ -68,25 +80,4 @@ struct IBMTriel { */ std::optional> calc_forces(Utils::Vector3d const &vec1, Utils::Vector3d const &vec2) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & l0; - ar & lp0; - ar & sinPhi0; - ar & cosPhi0; - ar & area0; - ar & a1; - ar & a2; - ar & b1; - ar & b2; - ar & maxDist; - ar & elasticLaw; - ar & k1; - ar & k2; - } }; - -#endif diff --git a/src/core/immersed_boundary/ibm_volcons.cpp b/src/core/immersed_boundary/ibm_volcons.cpp deleted file mode 100644 index 67f40b3a460..00000000000 --- a/src/core/immersed_boundary/ibm_volcons.cpp +++ /dev/null @@ -1,34 +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 . - */ - -#include "ibm_volcons.hpp" - -#include "immersed_boundaries.hpp" - -/** Set parameters of volume conservation */ -IBMVolCons::IBMVolCons(const int softID, const double kappaV) { - this->softID = softID; - this->kappaV = kappaV; - // NOTE: We cannot compute the reference volume here because not all - // interactions are setup and thus we do not know which triangles belong to - // this softID. Calculate it later in the init function of - // \ref ImmersedBoundaries::init_volume_conservation() - volRef = 0.; - immersed_boundaries.register_softID(softID); -} diff --git a/src/core/immersed_boundary/ibm_volcons.hpp b/src/core/immersed_boundary/ibm_volcons.hpp index afa992f1f36..041ffa683ef 100644 --- a/src/core/immersed_boundary/ibm_volcons.hpp +++ b/src/core/immersed_boundary/ibm_volcons.hpp @@ -17,15 +17,18 @@ * along with this program. If not, see . */ -#ifndef IBM_VOLCONS_H -#define IBM_VOLCONS_H +#pragma once -#include +#include + +#include +#include +#include /** Parameters for IBM volume conservation bond */ struct IBMVolCons { /** ID of the large soft particle to which this node belongs */ - int softID; + unsigned int softID; /** Reference volume */ double volRef; /** Spring constant for volume force */ @@ -35,16 +38,34 @@ struct IBMVolCons { static constexpr int num = 0; - IBMVolCons(int softID, double kappaV); + IBMVolCons(int softID, double kappaV) { + if (softID < 0) { + throw std::domain_error("IBMVolCons parameter 'softID' has to be >= 0"); + } + this->softID = static_cast(softID); + this->kappaV = kappaV; + // NOTE: We cannot compute the reference volume here because not all + // interactions are setup and thus we do not know which triangles belong to + // this softID. Calculate it later in the init function of + // \ref ImmersedBoundaries::init_volume_conservation() + volRef = 0.; + m_volumes = nullptr; + } -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & softID; - ar & volRef; - ar & kappaV; + double get_current_volume() const { + double volume = 0.; + if (m_volumes) { + assert(static_cast(softID) < m_volumes->size()); + volume = (*m_volumes)[softID]; + } + return volume; } -}; -#endif + void set_volumes_view(std::vector const &volumes) { + m_volumes = &volumes; + } + void unset_volumes_view() { m_volumes = nullptr; } + +private: + std::vector const *m_volumes; +}; diff --git a/src/core/integrate.cpp b/src/core/integrate.cpp index dddc9da6621..08b514efb12 100644 --- a/src/core/integrate.cpp +++ b/src/core/integrate.cpp @@ -37,7 +37,7 @@ #include "BoxGeometry.hpp" #include "ParticleRange.hpp" #include "PropagationMode.hpp" -#include "accumulators.hpp" +#include "accumulators/AutoUpdateAccumulators.hpp" #include "bond_breakage/bond_breakage.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" #include "cell_system/CellStructure.hpp" @@ -237,7 +237,7 @@ void System::System::integrator_sanity_checks() const { runtimeErrorMsg() << "The LB integrator requires the LB thermostat"; } } - if (::bonded_ia_params.get_n_thermalized_bonds() >= 1 and + if (bonded_ias->get_n_thermalized_bonds() >= 1 and (thermostat->thermalized_bond == nullptr or (thermo_switch & THERMO_BOND) == 0)) { runtimeErrorMsg() @@ -445,7 +445,7 @@ int System::System::integrate(int n_steps, int reuse_forces) { }; #endif #ifdef BOND_CONSTRAINT - auto const n_rigid_bonds = ::bonded_ia_params.get_n_rigid_bonds(); + auto const n_rigid_bonds = bonded_ias->get_n_rigid_bonds(); #endif // Prepare particle structure and run sanity checks of all active algorithms @@ -559,7 +559,7 @@ int System::System::integrate(int n_steps, int reuse_forces) { #ifdef BOND_CONSTRAINT // Correct particle positions that participate in a rigid/constrained bond if (n_rigid_bonds) { - correct_position_shake(*cell_structure, *box_geo); + correct_position_shake(*cell_structure, *box_geo, *bonded_ias); } #endif @@ -603,9 +603,8 @@ int System::System::integrate(int n_steps, int reuse_forces) { } } #ifdef BOND_CONSTRAINT - // SHAKE velocity updates if (n_rigid_bonds) { - correct_velocity_shake(*cell_structure, *box_geo); + correct_velocity_shake(*cell_structure, *box_geo, *bonded_ias); } #endif @@ -660,7 +659,7 @@ int System::System::integrate(int n_steps, int reuse_forces) { #endif #ifdef COLLISION_DETECTION - handle_collisions(*cell_structure); + collision_detection->handle_collisions(*cell_structure); #endif bond_breakage->process_queue(*this); } @@ -735,13 +734,11 @@ int System::System::integrate_with_signal_handler(int n_steps, int reuse_forces, return integrate(n_steps, reuse_forces); } - using Accumulators::auto_update; - using Accumulators::auto_update_next_update; - for (int i = 0; i < n_steps;) { /* Integrate to either the next accumulator update, or the * end, depending on what comes first. */ - auto const steps = std::min((n_steps - i), auto_update_next_update()); + auto const steps = + std::min((n_steps - i), auto_update_accumulators->next_update()); auto const local_retval = integrate(steps, reuse_forces); @@ -755,7 +752,7 @@ int System::System::integrate_with_signal_handler(int n_steps, int reuse_forces, reuse_forces = INTEG_REUSE_FORCES_ALWAYS; - auto_update(comm_cart, steps); + (*auto_update_accumulators)(comm_cart, steps); i += steps; } diff --git a/src/core/io/mpiio/mpiio.cpp b/src/core/io/mpiio/mpiio.cpp index 2dca7309762..8add15c9546 100644 --- a/src/core/io/mpiio/mpiio.cpp +++ b/src/core/io/mpiio/mpiio.cpp @@ -184,42 +184,44 @@ static unsigned long mpi_calculate_file_offset(unsigned long n_items) { * * @param fn The filename to write to * @param fields The dumped fields + * @param bonded_ias The list of bonds */ -static void dump_info(const std::string &fn, unsigned fields) { +static void dump_info(std::string const &fn, unsigned fields, + BondedInteractionsMap const &bonded_ias) { // MPI-IO requires consecutive bond ids - auto const nbonds = bonded_ia_params.size(); - assert(static_cast(bonded_ia_params.get_next_key()) == nbonds); + auto const nbonds = bonded_ias.size(); + assert(static_cast(bonded_ias.get_next_key()) == nbonds); FILE *f = fopen(fn.c_str(), "wb"); if (!f) { fatal_error("Could not open file", fn); } - static std::vector npartners; + std::vector npartners; bool success = (fwrite(&fields, sizeof(fields), 1u, f) == 1); - // Pack the necessary information of bonded_ia_params: - // The number of partners. This is needed to interpret the bond IntList. - if (nbonds > npartners.size()) - npartners.resize(nbonds); - - auto npartners_it = npartners.begin(); - for (int i = 0; i < bonded_ia_params.get_next_key(); ++i, ++npartners_it) { - *npartners_it = number_of_partners(*bonded_ia_params.at(i)); + // Pack the number of partners. This is needed to interpret the bond IntList. + npartners.reserve(nbonds); + for (int bond_id = 0; bond_id < bonded_ias.get_next_key(); ++bond_id) { + if (bonded_ias.contains(bond_id)) { + npartners.emplace_back(number_of_partners(*bonded_ias.at(bond_id))); + } } - success = success && (fwrite(&nbonds, sizeof(std::size_t), 1u, f) == 1); - success = - success && (fwrite(npartners.data(), sizeof(int), nbonds, f) == nbonds); + success &= fwrite(&nbonds, sizeof(std::size_t), 1u, f) == 1; + success &= fwrite(npartners.data(), sizeof(int), nbonds, f) == nbonds; fclose(f); static_cast(success or fatal_error("Could not write file", fn)); } -void mpi_mpiio_common_write(const std::string &prefix, unsigned fields, - const ParticleRange &particles) { +void mpi_mpiio_common_write(std::string const &prefix, unsigned fields, + BondedInteractionsMap const &bonded_ias, + ParticleRange const &particles, + write_buffers &buffers) { auto const nlocalpart = static_cast(particles.size()); auto const offset = mpi_calculate_file_offset(nlocalpart); - // Keep static buffers in order to avoid allocating them on every - // function call - static std::vector pos, vel; - static std::vector id, type; + // keep buffers in order to avoid allocating them on every function call + auto &pos = buffers.pos; + auto &vel = buffers.vel; + auto &id = buffers.id; + auto &type = buffers.type; // Realloc static buffers if necessary if (nlocalpart > id.size()) @@ -256,7 +258,7 @@ void mpi_mpiio_common_write(const std::string &prefix, unsigned fields, int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); if (rank == 0) - dump_info(prefix + ".head", fields); + dump_info(prefix + ".head", fields, bonded_ias); auto const pref_offset = static_cast(rank); mpiio_dump_array(prefix + ".pref", &offset, 1ul, pref_offset, MPI_UNSIGNED_LONG); @@ -401,8 +403,8 @@ read_prefs(const std::string &fn, int rank, int size, return {pref, nlocalpart}; } -void mpi_mpiio_common_read(const std::string &prefix, unsigned fields) { - auto &cell_structure = *System::get_system().cell_structure; +void mpi_mpiio_common_read(const std::string &prefix, unsigned fields, + CellStructure &cell_structure) { cell_structure.remove_all_particles(); int size, rank; diff --git a/src/core/io/mpiio/mpiio.hpp b/src/core/io/mpiio/mpiio.hpp index c65e6ebf320..f7ec17bed33 100644 --- a/src/core/io/mpiio/mpiio.hpp +++ b/src/core/io/mpiio/mpiio.hpp @@ -18,14 +18,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_IO_MPIIO_MPIIO_HPP -#define CORE_IO_MPIIO_MPIIO_HPP + +#pragma once /** @file * Implements binary output using MPI-IO. */ #include "ParticleRange.hpp" +#include "bonded_interactions/bonded_interaction_data.hpp" +#include "cell_system/CellStructure.hpp" #include @@ -44,6 +46,13 @@ enum MPIIOOutputFields : unsigned int { MPIIO_OUT_BND = 8u, }; +struct write_buffers { + std::vector pos; + std::vector vel; + std::vector id; + std::vector type; +}; + /** * @brief Parallel binary output using MPI-IO. * To be called by all MPI processes. Aborts ESPResSo if an error occurs. @@ -52,10 +61,14 @@ enum MPIIOOutputFields : unsigned int { * * @param prefix Filepath prefix. * @param fields Specifier for which fields to dump. + * @param bonded_ias Bonds to serialize. * @param particles Range of particles to serialize. + * @param buffers Write buffers. */ -void mpi_mpiio_common_write(const std::string &prefix, unsigned fields, - const ParticleRange &particles); +void mpi_mpiio_common_write(std::string const &prefix, unsigned fields, + BondedInteractionsMap const &bonded_ias, + ParticleRange const &particles, + write_buffers &buffers); /** * @brief Parallel binary input using MPI-IO. @@ -65,9 +78,9 @@ void mpi_mpiio_common_write(const std::string &prefix, unsigned fields, * * @param prefix Filepath prefix. * @param fields Specifier for which fields to read. + * @param cell_structure Bonds to serialize. */ -void mpi_mpiio_common_read(const std::string &prefix, unsigned fields); +void mpi_mpiio_common_read(std::string const &prefix, unsigned fields, + CellStructure &cell_structure); } // namespace Mpiio - -#endif diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index c2b0cba3c4a..7ce9fdceb22 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -24,6 +24,7 @@ #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include "electrostatics/coulomb.hpp" +#include "system/System.hpp" #include #include @@ -122,3 +123,7 @@ double InteractionsNonBonded::maximal_cutoff() const { } return max_cut_nonbonded; } + +void InteractionsNonBonded::on_non_bonded_ia_change() const { + get_system().on_non_bonded_ia_change(); +} diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp index 29474c56c26..05aa9784e9b 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.hpp @@ -27,6 +27,7 @@ #include "TabulatedPotential.hpp" #include "config/config.hpp" +#include "system/Leaf.hpp" #include #include @@ -34,7 +35,6 @@ #include #include #include -#include #include #include @@ -353,7 +353,7 @@ struct IA_parameters { #endif }; -class InteractionsNonBonded { +class InteractionsNonBonded : public System::Leaf { /** @brief List of pairwise interactions. */ std::vector> m_nonbonded_ia_params{}; /** @brief Maximal particle type seen so far. */ @@ -396,7 +396,7 @@ class InteractionsNonBonded { auto get_ia_param_key(int i, int j) const { assert(i >= 0 and i <= max_seen_particle_type); assert(j >= 0 and j <= max_seen_particle_type); - auto const key = static_cast( + auto const key = static_cast( Utils::lower_triangular(std::max(i, j), std::min(i, j))); assert(key < m_nonbonded_ia_params.size()); return key; @@ -436,4 +436,7 @@ class InteractionsNonBonded { /** @brief Get maximal cutoff. */ double maximal_cutoff() const; + + /** @brief Notify system that non-bonded interactions changed. */ + void on_non_bonded_ia_change() const; }; diff --git a/src/core/nonbonded_interactions/thole.hpp b/src/core/nonbonded_interactions/thole.hpp index dfc9998354b..d4b79a8ccba 100644 --- a/src/core/nonbonded_interactions/thole.hpp +++ b/src/core/nonbonded_interactions/thole.hpp @@ -18,8 +18,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_NB_IA_THOLE_HPP -#define CORE_NB_IA_THOLE_HPP + +#pragma once + /** \file * Routines to calculate the Thole damping potential between particle pairs. * See @cite thole81a. @@ -30,7 +31,6 @@ #ifdef THOLE #include "Particle.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" -#include "bonded_interactions/bonded_interaction_utils.hpp" #include "electrostatics/coulomb.hpp" #include "electrostatics/coulomb_inline.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" @@ -43,13 +43,13 @@ inline Utils::Vector3d thole_pair_force(Particle const &p1, Particle const &p2, IA_parameters const &ia_params, Utils::Vector3d const &d, - double dist, + double dist, BondedInteractionsMap const &bonded_ias, Coulomb::ShortRangeForceKernel::kernel_type const *kernel) { auto const thole_q1q2 = ia_params.thole.q1q2; auto const thole_s = ia_params.thole.scaling_coeff; if (thole_s != 0. and thole_q1q2 != 0. and kernel != nullptr and - !(pair_bond_enum_exists_between(p1, p2))) { + not bonded_ias.pair_bond_exists_between(p1, p2)) { // Calc damping function (see @cite thole81a) // S(r) = 1.0 - (1.0 + thole_s*r/2.0) * exp(-thole_s*r); // Calc F = - d/dr ( S(r)*q1q2/r) = @@ -66,7 +66,7 @@ thole_pair_force(Particle const &p1, Particle const &p2, inline double thole_pair_energy(Particle const &p1, Particle const &p2, IA_parameters const &ia_params, Utils::Vector3d const &d, - double dist, + double dist, BondedInteractionsMap const &bonded_ias, Coulomb::ShortRangeEnergyKernel::kernel_type const *kernel) { auto const thole_s = ia_params.thole.scaling_coeff; @@ -74,7 +74,7 @@ thole_pair_energy(Particle const &p1, Particle const &p2, if (thole_s != 0. and thole_q1q2 != 0. and kernel != nullptr and dist < Coulomb::get_coulomb().cutoff() and - !(pair_bond_enum_exists_between(p1, p2))) { + not bonded_ias.pair_bond_exists_between(p1, p2)) { auto const sd = thole_s * dist; auto const S_r = 1.0 - (1.0 + sd / 2.0) * exp(-sd); // Subtract p3m short-range energy and add thole energy @@ -83,4 +83,3 @@ thole_pair_energy(Particle const &p1, Particle const &p2, return 0.0; } #endif // THOLE -#endif diff --git a/src/core/object-in-fluid/oif_global_forces.cpp b/src/core/object-in-fluid/oif_global_forces.cpp index 30fee91916c..6ac1465b622 100644 --- a/src/core/object-in-fluid/oif_global_forces.cpp +++ b/src/core/object-in-fluid/oif_global_forces.cpp @@ -21,65 +21,68 @@ #include "BoxGeometry.hpp" #include "Particle.hpp" -#include "cell_system/CellStructure.hpp" - #include "bonded_interactions/bonded_interaction_data.hpp" +#include "cell_system/CellStructure.hpp" +#include "communication.hpp" +#include "oif_global_forces_params.hpp" +#include "system/System.hpp" #include #include +#include +#include + +#include +#include +#include #include -int max_oif_objects = 0; +/** Calculate the mesh volume and area. */ +static auto calc_oif_mesh(int molType, BoxGeometry const &box_geo, + CellStructure &cs, + BondedInteractionsMap const &bonded_ias) { -Utils::Vector2d calc_oif_global(int molType, BoxGeometry const &box_geo, - CellStructure &cs) { - // first-fold-then-the-same approach - double partArea = 0.0; - // z volume - double VOL_partVol = 0.; + double area = 0.; + double volume = 0.; - cs.bond_loop([&partArea, &VOL_partVol, &box_geo, molType]( + cs.bond_loop([&area, &volume, &box_geo, &bonded_ias, molType]( Particle &p1, int bond_id, std::span partners) { if (p1.mol_id() != molType) return false; - if (boost::get(bonded_ia_params.at(bond_id).get()) != - nullptr) { - // remaining neighbors fetched + if (boost::get(bonded_ias.at(bond_id).get())) { auto const p11 = box_geo.unfolded_position(p1.pos(), p1.image_box()); auto const p22 = p11 + box_geo.get_mi_vector(partners[0]->pos(), p11); auto const p33 = p11 + box_geo.get_mi_vector(partners[1]->pos(), p11); - // unfolded positions correct auto const VOL_A = Utils::area_triangle(p11, p22, p33); - partArea += VOL_A; + area += VOL_A; auto const VOL_norm = Utils::get_n_triangle(p11, p22, p33); auto const VOL_dn = VOL_norm.norm(); - auto const VOL_hz = 1.0 / 3.0 * (p11[2] + p22[2] + p33[2]); - VOL_partVol -= VOL_A * VOL_norm[2] / VOL_dn * VOL_hz; + auto const VOL_hz = (1.0 / 3.0) * (p11[2] + p22[2] + p33[2]); + volume -= VOL_A * VOL_norm[2] / VOL_dn * VOL_hz; } return false; }); - return {{partArea, VOL_partVol}}; + return Utils::Vector2d{{area, volume}}; } -void add_oif_global_forces(Utils::Vector2d const &area_volume, int molType, - BoxGeometry const &box_geo, CellStructure &cs) { - // first-fold-then-the-same approach - double area = area_volume[0]; - double VOL_volume = area_volume[1]; +/** Distribute the OIF global forces to all particles in the mesh. */ +static void add_oif_global_forces(double area, double volume, int molType, + BoxGeometry const &box_geo, CellStructure &cs, + BondedInteractionsMap const &bonded_ias) { - cs.bond_loop([&box_geo, area, VOL_volume, molType]( + cs.bond_loop([&box_geo, &bonded_ias, area, volume, molType]( Particle &p1, int bond_id, std::span partners) { if (p1.mol_id() != molType) return false; - if (auto const *iaparams = boost::get( - bonded_ia_params.at(bond_id).get())) { + auto const *bond_ptr = bonded_ias.at(bond_id).get(); + if (auto const *bond = boost::get(bond_ptr)) { auto const p11 = box_geo.unfolded_position(p1.pos(), p1.image_box()); auto const p22 = p11 + box_geo.get_mi_vector(partners[0]->pos(), p11); auto const p33 = p11 + box_geo.get_mi_vector(partners[1]->pos(), p11); @@ -88,14 +91,13 @@ void add_oif_global_forces(Utils::Vector2d const &area_volume, int molType, // starting code from volume force auto const VOL_norm = Utils::get_n_triangle(p11, p22, p33).normalize(); auto const VOL_A = Utils::area_triangle(p11, p22, p33); - auto const VOL_vv = (VOL_volume - iaparams->V0) / iaparams->V0; + auto const VOL_vv = (volume - bond->V0) / bond->V0; - auto const VOL_force = - (1.0 / 3.0) * iaparams->kv * VOL_vv * VOL_A * VOL_norm; + auto const VOL_force = (1. / 3.) * bond->kv * VOL_vv * VOL_A * VOL_norm; auto const h = (1. / 3.) * (p11 + p22 + p33); - auto const deltaA = (area - iaparams->A0_g) / iaparams->A0_g; + auto const deltaA = (area - bond->A0_g) / bond->A0_g; auto const m1 = h - p11; auto const m2 = h - p22; @@ -105,7 +107,7 @@ void add_oif_global_forces(Utils::Vector2d const &area_volume, int molType, auto const m2_length = m2.norm(); auto const m3_length = m3.norm(); - auto const fac = iaparams->ka_g * VOL_A * deltaA / + auto const fac = bond->ka_g * VOL_A * deltaA / (m1_length * m1_length + m2_length * m2_length + m3_length * m3_length); @@ -117,3 +119,22 @@ void add_oif_global_forces(Utils::Vector2d const &area_volume, int molType, return false; }); } + +void OifGlobal::run_force_kernel() { + auto &system = get_system(); + auto &box_geo = *system.box_geo; + auto &bonded_ias = *system.bonded_ias; + auto &cell_structure = *system.cell_structure; + for (int i = 0; i < max_oif_objects; ++i) { + // There are two global quantities that need to be evaluated: + // object's surface and object's volume. + auto const local = calc_oif_mesh(i, box_geo, cell_structure, bonded_ias); + auto const global = boost::mpi::all_reduce(comm_cart, local, std::plus()); + auto const area = std::abs(global[0]); + auto const volume = std::abs(global[1]); + if (area < 1e-100 and volume < 1e-100) { + break; + } + add_oif_global_forces(area, volume, i, box_geo, cell_structure, bonded_ias); + } +} diff --git a/src/core/object-in-fluid/oif_global_forces.hpp b/src/core/object-in-fluid/oif_global_forces.hpp index 908a349e136..e74d045198f 100644 --- a/src/core/object-in-fluid/oif_global_forces.hpp +++ b/src/core/object-in-fluid/oif_global_forces.hpp @@ -16,33 +16,26 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_OBJECT_IN_FLUID_OIF_GLOBAL_FORCES_HPP -#define CORE_OBJECT_IN_FLUID_OIF_GLOBAL_FORCES_HPP + +#pragma once + /** \file - * Routines to calculate the OIF global forces energy or/and and force + * Routines to calculate the OIF global forces * for a particle triple (triangle from mesh). See @cite dupin07a. */ -#include "BoxGeometry.hpp" -#include "cell_system/CellStructure.hpp" -#include "oif_global_forces_params.hpp" - -#include - -/** Calculate the OIF global force. - * Called in force_calc() from within forces.cpp - * - calculates the global area and global volume for a cell before the forces - * are handled - * - MPI synchronization with all reduce - * - !!! loop over particles from regular_decomposition !!! - */ -Utils::Vector2d calc_oif_global(int molType, BoxGeometry const &box_geo, - CellStructure &cs); +#include "system/Leaf.hpp" -/** Distribute the OIF global forces to all particles in the mesh. */ -void add_oif_global_forces(Utils::Vector2d const &area_volume, int molType, - BoxGeometry const &box_geo, CellStructure &cs); +class OifGlobal : public System::Leaf { +public: + int max_oif_objects = 0; -extern int max_oif_objects; + void calculate_forces() { + if (max_oif_objects > 0) { + run_force_kernel(); + } + } -#endif +private: + void run_force_kernel(); +}; diff --git a/src/core/object-in-fluid/oif_global_forces_params.hpp b/src/core/object-in-fluid/oif_global_forces_params.hpp index 8906b759a9b..3f2e1d578d8 100644 --- a/src/core/object-in-fluid/oif_global_forces_params.hpp +++ b/src/core/object-in-fluid/oif_global_forces_params.hpp @@ -16,10 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef OIF_GLOBAL_FORCES_PARAMS_HPP -#define OIF_GLOBAL_FORCES_PARAMS_HPP -#include +#pragma once /** Parameters for OIF global forces * @@ -46,16 +44,4 @@ struct OifGlobalForcesBond { this->V0 = V0; this->kv = kv; } - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & A0_g; - ar & ka_g; - ar & V0; - ar & kv; - } }; - -#endif diff --git a/src/core/object-in-fluid/oif_local_forces.hpp b/src/core/object-in-fluid/oif_local_forces.hpp index 3f741d1d75e..763a00f44bd 100644 --- a/src/core/object-in-fluid/oif_local_forces.hpp +++ b/src/core/object-in-fluid/oif_local_forces.hpp @@ -16,8 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_OBJECT_IN_FLUID_OIF_LOCAL_FORCES_HPP -#define CORE_OBJECT_IN_FLUID_OIF_LOCAL_FORCES_HPP + +#pragma once /** \file * Routines to calculate the OIF local forces for a particle quadruple @@ -80,21 +80,6 @@ struct OifLocalForcesBond { std::tuple calc_forces(BoxGeometry const &box_geo, Particle const &p2, Particle const &p1, Particle const &p3, Particle const &p4) const; - -private: - friend boost::serialization::access; - template - void serialize(Archive &ar, long int /* version */) { - ar & r0; - ar & ks; - ar & kslin; - ar & phi0; - ar & kb; - ar & A01; - ar & A02; - ar & kal; - ar & kvisc; - } }; /** Compute the OIF local forces. @@ -252,5 +237,3 @@ OifLocalForcesBond::calc_forces(BoxGeometry const &box_geo, Particle const &p2, } return std::make_tuple(force2, force1, force3, force4); } - -#endif diff --git a/src/core/pair_criteria/EnergyCriterion.hpp b/src/core/pair_criteria/EnergyCriterion.hpp index e81a59dd66b..3418b2bdbeb 100644 --- a/src/core/pair_criteria/EnergyCriterion.hpp +++ b/src/core/pair_criteria/EnergyCriterion.hpp @@ -16,8 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef ESPRESSO_SRC_CORE_PAIR_CRITERIA_ENERGY_CRITERION_HPP -#define ESPRESSO_SRC_CORE_PAIR_CRITERIA_ENERGY_CRITERION_HPP + +#pragma once #include "pair_criteria/PairCriterion.hpp" @@ -44,7 +44,8 @@ class EnergyCriterion : public PairCriterion { auto const coulomb_kernel = m_system.coulomb.pair_energy_kernel(); auto const energy = calc_non_bonded_pair_energy( - p1, p2, ia_params, d, d.norm(), get_ptr(coulomb_kernel)); + p1, p2, ia_params, d, d.norm(), *m_system.bonded_ias, + get_ptr(coulomb_kernel)); return energy >= m_cut_off; } @@ -56,5 +57,3 @@ class EnergyCriterion : public PairCriterion { System::System const &m_system; }; } // namespace PairCriteria - -#endif diff --git a/src/core/particle_node.cpp b/src/core/particle_node.cpp index 8bd95199b4d..074188e826a 100644 --- a/src/core/particle_node.cpp +++ b/src/core/particle_node.cpp @@ -574,7 +574,7 @@ int number_of_particles_with_type(int type) { bool particle_exists(int p_id) { if (particle_node.empty()) build_particle_node(); - return particle_node.count(p_id); + return particle_node.contains(p_id); } std::vector get_particle_ids() { diff --git a/src/core/polymer.cpp b/src/core/polymer.cpp index 02d3b3b7c25..5fcb32cc18e 100644 --- a/src/core/polymer.cpp +++ b/src/core/polymer.cpp @@ -32,7 +32,6 @@ #include "BoxGeometry.hpp" #include "cell_system/CellStructure.hpp" #include "communication.hpp" -#include "constraints.hpp" #include "constraints/Constraints.hpp" #include "constraints/ShapeBasedConstraint.hpp" #include "random.hpp" @@ -74,9 +73,9 @@ template static Utils::Vector3d random_unit_vector(RNG &rng) { /** Determines whether a given position @p pos is valid, i.e., it doesn't * collide with existing or buffered particles, nor with existing constraints * (if @c respect_constraints). + * @param system the system containing the particles * @param pos the trial position in question * @param positions buffered positions to respect - * @param cell_structure existing particles to respect * @param box_geo Box geometry * @param min_distance threshold for the minimum distance between * trial position and buffered/existing particles @@ -84,9 +83,8 @@ template static Utils::Vector3d random_unit_vector(RNG &rng) { * @return true if valid position, false if not. */ static bool -is_valid_position(Utils::Vector3d const &pos, +is_valid_position(System::System const &system, Utils::Vector3d const &pos, std::vector> const &positions, - CellStructure const &cell_structure, BoxGeometry const &box_geo, double const min_distance, int const respect_constraints) { @@ -100,7 +98,7 @@ is_valid_position(Utils::Vector3d const &pos, if (respect_constraints) { Utils::Vector3d const folded_pos = box_geo.folded_position(pos); - for (auto &c : Constraints::constraints) { + for (auto &c : *system.constraints) { auto cs = std::dynamic_pointer_cast(c); if (cs) { @@ -119,7 +117,7 @@ is_valid_position(Utils::Vector3d const &pos, if (min_distance > 0.) { // check for collision with existing particles auto local_mindist_sq = std::numeric_limits::infinity(); - for (auto const &p : cell_structure.local_particles()) { + for (auto const &p : system.cell_structure->local_particles()) { auto const d = box_geo.get_mi_vector(pos, p.pos()); local_mindist_sq = std::min(local_mindist_sq, d.norm2()); } @@ -160,8 +158,8 @@ draw_polymer_positions(System::System const &system, int const n_polymers, } auto is_valid_pos = [&](Utils::Vector3d const &pos) { - return is_valid_position(pos, positions, *system.cell_structure, box_geo, - min_distance, respect_constraints); + return is_valid_position(system, pos, positions, box_geo, min_distance, + respect_constraints); }; for (std::size_t p = 0; p < start_positions.size(); p++) { diff --git a/src/core/pressure.cpp b/src/core/pressure.cpp index a0e14ca5b91..164025915c2 100644 --- a/src/core/pressure.cpp +++ b/src/core/pressure.cpp @@ -46,7 +46,7 @@ namespace System { std::shared_ptr System::calculate_pressure() { auto obs_pressure_ptr = std::make_shared( - 9ul, static_cast(::bonded_ia_params.get_next_key()), + 9ul, static_cast(bonded_ias->get_next_key()), nonbonded_ias->get_max_seen_particle_type()); if (long_range_interactions_sanity_checks()) { @@ -71,7 +71,7 @@ std::shared_ptr System::calculate_pressure() { [this, coulomb_force_kernel_ptr = get_ptr(coulomb_force_kernel), &obs_pressure](Particle const &p1, int bond_id, std::span partners) { - auto const &iaparams = *bonded_ia_params.at(bond_id); + auto const &iaparams = *bonded_ias->at(bond_id); auto const result = calc_bonded_pressure_tensor( iaparams, p1, partners, *box_geo, coulomb_force_kernel_ptr); if (result) { @@ -93,10 +93,10 @@ std::shared_ptr System::calculate_pressure() { auto const &ia_params = nonbonded_ias->get_ia_param(p1.type(), p2.type()); add_non_bonded_pair_virials(p1, p2, d.vec21, sqrt(d.dist2), ia_params, - coulomb_force_kernel_ptr, + *bonded_ias, coulomb_force_kernel_ptr, coulomb_pressure_kernel_ptr, obs_pressure); }, - *cell_structure, maximal_cutoff(), maximal_cutoff_bonded()); + *cell_structure, maximal_cutoff(), bonded_ias->maximal_cutoff()); #ifdef ELECTROSTATICS /* calculate k-space part of electrostatic interaction. */ diff --git a/src/core/pressure_inline.hpp b/src/core/pressure_inline.hpp index 91ff4a26d07..fdbc1579288 100644 --- a/src/core/pressure_inline.hpp +++ b/src/core/pressure_inline.hpp @@ -39,6 +39,7 @@ #include +#include #include #include #include @@ -50,6 +51,7 @@ * @param d vector between p1 and p2. * @param dist distance between p1 and p2. * @param ia_params non-bonded interaction kernels. + * @param bonded_ias bonded interaction kernels. * @param kernel_forces Coulomb force kernel. * @param kernel_pressure Coulomb pressure kernel. * @param[in,out] obs_pressure pressure observable. @@ -57,6 +59,7 @@ inline void add_non_bonded_pair_virials( Particle const &p1, Particle const &p2, Utils::Vector3d const &d, double dist, IA_parameters const &ia_params, + [[maybe_unused]] BondedInteractionsMap const &bonded_ias, Coulomb::ShortRangeForceKernel::kernel_type const *kernel_forces, Coulomb::ShortRangePressureKernel::kernel_type const *kernel_pressure, Observable_stat &obs_pressure) { @@ -65,9 +68,10 @@ inline void add_non_bonded_pair_virials( #endif { auto const force = calc_central_radial_force(ia_params, d, dist).f + - calc_central_radial_charge_force(p1, p2, ia_params, d, - dist, kernel_forces) - .f + +#ifdef THOLE + thole_pair_force(p1, p2, ia_params, d, dist, bonded_ias, + kernel_forces) + +#endif calc_non_central_force(p1, p2, ia_params, d, dist).f; auto const stress = Utils::tensor_product(d, force); obs_pressure.add_non_bonded_contribution(p1.type(), p2.type(), p1.mol_id(), diff --git a/src/core/rattle.cpp b/src/core/rattle.cpp index 47a1dfd19d7..1891300003d 100644 --- a/src/core/rattle.cpp +++ b/src/core/rattle.cpp @@ -104,17 +104,19 @@ static bool calculate_positional_correction(RigidBond const &ia_params, * * @param cs cell structure * @param box_geo Box geometry + * @param bonded_ias Bonded interactions * @param kernel kernel function * @return True if correction is necessary */ template static bool compute_correction_vector(CellStructure &cs, BoxGeometry const &box_geo, + BondedInteractionsMap const &bonded_ias, Kernel kernel) { bool correction = false; - cs.bond_loop([&correction, &kernel, &box_geo]( + cs.bond_loop([&correction, &kernel, &box_geo, &bonded_ias]( Particle &p1, int bond_id, std::span partners) { - auto const &iaparams = *bonded_ia_params.at(bond_id); + auto const &iaparams = *bonded_ias.at(bond_id); if (auto const *bond = boost::get(&iaparams)) { auto const corrected = kernel(*bond, box_geo, p1, *partners[0]); @@ -141,7 +143,8 @@ static void apply_positional_correction(const ParticleRange &particles) { }); } -void correct_position_shake(CellStructure &cs, BoxGeometry const &box_geo) { +void correct_position_shake(CellStructure &cs, BoxGeometry const &box_geo, + BondedInteractionsMap const &bonded_ias) { unsigned const flag = Cells::DATA_PART_POSITION | Cells::DATA_PART_PROPERTIES; cs.update_ghosts_and_resort_particle(flag); @@ -151,8 +154,8 @@ void correct_position_shake(CellStructure &cs, BoxGeometry const &box_geo) { int cnt; for (cnt = 0; cnt < SHAKE_MAX_ITERATIONS; ++cnt) { init_correction_vector(particles, ghost_particles); - bool const repeat_ = - compute_correction_vector(cs, box_geo, calculate_positional_correction); + bool const repeat_ = compute_correction_vector( + cs, box_geo, bonded_ias, calculate_positional_correction); bool const repeat = boost::mpi::all_reduce(comm_cart, repeat_, std::logical_or()); @@ -218,7 +221,8 @@ static void apply_velocity_correction(ParticleRange const &particles) { [](Particle &p) { p.v() += p.rattle_params().correction; }); } -void correct_velocity_shake(CellStructure &cs, BoxGeometry const &box_geo) { +void correct_velocity_shake(CellStructure &cs, BoxGeometry const &box_geo, + BondedInteractionsMap const &bonded_ias) { cs.ghosts_update(Cells::DATA_PART_POSITION | Cells::DATA_PART_MOMENTUM); auto particles = cs.local_particles(); @@ -227,8 +231,8 @@ void correct_velocity_shake(CellStructure &cs, BoxGeometry const &box_geo) { int cnt; for (cnt = 0; cnt < SHAKE_MAX_ITERATIONS; ++cnt) { init_correction_vector(particles, ghost_particles); - bool const repeat_ = - compute_correction_vector(cs, box_geo, calculate_velocity_correction); + bool const repeat_ = compute_correction_vector( + cs, box_geo, bonded_ias, calculate_velocity_correction); bool const repeat = boost::mpi::all_reduce(comm_cart, repeat_, std::logical_or()); diff --git a/src/core/rattle.hpp b/src/core/rattle.hpp index 5502bd62558..156225e58e1 100644 --- a/src/core/rattle.hpp +++ b/src/core/rattle.hpp @@ -18,8 +18,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef RATTLE_H -#define RATTLE_H + +#pragma once /** \file * RATTLE algorithm (@cite andersen83a). @@ -32,25 +32,27 @@ #ifdef BOND_CONSTRAINT #include "BoxGeometry.hpp" +#include "bonded_interactions/bonded_interaction_data.hpp" #include "cell_system/CellStructure.hpp" -/** Transfer the current particle positions from @ref Particle::pos - * "Particle::pos" to @ref Particle::pos_last_time_step - * "Particle::pos_last_time_step" +/** + * @brief Transfer the current particle positions from @ref Particle::pos + * to @ref Particle::pos_last_time_step. */ -void save_old_position(const ParticleRange &particles, - const ParticleRange &ghost_particles); +void save_old_position(ParticleRange const &particles, + ParticleRange const &ghost_particles); /** * @brief Propagate velocity and position while using SHAKE algorithm for bond * constraint. */ -void correct_position_shake(CellStructure &cs, BoxGeometry const &box_geo); +void correct_position_shake(CellStructure &cs, BoxGeometry const &box_geo, + BondedInteractionsMap const &bonded_ias); /** * @brief Correction of current velocities using RATTLE algorithm. */ -void correct_velocity_shake(CellStructure &cs, BoxGeometry const &box_geo); +void correct_velocity_shake(CellStructure &cs, BoxGeometry const &box_geo, + BondedInteractionsMap const &bonded_ias); #endif -#endif diff --git a/src/core/system/System.cpp b/src/core/system/System.cpp index b44db5df266..676b8241554 100644 --- a/src/core/system/System.cpp +++ b/src/core/system/System.cpp @@ -25,6 +25,7 @@ #include "BoxGeometry.hpp" #include "LocalBox.hpp" #include "PropagationMode.hpp" +#include "accumulators/AutoUpdateAccumulators.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" #include "bonded_interactions/thermalized_bond.hpp" #include "cell_system/CellStructure.hpp" @@ -32,10 +33,8 @@ #include "cell_system/HybridDecomposition.hpp" #include "collision.hpp" #include "communication.hpp" -#include "constraints.hpp" #include "electrostatics/icc.hpp" #include "errorhandling.hpp" -#include "immersed_boundaries.hpp" #include "npt.hpp" #include "particle_node.hpp" #include "thermostat.hpp" @@ -68,12 +67,21 @@ System::System(Private) { local_geo = std::make_shared(); cell_structure = std::make_shared(*box_geo); propagation = std::make_shared(); + bonded_ias = std::make_shared(); thermostat = std::make_shared(); nonbonded_ias = std::make_shared(); comfixed = std::make_shared(); galilei = std::make_shared(); + oif_global = std::make_shared(); + immersed_boundaries = std::make_shared(); +#ifdef COLLISION_DETECTION + collision_detection = std::make_shared(); +#endif bond_breakage = std::make_shared(); lees_edwards = std::make_shared(); + auto_update_accumulators = + std::make_shared(); + constraints = std::make_shared(); reinit_thermo = true; time_step = -1.; sim_time = 0.; @@ -85,7 +93,17 @@ void System::initialize() { auto handle = shared_from_this(); cell_structure->bind_system(handle); lees_edwards->bind_system(handle); + immersed_boundaries->bind_system(handle); + bonded_ias->bind_system(handle); thermostat->bind_system(handle); + nonbonded_ias->bind_system(handle); + oif_global->bind_system(handle); + immersed_boundaries->bind_system(handle); +#ifdef COLLISION_DETECTION + collision_detection->bind_system(handle); +#endif + auto_update_accumulators->bind_system(handle); + constraints->bind_system(handle); #ifdef CUDA gpu.bind_system(handle); gpu.initialize(); @@ -94,8 +112,6 @@ void System::initialize() { ek.bind_system(handle); } -bool is_system_set() { return instance != nullptr; } - void reset_system() { instance.reset(); } void set_system(std::shared_ptr new_instance) { @@ -181,7 +197,7 @@ void System::on_boxl_change(bool skip_method_adaption) { dipoles.on_boxl_change(); #endif } - Constraints::constraints.on_boxl_change(); + constraints->on_boxl_change(); } void System::veto_boxl_change(bool skip_particle_checks) const { @@ -193,7 +209,7 @@ void System::veto_boxl_change(bool skip_particle_checks) const { "Cannot reset the box length when particles are present"); } } - Constraints::constraints.veto_boxl_change(); + constraints->veto_boxl_change(); lb.veto_boxl_change(); ek.veto_boxl_change(); } @@ -341,7 +357,7 @@ void System::update_dependent_particles() { // Here we initialize volume conservation // This function checks if the reference volumes have been set and if // necessary calculates them - ::immersed_boundaries.init_volume_conservation(*cell_structure); + immersed_boundaries->init_volume_conservation(*cell_structure); } void System::on_observable_calc() { @@ -379,11 +395,13 @@ double System::maximal_cutoff() const { if (::communicator.size > 1) { // If there is just one node, the bonded cutoff can be omitted // because bond partners are always on the local node. - max_cut = std::max(max_cut, maximal_cutoff_bonded()); + max_cut = std::max(max_cut, bonded_ias->maximal_cutoff()); } max_cut = std::max(max_cut, nonbonded_ias->maximal_cutoff()); - max_cut = std::max(max_cut, collision_detection_cutoff()); +#ifdef COLLISION_DETECTION + max_cut = std::max(max_cut, collision_detection->cutoff()); +#endif return max_cut; } @@ -485,7 +503,7 @@ unsigned System::get_global_ghost_flags() const { } #ifdef COLLISION_DETECTION - if (::collision_params.mode != CollisionModeType::OFF) { + if (collision_detection->mode != CollisionModeType::OFF) { data_parts |= Cells::DATA_PART_BONDS; } #endif diff --git a/src/core/system/System.hpp b/src/core/system/System.hpp index 2bf9dbeb5b5..bc576045c55 100644 --- a/src/core/system/System.hpp +++ b/src/core/system/System.hpp @@ -41,18 +41,28 @@ class LocalBox; struct CellStructure; class Propagation; class InteractionsNonBonded; +class BondedInteractionsMap; namespace Thermostat { class Thermostat; } class ComFixed; class Galilei; class Observable_stat; +class OifGlobal; +class ImmersedBoundaries; +class CollisionDetection; namespace BondBreakage { class BondBreakage; } namespace LeesEdwards { class LeesEdwards; } +namespace Accumulators { +class AutoUpdateAccumulators; +} +namespace Constraints { +class Constraints; +} namespace System { @@ -266,12 +276,21 @@ class System : public std::enable_shared_from_this { std::shared_ptr local_geo; std::shared_ptr cell_structure; std::shared_ptr propagation; + std::shared_ptr bonded_ias; std::shared_ptr nonbonded_ias; std::shared_ptr thermostat; std::shared_ptr comfixed; std::shared_ptr galilei; + std::shared_ptr oif_global; + std::shared_ptr immersed_boundaries; +#ifdef COLLISION_DETECTION + std::shared_ptr collision_detection; +#endif std::shared_ptr bond_breakage; std::shared_ptr lees_edwards; + std::shared_ptr + auto_update_accumulators; + std::shared_ptr constraints; protected: /** @brief Whether the thermostat has to be reinitialized before integration. @@ -306,6 +325,5 @@ class System : public std::enable_shared_from_this { System &get_system(); void set_system(std::shared_ptr new_instance); void reset_system(); -bool is_system_set(); } // namespace System diff --git a/src/core/system/System.impl.hpp b/src/core/system/System.impl.hpp index 45dc104b29e..24a3f8c9c0e 100644 --- a/src/core/system/System.impl.hpp +++ b/src/core/system/System.impl.hpp @@ -25,13 +25,19 @@ #include "ek/Implementation.hpp" #include "lb/Implementation.hpp" +#include "accumulators/AutoUpdateAccumulators.hpp" #include "bond_breakage/bond_breakage.hpp" +#include "bonded_interactions/bonded_interaction_data.hpp" #include "cell_system/CellStructure.hpp" +#include "collision.hpp" +#include "constraints/Constraints.hpp" #include "galilei/ComFixed.hpp" #include "galilei/Galilei.hpp" +#include "immersed_boundary/ImmersedBoundaries.hpp" #include "integrators/Propagation.hpp" #include "lees_edwards/lees_edwards.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" +#include "object-in-fluid/oif_global_forces.hpp" #include "thermostat.hpp" #include "BoxGeometry.hpp" diff --git a/src/core/thermostat.cpp b/src/core/thermostat.cpp index ed55999f4ff..a8d9ba94742 100644 --- a/src/core/thermostat.cpp +++ b/src/core/thermostat.cpp @@ -37,7 +37,7 @@ void Thermostat::Thermostat::recalc_prefactors(double time_step) { if (thermalized_bond) { - thermalized_bond->recalc_prefactors(time_step); + thermalized_bond->recalc_prefactors(time_step, *(get_system().bonded_ias)); } if (langevin) { langevin->recalc_prefactors(kT, time_step); @@ -97,8 +97,9 @@ void Thermostat::Thermostat::lb_coupling_deactivate() { } } -void ThermalizedBondThermostat::recalc_prefactors(double time_step) { - for (auto &kv : ::bonded_ia_params) { +void ThermalizedBondThermostat::recalc_prefactors( + double time_step, BondedInteractionsMap &bonded_ias) { + for (auto &kv : bonded_ias) { if (auto *bond = boost::get(&(*kv.second))) { bond->recalc_prefactors(time_step); } diff --git a/src/core/thermostat.hpp b/src/core/thermostat.hpp index 88ed175641a..121c6a4ebda 100644 --- a/src/core/thermostat.hpp +++ b/src/core/thermostat.hpp @@ -137,7 +137,6 @@ struct LangevinThermostat : public BaseThermostat { * Needs to be called every time the parameters are changed. */ void recalc_prefactors(double kT, double time_step) { - // get_system().get_time_step() pref_friction = -gamma; pref_noise = sigma(kT, time_step, gamma); #ifdef ROTATION @@ -336,9 +335,11 @@ struct LBThermostat : public BaseThermostat { /**@}*/ }; +class BondedInteractionsMap; + /** Thermostat for thermalized bonds. */ struct ThermalizedBondThermostat : public BaseThermostat { - void recalc_prefactors(double time_step); + void recalc_prefactors(double time_step, BondedInteractionsMap &bonded_ias); }; #ifdef DPD diff --git a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp index ce9258b733c..a5f22987bd8 100644 --- a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp +++ b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp @@ -32,7 +32,7 @@ namespace utf = boost::unit_test; #include "PropagationMode.hpp" #include "accumulators/TimeSeries.hpp" #include "actor/registration.hpp" -#include "bonded_interactions/bonded_interaction_utils.hpp" +#include "bonded_interactions/bonded_interaction_data.hpp" #include "bonded_interactions/fene.hpp" #include "bonded_interactions/harmonic.hpp" #include "cell_system/CellStructure.hpp" @@ -176,7 +176,7 @@ BOOST_FIXTURE_TEST_CASE(espresso_system_stand_alone, ParticleFactory) { if (n_nodes == 1) { auto const pids = std::vector{pid2}; auto obs = std::make_shared(pids); - auto acc = Accumulators::TimeSeries(obs, 1); + auto acc = Accumulators::TimeSeries(&system, 1, obs); auto const obs_shape = obs->shape(); auto const ref_shape = std::vector{pids.size(), 3u}; @@ -265,18 +265,18 @@ BOOST_FIXTURE_TEST_CASE(espresso_system_stand_alone, ParticleFactory) { // set up a harmonic bond auto const bond = HarmonicBond(200.0, 0.3, 1.0); auto const bond_ia = std::make_shared(bond); - bonded_ia_params.insert(harm_bond_id, bond_ia); + system.bonded_ias->insert(harm_bond_id, bond_ia); } { // set up a FENE bond auto const bond = FeneBond(300.0, 1.0, 0.3); auto const bond_ia = std::make_shared(bond); - bonded_ia_params.insert(fene_bond_id, bond_ia); + system.bonded_ias->insert(fene_bond_id, bond_ia); } auto const &harm_bond = - *boost::get(bonded_ia_params.at(harm_bond_id).get()); + *boost::get(system.bonded_ias->at(harm_bond_id).get()); auto const &fene_bond = - *boost::get(bonded_ia_params.at(fene_bond_id).get()); + *boost::get(system.bonded_ias->at(fene_bond_id).get()); insert_particle_bond(pid2, harm_bond_id, {pid1}); insert_particle_bond(pid2, fene_bond_id, {pid3}); diff --git a/src/core/unit_tests/bonded_interactions_map_test.cpp b/src/core/unit_tests/bonded_interactions_map_test.cpp index 2bc441b570c..d6ae3b066c7 100644 --- a/src/core/unit_tests/bonded_interactions_map_test.cpp +++ b/src/core/unit_tests/bonded_interactions_map_test.cpp @@ -24,15 +24,20 @@ #include "bonded_interactions/angle_cosine.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" #include "bonded_interactions/fene.hpp" -#include "system/System.hpp" #include #include #include +class BondedInteractionsMapTest : public BondedInteractionsMap { +public: + ~BondedInteractionsMapTest() override = default; + void activate_bond(mapped_type const &ptr) override {} + void deactivate_bond(mapped_type const &ptr) override {} +}; + BOOST_AUTO_TEST_CASE(insert_bond_types) { - System::reset_system(); - BondedInteractionsMap bond_map{}; + BondedInteractionsMapTest bond_map{}; std::unordered_map> mock_core{}; // check defaulted maps are empty BOOST_TEST(bond_map.empty()); diff --git a/src/python/espressomd/CMakeLists.txt b/src/python/espressomd/CMakeLists.txt index bd4b5f9eb63..36996be03ab 100644 --- a/src/python/espressomd/CMakeLists.txt +++ b/src/python/espressomd/CMakeLists.txt @@ -49,6 +49,7 @@ target_compile_options( espresso_pyx_flags INTERFACE $<$>:-Wno-pedantic> + $<$>:-Wno-cast-qual> $<$>:-Wno-deprecated-declarations> $<$:-diag-disable=1224> $<$:-Wno-cpp> diff --git a/src/python/espressomd/accumulators.py b/src/python/espressomd/accumulators.py index e43e66a585e..885a9ed7dc8 100644 --- a/src/python/espressomd/accumulators.py +++ b/src/python/espressomd/accumulators.py @@ -1,3 +1,4 @@ +# # Copyright (C) 2010-2022 The ESPResSo project # # This file is part of ESPResSo. @@ -14,12 +15,21 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# + from .script_interface import ScriptObjectList, ScriptInterfaceHelper, script_interface_register import numpy as np +class _AccumulatorBase(ScriptInterfaceHelper): + + def __reduce__(self): + raise RuntimeError( + "Accumulators can only be checkpointed through an ESPResSo system's auto_update_accumulators property") + + @script_interface_register -class MeanVarianceCalculator(ScriptInterfaceHelper): +class MeanVarianceCalculator(_AccumulatorBase): """ Accumulates results from observables. @@ -65,7 +75,7 @@ def std_error(self): @script_interface_register -class TimeSeries(ScriptInterfaceHelper): +class TimeSeries(_AccumulatorBase): """ Records results from observables. @@ -100,7 +110,7 @@ def time_series(self): @script_interface_register -class Correlator(ScriptInterfaceHelper): +class Correlator(_AccumulatorBase): """ Calculates the correlation of two observables :math:`A` and :math:`B`, diff --git a/src/python/espressomd/cell_system.py b/src/python/espressomd/cell_system.py index 17a5364337a..2f14a1f4b9e 100644 --- a/src/python/espressomd/cell_system.py +++ b/src/python/espressomd/cell_system.py @@ -98,14 +98,6 @@ class CellSystem(ScriptInterfaceHelper): _so_creation_policy = "GLOBAL" _so_bind_methods = ("get_state", "tune_skin", "resort") - def __reduce__(self): - so_callback, so_callback_args = super().__reduce__() - return (CellSystem._restore_object, (so_callback, so_callback_args)) - - @classmethod - def _restore_object(cls, so_callback, so_callback_args): - return so_callback(*so_callback_args) - def set_regular_decomposition(self, **kwargs): """ Activate the regular decomposition cell system. diff --git a/src/python/espressomd/checkpointing.py b/src/python/espressomd/checkpointing.py index db7341d8d64..22f0d953e73 100644 --- a/src/python/espressomd/checkpointing.py +++ b/src/python/espressomd/checkpointing.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2013-2022 The ESPResSo project +# Copyright (C) 2013-2024 The ESPResSo project # # This file is part of ESPResSo. # @@ -18,18 +18,14 @@ # import collections import inspect +import pickle import os import re import signal from . import utils +from . import script_interface -try: - import cPickle as pickle -except ImportError: - import pickle - -# Convenient Checkpointing for ESPResSo class Checkpoint: """Checkpoint handling (reading and writing). @@ -128,21 +124,27 @@ def register(self, *args): Names of python objects to be registered for checkpointing. """ - for a in args: - if not isinstance(a, str): + for varname in args: + if not isinstance(varname, str): raise ValueError( "The object that should be checkpointed is identified with its name given as a string.") # if not a in dir(self.calling_module): - if not self.__hasattr_submodule(self.calling_module, a): + if not self.__hasattr_submodule(self.calling_module, varname): raise KeyError( - f"The given object '{a}' was not found in the current scope.") + f"The given object '{varname}' was not found in the current scope.") - if a in self.checkpoint_objects: + if varname in self.checkpoint_objects: raise KeyError( - f"The given object '{a}' is already registered for checkpointing.") + f"The given object '{varname}' is already registered for checkpointing.") + + obj = self.__getattr_submodule(self.calling_module, varname, None) + if isinstance( + obj, script_interface.ScriptInterfaceHelper) and not obj._so_checkpointable: + raise TypeError( + f"Objects of type {type(obj)} cannot be checkpointed.") - self.checkpoint_objects.append(a) + self.checkpoint_objects.append(varname) def unregister(self, *args): """Unregister python objects for checkpointing. @@ -153,12 +155,15 @@ def unregister(self, *args): Names of python objects to be unregistered for checkpointing. """ - for a in args: - if not isinstance(a, str) or a not in self.checkpoint_objects: + for varname in args: + if not isinstance(varname, str): + raise ValueError( + "The object that should be checkpointed is identified with its name given as a string.") + if varname not in self.checkpoint_objects: raise KeyError( - f"The given object '{a}' was not registered for checkpointing yet.") + f"The given object '{varname}' was not registered for checkpointing yet.") - self.checkpoint_objects.remove(a) + self.checkpoint_objects.remove(varname) def get_registered_objects(self): """ diff --git a/src/python/espressomd/cluster_analysis.py b/src/python/espressomd/cluster_analysis.py index 46815f2262c..bba077d513a 100644 --- a/src/python/espressomd/cluster_analysis.py +++ b/src/python/espressomd/cluster_analysis.py @@ -1,3 +1,4 @@ +# # Copyright (C) 2010-2022 The ESPResSo project # # This file is part of ESPResSo. @@ -14,8 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# from .script_interface import ScriptInterfaceHelper, script_interface_register -from .particle_data import ParticleHandle, ParticleSlice +from .particle_data import ParticleHandle @script_interface_register @@ -28,6 +30,9 @@ class Cluster(ScriptInterfaceHelper): particle_ids() Returns list of particle ids in the cluster + particles() + Get particles in the cluster + size() Returns the number of particles in the cluster @@ -60,22 +65,12 @@ class Cluster(ScriptInterfaceHelper): """ _so_name = "ClusterAnalysis::Cluster" - _so_bind_methods = ("particle_ids", "size", "longest_distance", + _so_checkpointable = False + _so_bind_methods = ("particle_ids", "particles", "size", "longest_distance", "radius_of_gyration", "fractal_dimension", "center_of_mass") _so_creation_policy = "LOCAL" - def particles(self): - """ - Get particles in the cluster. - - Returns - ------- - :class:`espressomd.particle_data.ParticleSlice` - - """ - return ParticleSlice(id_selection=self.particle_ids()) - @script_interface_register class ClusterStructure(ScriptInterfaceHelper): @@ -86,9 +81,12 @@ class ClusterStructure(ScriptInterfaceHelper): ---------- pair_criterion: :class:`espressomd.pair_criteria._PairCriterion` Criterion to decide whether two particles are neighbors. + system: :class:`espressomd.system.System` + System to run the cluster analysis on. """ _so_name = "ClusterAnalysis::ClusterStructure" + _so_checkpointable = False _so_creation_policy = "LOCAL" def __init__(self, *args, **kwargs): diff --git a/src/python/espressomd/collision_detection.py b/src/python/espressomd/collision_detection.py index 24dde5de809..ac90777f507 100644 --- a/src/python/espressomd/collision_detection.py +++ b/src/python/espressomd/collision_detection.py @@ -18,8 +18,6 @@ # from .script_interface import ScriptInterfaceHelper, script_interface_register -from .interactions import BondedInteraction, BondedInteractions -from .code_features import assert_features @script_interface_register @@ -40,14 +38,7 @@ class CollisionDetection(ScriptInterfaceHelper): """ _so_name = "CollisionDetection::CollisionDetection" - - def __init__(self, **kwargs): - assert_features("COLLISION_DETECTION") - if "sip" in kwargs: - super().__init__(**kwargs) - return - super().__init__() - self.set_params(**kwargs) + _so_features = ("COLLISION_DETECTION",) # Do not allow setting of individual attributes def __setattr__(self, *args, **kwargs): @@ -97,22 +88,15 @@ def set_params(self, **kwargs): if "mode" not in kwargs: raise ValueError( "Collision mode must be specified via the 'mode' argument") - # Convert bonds to bond ids - for name in ["bond_centers", "bond_vs"]: - if name in kwargs: - if isinstance(kwargs[name], BondedInteraction): - kwargs[name] = kwargs[name]._bond_id - self.call_method('instantiate', **kwargs) + self.call_method("set_params", **kwargs) def get_parameter(self, name): - """Gets a single parameter from the collision detection.""" - value = super().get_parameter(name) if name in ["bond_centers", "bond_vs"]: if value == -1: # Not defined value = None else: - value = BondedInteractions()[value] + value = self.call_method("get_bond_by_id", bond_id=value) return value def get_params(self): @@ -124,14 +108,3 @@ def get_params(self): for name in self.call_method("params_for_mode", mode=mode): params[name] = self.get_parameter(name) return params - - def __reduce__(self): - so_callback, so_callback_args = super().__reduce__() - return (CollisionDetection._restore_object, - (so_callback, so_callback_args, self.get_params())) - - @classmethod - def _restore_object(cls, so_callback, so_callback_args, state): - so = so_callback(*so_callback_args) - so.set_params(**state) - return so diff --git a/src/python/espressomd/constraints.py b/src/python/espressomd/constraints.py index 79217173de2..3ddfb28640e 100644 --- a/src/python/espressomd/constraints.py +++ b/src/python/espressomd/constraints.py @@ -1,3 +1,4 @@ +# # Copyright (C) 2010-2022 The ESPResSo project # # This file is part of ESPResSo. @@ -14,6 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# from .script_interface import ScriptObjectList, ScriptInterfaceHelper, script_interface_register import numpy as np import itertools @@ -90,6 +92,10 @@ class Constraint(ScriptInterfaceHelper): _so_name = "Constraints::Constraint" + def __reduce__(self): + raise RuntimeError( + "Constraints can only be checkpointed through an ESPResSo system's constraints property") + @script_interface_register class ShapeBasedConstraint(Constraint): @@ -359,9 +365,6 @@ class ForceField(_Interpolated): """ - def __init__(self, **kwargs): - super().__init__(**kwargs) - _codim = 3 _so_name = "Constraints::ForceField" @@ -391,9 +394,6 @@ class PotentialField(_Interpolated): """ - def __init__(self, **kwargs): - super().__init__(**kwargs) - _codim = 1 _so_name = "Constraints::PotentialField" @@ -550,9 +550,6 @@ class FlowField(_Interpolated): """ - def __init__(self, **kwargs): - super().__init__(**kwargs) - _codim = 3 _so_name = "Constraints::FlowField" @@ -613,8 +610,5 @@ class ElectricPotential(_Interpolated): """ - def __init__(self, **kwargs): - super().__init__(**kwargs) - _codim = 1 _so_name = "Constraints::ElectricPotential" diff --git a/src/python/espressomd/integrate.py b/src/python/espressomd/integrate.py index 9e2ac99f283..dfe07d42c68 100644 --- a/src/python/espressomd/integrate.py +++ b/src/python/espressomd/integrate.py @@ -30,11 +30,6 @@ class IntegratorHandle(ScriptInterfaceHelper): _so_name = "Integrators::IntegratorHandle" _so_creation_policy = "GLOBAL" - def __init__(self, **kwargs): - if "sip" not in kwargs and "integrator" not in kwargs: - kwargs["integrator"] = VelocityVerlet() - super().__init__(**kwargs) - def __str__(self): return f'{self.__class__.__name__}({self.integrator.__class__.__name__})' # nopep8 diff --git a/src/python/espressomd/interactions.py b/src/python/espressomd/interactions.py index 4f0600f308d..9b95b78fefa 100644 --- a/src/python/espressomd/interactions.py +++ b/src/python/espressomd/interactions.py @@ -57,14 +57,6 @@ def set_params(self, **kwargs): err_msg = f"setting {self.__class__.__name__} raised an error" self.call_method("set_params", handle_errors_message=err_msg, **params) - def __reduce__(self): - return (NonBondedInteraction._restore_object, - (self.__class__, self.get_params())) - - @classmethod - def _restore_object(cls, derived_class, kwargs): - return derived_class(**kwargs) - @abc.abstractmethod def default_params(self): pass @@ -681,31 +673,8 @@ class NonBondedInteractionHandle(ScriptInterfaceHelper): """ _so_name = "Interactions::NonBondedInteractionHandle" - def __getattr__(self, key): - obj = super().__getattr__(key) - return globals()[obj.__class__.__name__]( - _types=self.call_method("get_types"), **obj.get_params()) - - def _serialize(self): - serialized = [] - for name, obj in self.get_params().items(): - serialized.append((name, obj.__reduce__()[1])) - return serialized - def reset(self): - for key in self._valid_parameters(): - getattr(self, key).deactivate() - - @classmethod - def _restore_object(cls, types, kwargs): - objects = {} - for name, (obj_class, obj_params) in kwargs: - objects[name] = obj_class(**obj_params) - return NonBondedInteractionHandle(_types=types, **objects) - - def __reduce__(self): - return (NonBondedInteractionHandle._restore_object, - (self.call_method("get_types"), self._serialize())) + self.call_method("reset") @script_interface_register @@ -724,40 +693,8 @@ class NonBondedInteractions(ScriptInterfaceHelper): _so_creation_policy = "GLOBAL" _so_bind_methods = ("reset",) - def keys(self): - return [tuple(x) for x in self.call_method("keys")] - def __getitem__(self, key): - self.call_method("check_key", key=key) - return NonBondedInteractionHandle(_types=key) - - def __setitem__(self, key, value): - self.call_method("insert", key=key, object=value) - - def __getstate__(self): - n_types = self.call_method("get_n_types") - state = [] - for i in range(n_types): - for j in range(i, n_types): - handle = NonBondedInteractionHandle(_types=(i, j)) - state.append(((i, j), handle._serialize())) - return {"state": state} - - def __setstate__(self, params): - for types, kwargs in params["state"]: - obj = NonBondedInteractionHandle._restore_object(types, kwargs) - self.call_method("insert", key=types, object=obj) - - @classmethod - def _restore_object(cls, so_callback, so_callback_args, state): - so = so_callback(*so_callback_args) - so.__setstate__(state) - return so - - def __reduce__(self): - so_callback, (so_name, so_bytestring) = super().__reduce__() - return (NonBondedInteractions._restore_object, - (so_callback, (so_name, so_bytestring), self.__getstate__())) + return self.call_method("get_handle", key=key) class BONDED_IA(enum.IntEnum): @@ -802,53 +739,16 @@ def __init__(self, **kwargs): feature = self.__class__.__dict__.get("_so_feature") if feature is not None: code_features.assert_features(feature) - - if "sip" not in kwargs: - if "bond_id" in kwargs: - # create a new script interface object for a bond that already - # exists in the core via its id (BondedInteractions getter and - # checkpointing constructor #1) - bond_id = kwargs["bond_id"] - super().__init__(bond_id=bond_id) - # Check if the bond type in ESPResSo core matches this class - if self.call_method("get_zero_based_type", - bond_id=bond_id) != self._type_number: - raise RuntimeError( - f"The bond with id {bond_id} is not defined as a " - f"{self._type_number.name} bond in the ESPResSo core.") - self._bond_id = bond_id - self._ctor_params = self.get_params() - else: - # create a new script interface object from bond parameters - # (normal bond creation and checkpointing constructor #2) - params = self.get_default_params() - params.update(kwargs) - super().__init__(**params) - self._ctor_params = params - self._bond_id = -1 - else: - # create a new bond based on a bond in the script interface - # (checkpointing constructor #3) + if "sip" in kwargs: super().__init__(**kwargs) - self._bond_id = -1 self._ctor_params = self.get_params() - - def __reduce__(self): - if self._bond_id != -1: - # checkpointing constructor #1 - return (BondedInteraction._restore_object, - (self.__class__, {"bond_id": self._bond_id})) + self._bond_id = -1 else: - # checkpointing constructor #2 - return (BondedInteraction._restore_object, - (self.__class__, self._serialize())) - - def _serialize(self): - return self._ctor_params.copy() - - @classmethod - def _restore_object(cls, derived_class, kwargs): - return derived_class(**kwargs) + params = self.get_default_params() + params.update(kwargs) + super().__init__(**params) + self._ctor_params = params + self._bond_id = -1 def __setattr__(self, attr, value): super().__setattr__(attr, value) @@ -1301,7 +1201,7 @@ def get_default_params(self): """Gets default values of optional parameters. """ - return {"k2": 0} + return {"k2": 0., "is_initialized": False, "_cache": None} @script_interface_register @@ -1331,7 +1231,7 @@ def get_default_params(self): """Gets default values of optional parameters. """ - return {"refShape": "Flat"} + return {"refShape": "Flat", "theta0": 0., "is_initialized": False} @script_interface_register @@ -1585,27 +1485,3 @@ def __iter__(self): for bond_id in self.call_method('get_bond_ids'): if self.call_method("get_zero_based_type", bond_id=bond_id): yield self[bond_id] - - def __getstate__(self): - params = {} - for bond_id in self.call_method('get_bond_ids'): - if self.call_method("get_zero_based_type", bond_id=bond_id): - obj = self[bond_id] - if hasattr(obj, "params"): - params[bond_id] = (obj._type_number, obj._serialize()) - return params - - def __setstate__(self, params): - for bond_id, (type_number, bond_params) in params.items(): - self[bond_id] = self._bond_classes[type_number](**bond_params) - - def __reduce__(self): - so_callback, (so_name, so_bytestring) = super().__reduce__() - return (BondedInteractions._restore_object, - (so_callback, (so_name, so_bytestring), self.__getstate__())) - - @classmethod - def _restore_object(cls, so_callback, so_callback_args, state): - so = so_callback(*so_callback_args) - so.__setstate__(state) - return so diff --git a/src/python/espressomd/io/mpiio.py b/src/python/espressomd/io/mpiio.py index bf56ef67e9d..f6832fe8758 100644 --- a/src/python/espressomd/io/mpiio.py +++ b/src/python/espressomd/io/mpiio.py @@ -1,3 +1,4 @@ +# # Copyright (C) 2010-2022 The ESPResSo project # # This file is part of ESPResSo. @@ -14,6 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# + from ..script_interface import ScriptInterfaceHelper, script_interface_register @@ -25,6 +28,7 @@ class Mpiio(ScriptInterfaceHelper): Used to output particle data using MPI-IO to binary files. """ _so_name = "ScriptInterface::MPIIO::MPIIOScript" + _so_checkpointable = False _so_creation_policy = "GLOBAL" def write(self, prefix=None, positions=False, velocities=False, diff --git a/src/python/espressomd/particle_data.py b/src/python/espressomd/particle_data.py index 4880dc4e3ef..8b016f3b166 100644 --- a/src/python/espressomd/particle_data.py +++ b/src/python/espressomd/particle_data.py @@ -21,7 +21,6 @@ import collections import functools from .interactions import BondedInteraction -from .interactions import BondedInteractions from .utils import nesting_level, array_locked, is_valid_type from .utils import check_type_or_throw_except from .code_features import assert_features, has_features @@ -375,6 +374,7 @@ class ParticleHandle(ScriptInterfaceHelper): """ _so_name = "Particles::ParticleHandle" + _so_checkpointable = False _so_creation_policy = "GLOBAL" _so_bind_methods = ( "delete_all_bonds", "is_virtual", @@ -515,10 +515,9 @@ def bonds(self): """ bonds = [] - for bond_view in self.call_method("get_bonds_view"): - bond_id = bond_view[0] - partner_ids = bond_view[1:] - bonds.append((BondedInteractions()[bond_id], *partner_ids)) + for bond_id, *partner_ids in self.call_method("get_bonds_view"): + bond = self.call_method("get_bond_by_id", bond_id=bond_id) + bonds.append((bond, *partner_ids)) return tuple(bonds) @@ -667,7 +666,9 @@ def normalize_and_check_bond_or_throw_exception(self, bond): bond = list(bond) # Bond type or numerical bond id if is_valid_type(bond[0], int): - bond[0] = BondedInteractions()[bond[0]] + bond_id = bond[0] + bond[0] = self.call_method("get_bond_by_id", bond_id=bond_id) + bond[0]._bond_id = bond_id elif not isinstance(bond[0], BondedInteraction): raise Exception( f"1st element of Bond has to be of type BondedInteraction or int, got {type(bond[0])}") @@ -857,14 +858,8 @@ class ParticleSlice(ScriptInterfaceHelper): """ _so_name = "Particles::ParticleSlice" - _so_creation_policy = "LOCAL" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - if "sip" not in kwargs: - for p_id in self.id_selection: - if not self.call_method("particle_exists", p_id=p_id): - raise IndexError(f"Particle does not exist: {p_id}") + _so_checkpointable = False + _so_creation_policy = "GLOBAL" def __iter__(self): return self._id_gen() @@ -875,8 +870,8 @@ def _id_gen(self): """ for chunk in self.chunks(self.id_selection, self.chunk_size): self.call_method("prefetch_particle_data", chunk=chunk) - for i in chunk: - yield ParticleHandle(id=i) + for p_id in chunk: + yield self.call_method("get_particle", p_id=p_id) def chunks(self, l, n): """ @@ -896,8 +891,8 @@ def pos_folded(self): """ pos_array = np.zeros((len(self.id_selection), 3)) for i in range(len(self.id_selection)): - pos_array[i, :] = ParticleHandle( - id=self.id_selection[i]).pos_folded + pos_array[i, :] = self.call_method( + "get_particle", p_id=self.id_selection[i]).pos_folded return pos_array @pos_folded.setter @@ -907,17 +902,18 @@ def pos_folded(self, value): def add_exclusion(self, _partner): assert_features(["EXCLUSIONS"]) for p_id in self.id_selection: - ParticleHandle(id=p_id).add_exclusion(_partner) + self.call_method("get_particle", p_id=p_id).add_exclusion(_partner) def delete_exclusion(self, _partner): assert_features(["EXCLUSIONS"]) for p_id in self.id_selection: - ParticleHandle(id=p_id).delete_exclusion(_partner) + p = self.call_method("get_particle", p_id=p_id) + p.delete_exclusion(_partner) def __str__(self): return "ParticleSlice([" + \ - ", ".join(str(ParticleHandle(id=i)) - for i in self.id_selection) + "])" + ", ".join(str(self.call_method("get_particle", p_id=p_id)) + for p_id in self.id_selection) + "])" def update(self, new_properties): if "id" in new_properties: @@ -933,7 +929,7 @@ def add_bond(self, _bond): """ for p_id in self.id_selection: - ParticleHandle(id=p_id).add_bond(_bond) + self.call_method("get_particle", p_id=p_id).add_bond(_bond) def delete_bond(self, _bond): """ @@ -941,11 +937,11 @@ def delete_bond(self, _bond): """ for p_id in self.id_selection: - ParticleHandle(id=p_id).delete_bond(_bond) + self.call_method("get_particle", p_id=p_id).delete_bond(_bond) def delete_all_bonds(self): for p_id in self.id_selection: - ParticleHandle(id=p_id).delete_all_bonds() + self.call_method("get_particle", p_id=p_id).delete_all_bonds() def remove(self): """ @@ -957,7 +953,7 @@ def remove(self): """ for p_id in self.id_selection: - ParticleHandle(id=p_id).remove() + self.call_method("get_particle", p_id=p_id).remove() def __setattr__(self, name, value): if name != "chunk_size" and name != "id_selection" and name not in particle_attributes: @@ -987,7 +983,7 @@ def to_dict(self): odict = {} for p in self: - pdict = ParticleHandle(id=p.id).to_dict() + pdict = self.call_method("get_particle", p_id=p.id).to_dict() for p_key, p_value in pdict.items(): if p_key in odict: odict[p_key].append(p_value) @@ -1033,6 +1029,7 @@ class ParticleList(ScriptInterfaceHelper): """ _so_name = "Particles::ParticleList" + _so_checkpointable = False _so_creation_policy = "GLOBAL" _so_bind_methods = ( "clear", "auto_exclusions" @@ -1042,13 +1039,13 @@ def by_id(self, p_id): """ Access a particle by its integer id. """ - return ParticleHandle(id=p_id) + return self.call_method("by_id", p_id=p_id) def by_ids(self, ids): """ Get a slice of particles by their integer ids. """ - return ParticleSlice(id_selection=ids) + return self.call_method("by_ids", id_selection=ids) def all(self): """ @@ -1275,7 +1272,7 @@ def select(self, *args, **kwargs): for p in self: if args[0](p): ids.append(p.id) - return ParticleSlice(id_selection=ids) + return self.call_method("by_ids", id_selection=ids) # Did we get a set of keyword args? elif len(args) == 0: @@ -1296,20 +1293,20 @@ def select(self, *args, **kwargs): break if select: ids.append(p.id) - return ParticleSlice(id_selection=ids) + return self.call_method("by_ids", id_selection=ids) else: raise Exception( "select() takes either selection function as positional argument or a set of keyword arguments.") -def set_slice_one_for_all(particle_slice, attribute, values): - for i in particle_slice.id_selection: - setattr(ParticleHandle(id=i), attribute, values) +def set_slice_one_for_all(p_slice, attribute, values): + for i in p_slice.id_selection: + setattr(p_slice.call_method("get_particle", p_id=i), attribute, values) -def set_slice_one_for_each(particle_slice, attribute, values): - for i, v in zip(particle_slice.id_selection, values): - setattr(ParticleHandle(id=i), attribute, v) +def set_slice_one_for_each(p_slice, attribute, values): + for i, v in zip(p_slice.id_selection, values): + setattr(p_slice.call_method("get_particle", p_id=i), attribute, v) def _add_particle_slice_properties(): @@ -1371,7 +1368,7 @@ def set_attribute(particle_slice, values, attribute): else: target = getattr( - ParticleHandle(id=particle_slice.id_selection[0]), attribute) + particle_slice.call_method("get_particle", p_id=particle_slice.id_selection[0]), attribute) target_shape = np.shape(target) if not target_shape: # scalar quantity @@ -1409,8 +1406,9 @@ def get_attribute(particle_slice, attribute): return np.empty(0, dtype=type(None)) # get first slice member to determine its type - target = getattr(ParticleHandle( - id=particle_slice.id_selection[0]), attribute) + p_id = particle_slice.id_selection[0] + target = getattr( + particle_slice.call_method("get_particle", p_id=p_id), attribute) if isinstance(target, array_locked): # vectorial quantity target_type = target.dtype else: # scalar quantity diff --git a/src/python/espressomd/script_interface.pyx b/src/python/espressomd/script_interface.pyx index 338b21a0148..a99b48d8e3a 100644 --- a/src/python/espressomd/script_interface.pyx +++ b/src/python/espressomd/script_interface.pyx @@ -431,6 +431,7 @@ class ScriptInterfaceHelper(PScriptInterface): _so_name = None _so_features = () _so_bind_methods = () + _so_checkpointable = True _so_creation_policy = "GLOBAL" def __init__(self, **kwargs): @@ -444,6 +445,7 @@ class ScriptInterfaceHelper(PScriptInterface): self.define_bound_methods() def __reduce__(self): + assert self._so_checkpointable return (_unpickle_so_class, (self._so_name, self._serialize())) def __dir__(self): diff --git a/src/python/espressomd/system.py b/src/python/espressomd/system.py index dc0e3151cda..ed21a3b760f 100644 --- a/src/python/espressomd/system.py +++ b/src/python/espressomd/system.py @@ -20,6 +20,7 @@ import numpy as np import collections +# pylint: disable=unused-import from . import accumulators from . import analyze from . import bond_breakage @@ -36,35 +37,12 @@ from . import lees_edwards from . import particle_data from . import thermostat +# pylint: enable=unused-import from .code_features import has_features, assert_features from .script_interface import script_interface_register, ScriptInterfaceHelper -@script_interface_register -class _System(ScriptInterfaceHelper): - """ - Wrapper class required for technical reasons only. - - When reloading from a checkpoint file, the box length, periodicity, and - global cutoff must be set before anything else. Due to how pickling works, - this can only be achieved by encapsulating them in a member object of the - System class, and adding that object as the first element of the ordered - dict that is used during serialization. When the System class is reloaded, - the ordered dict is walked through and objects are deserialized in the same - order. Since many objects depend on the box length, the `_System` has - to be deserialized first. This guarantees the box geometry is already set - in the core before e.g. particles and bonds are deserialized. - - """ - _so_name = "System::System" - _so_creation_policy = "GLOBAL" - _so_bind_methods = ( - "setup_type_map", - "number_of_particles", - "rotate_system") - - @script_interface_register class System(ScriptInterfaceHelper): """ @@ -146,60 +124,24 @@ class System(ScriptInterfaceHelper): How much to rotate """ - _so_name = "System::SystemFacade" + _so_name = "System::System" _so_creation_policy = "GLOBAL" - _so_bind_methods = _System._so_bind_methods + _so_bind_methods = ( + "setup_type_map", + "number_of_particles", + "rotate_system") def __init__(self, **kwargs): if "sip" in kwargs: super().__init__(**kwargs) self._setup_atexit() return - super().__init__() - - if self.call_method("is_system_created"): - raise RuntimeError( - "You can only have one instance of the system class at a time.") - if "box_l" not in kwargs: - raise ValueError("Required argument 'box_l' not provided.") - - setable_properties = ["box_l", "min_global_cut", "periodicity", "time", - "time_step", "force_cap", "max_oif_objects"] - - self.call_method("set_system_handle", obj=_System(**kwargs)) - self.integrator = integrate.IntegratorHandle() - for key in ("box_l", "periodicity", "min_global_cut"): - if key in kwargs: - del kwargs[key] - for arg in kwargs: - if arg not in setable_properties: - raise ValueError( - f"Property '{arg}' can not be set via argument to System class.") - System.__setattr__(self, arg, kwargs.get(arg)) - self.analysis = analyze.Analysis() - self.auto_update_accumulators = accumulators.AutoUpdateAccumulators() - self.bonded_inter = interactions.BondedInteractions() - self.cell_system = cell_system.CellSystem() - self.bond_breakage = bond_breakage.BreakageSpecs() - if has_features("COLLISION_DETECTION"): - self.collision_detection = collision_detection.CollisionDetection( - mode="off") - self.comfixed = comfixed.ComFixed() - self.constraints = constraints.Constraints() + super().__init__(_regular_constructor=True, **kwargs) if has_features("CUDA"): self.cuda_init_handle = cuda_init.CudaInitHandle() - if has_features("ELECTROSTATICS"): - self.electrostatics = electrostatics.Container() - if has_features("DIPOLES"): - self.magnetostatics = magnetostatics.Container() if has_features("WALBERLA"): self._lb = None self._ekcontainer = None - self.galilei = galilei.GalileiTransform() - self.lees_edwards = lees_edwards.LeesEdwards() - self.non_bonded_inter = interactions.NonBondedInteractions() - self.part = particle_data.ParticleList() - self.thermostat = thermostat.Thermostat() self._ase_interface = None # lock class @@ -225,18 +167,11 @@ def _restore_object(cls, so_callback, so_callback_args, state): return so def __getstate__(self): - checkpointable_properties = [ - "non_bonded_inter", "bonded_inter", - "part", "auto_update_accumulators", - "constraints", - ] - if has_features("COLLISION_DETECTION"): - checkpointable_properties.append("collision_detection") + checkpointable_properties = [] if has_features("WALBERLA"): checkpointable_properties += ["_lb", "_ekcontainer"] odict = collections.OrderedDict() - odict["_system_handle"] = self.call_method("get_system_handle") for property_name in checkpointable_properties: odict[property_name] = System.__getattribute__(self, property_name) if self._ase_interface is not None: @@ -244,8 +179,6 @@ def __getstate__(self): return odict def __setstate__(self, params): - # note: this class is initialized twice by pickle - self.call_method("set_system_handle", obj=params.pop("_system_handle")) # initialize Python-only members if "_ase_interface" in params: from espressomd.plugins.ase import ASEInterface diff --git a/src/script_interface/ObjectHandle.hpp b/src/script_interface/ObjectHandle.hpp index c8258fcc09a..2648d716faa 100644 --- a/src/script_interface/ObjectHandle.hpp +++ b/src/script_interface/ObjectHandle.hpp @@ -77,7 +77,6 @@ class ObjectHandle { */ void construct(VariantMap const ¶ms) { do_construct(params); } -private: virtual void do_construct(VariantMap const ¶ms) { for (auto const &p : params) { do_set_parameter(p.first, p.second); diff --git a/src/script_interface/ObjectList.hpp b/src/script_interface/ObjectList.hpp index ccfcad19f44..a88dbbc3a1f 100644 --- a/src/script_interface/ObjectList.hpp +++ b/src/script_interface/ObjectList.hpp @@ -45,6 +45,7 @@ class ObjectList : public ObjectContainer { public: using Base = ObjectContainer; using Base::add_parameters; + using Base::context; private: std::vector> m_elements; @@ -81,7 +82,7 @@ class ObjectList : public ObjectContainer { } throw Exception(""); } - add_in_core(element); + context()->parallel_try_catch([&]() { add_in_core(element); }); m_elements.push_back(element); } diff --git a/src/script_interface/ObjectMap.hpp b/src/script_interface/ObjectMap.hpp index 8beb509b897..0b4adea93a6 100644 --- a/src/script_interface/ObjectMap.hpp +++ b/src/script_interface/ObjectMap.hpp @@ -53,7 +53,6 @@ class ObjectMap : public ObjectContainer { virtual void insert_in_core(KeyType const &key, std::shared_ptr const &obj_ptr) = 0; virtual void erase_in_core(KeyType const &key) = 0; - virtual void before_do_construct() = 0; public: ObjectMap() { @@ -63,13 +62,7 @@ class ObjectMap : public ObjectContainer { }); } - void do_construct(VariantMap const ¶ms) override { - before_do_construct(); - m_elements = get_value_or(params, "_objects", {}); - for (auto const &[key, element] : m_elements) { - insert_in_core(key, element); - } - } + void do_construct(VariantMap const ¶ms) override = 0; /** * @brief Add an element to the map. @@ -179,6 +172,13 @@ class ObjectMap : public ObjectContainer { return Base::do_call_method(method, parameters); } + void restore_from_checkpoint(VariantMap const ¶ms) { + m_elements = get_value_or(params, "_objects", {}); + for (auto const &[key, element] : m_elements) { + insert_in_core(key, element); + } + } + KeyType get_key(Variant const &key) const { try { return get_value(key); diff --git a/src/script_interface/accumulators/AccumulatorBase.hpp b/src/script_interface/accumulators/AccumulatorBase.hpp index b5e7c88cd9c..118c177b898 100644 --- a/src/script_interface/accumulators/AccumulatorBase.hpp +++ b/src/script_interface/accumulators/AccumulatorBase.hpp @@ -19,9 +19,12 @@ #pragma once -#include "core/accumulators/AccumulatorBase.hpp" #include "script_interface/ScriptInterface.hpp" #include "script_interface/auto_parameters/AutoParameters.hpp" +#include "script_interface/system/System.hpp" + +#include "core/accumulators/AccumulatorBase.hpp" +#include "core/system/System.hpp" #include #include @@ -39,16 +42,42 @@ class AccumulatorBase : public AutoParameters { }, [this]() { return accumulator()->delta_N(); }}}); } - Variant call_method(std::string const &method, VariantMap const ¶meters) { + Variant do_call_method(std::string const &method, + VariantMap const ¶meters) override { if (method == "shape") { auto const shape = accumulator()->shape(); return std::vector{shape.begin(), shape.end()}; } + if (method == "reload_from_checkpoint") { + accumulator()->set_internal_state( + get_value(parameters, "state")); + return {}; + } return {}; } virtual std::shared_ptr accumulator() const = 0; virtual std::shared_ptr<::Accumulators::AccumulatorBase> accumulator() = 0; + +protected: + auto get_core_system_pointer(VariantMap const ¶ms) const { + auto const *system = &::System::get_system(); + if (params.contains("system")) { + system = + &(get_value>(params, "system") + ->get_system()); + } + return system; + } + +private: + std::string get_internal_state() const override { + return accumulator()->get_internal_state(); + } + + void set_internal_state(std::string const &state) override { + call_method("reload_from_checkpoint", {{"state", state}}); + } }; } // namespace Accumulators diff --git a/src/script_interface/accumulators/AutoUpdateAccumulators.hpp b/src/script_interface/accumulators/AutoUpdateAccumulators.hpp index 79057a0e241..27f0f8f8611 100644 --- a/src/script_interface/accumulators/AutoUpdateAccumulators.hpp +++ b/src/script_interface/accumulators/AutoUpdateAccumulators.hpp @@ -21,31 +21,52 @@ #include "AccumulatorBase.hpp" -#include "core/accumulators.hpp" +#include "core/accumulators/AutoUpdateAccumulators.hpp" +#include "core/system/System.hpp" + #include "script_interface/ObjectList.hpp" #include "script_interface/ScriptInterface.hpp" +#include "script_interface/system/Leaf.hpp" +#include "script_interface/system/System.hpp" + +#include namespace ScriptInterface { namespace Accumulators { -class AutoUpdateAccumulators : public ObjectList { + +using AutoUpdateAccumulators_t = ObjectList< + AccumulatorBase, + AutoParameters, System::Leaf>>; + +class AutoUpdateAccumulators : public AutoUpdateAccumulators_t { + using Base = AutoUpdateAccumulators_t; + std::shared_ptr<::Accumulators::AutoUpdateAccumulators> m_handle; + std::unique_ptr m_params; + bool has_in_core(std::shared_ptr const &obj_ptr) const override { - return ::Accumulators::auto_update_contains(obj_ptr->accumulator().get()); + return m_handle->contains(obj_ptr->accumulator().get()); } void add_in_core(std::shared_ptr const &obj_ptr) override { - ::Accumulators::auto_update_add(obj_ptr->accumulator().get()); + m_handle->add(obj_ptr->accumulator().get()); } void remove_in_core(std::shared_ptr const &obj_ptr) override { - ::Accumulators::auto_update_remove(obj_ptr->accumulator().get()); + m_handle->remove(obj_ptr->accumulator().get()); + } + + void do_construct(VariantMap const ¶ms) override { + m_params = std::make_unique(params); } -private: - // disable serialization: pickling done by the python interface - std::string get_internal_state() const override { return {}; } - void set_internal_state(std::string const &) override {} + void on_bind_system(::System::System &system) override { + m_handle = system.auto_update_accumulators; + m_handle->bind_system(m_system.lock()); + Base::do_construct(*m_params); + m_params.reset(); + } }; } /* namespace Accumulators */ } /* namespace ScriptInterface */ diff --git a/src/script_interface/accumulators/Correlator.hpp b/src/script_interface/accumulators/Correlator.hpp index 4e798b81dc7..18d51c8c937 100644 --- a/src/script_interface/accumulators/Correlator.hpp +++ b/src/script_interface/accumulators/Correlator.hpp @@ -19,16 +19,17 @@ * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_CORRELATORS_CORRELATOR_HPP -#define SCRIPT_INTERFACE_CORRELATORS_CORRELATOR_HPP +#pragma once #include "AccumulatorBase.hpp" #include "script_interface/ScriptInterface.hpp" #include "script_interface/auto_parameters/AutoParameters.hpp" #include "script_interface/observables/Observable.hpp" +#include "script_interface/system/System.hpp" #include "core/accumulators/Correlator.hpp" +#include "core/system/System.hpp" #include @@ -59,7 +60,7 @@ class Correlator : public AccumulatorBase { void do_construct(VariantMap const &args) override { set_from_args(m_obs1, args, "obs1"); - if (args.count("obs2")) + if (args.contains("obs2")) set_from_args(m_obs2, args, "obs2"); else m_obs2 = m_obs1; @@ -69,10 +70,10 @@ class Correlator : public AccumulatorBase { ObjectHandle::context()->parallel_try_catch([&]() { m_correlator = std::make_shared( + get_core_system_pointer(args), get_value(args, "delta_N"), get_value(args, "tau_lin"), get_value(args, "tau_max"), - get_value(args, "delta_N"), comp1, comp2, - get_value(args, "corr_operation"), m_obs1->observable(), - m_obs2->observable(), + comp1, comp2, get_value(args, "corr_operation"), + m_obs1->observable(), m_obs2->observable(), get_value_or(args, "args", {})); }); } @@ -86,10 +87,12 @@ class Correlator : public AccumulatorBase { if (method == "update") { ObjectHandle::context()->parallel_try_catch( [&]() { correlator()->update(context()->get_comm()); }); + return {}; } if (method == "finalize") { ObjectHandle::context()->parallel_try_catch( [&]() { correlator()->finalize(context()->get_comm()); }); + return {}; } if (method == "get_correlation") { if (ObjectHandle::context()->is_head_node()) { @@ -110,7 +113,7 @@ class Correlator : public AccumulatorBase { return {}; } - return AccumulatorBase::call_method(method, parameters); + return AccumulatorBase::do_call_method(method, parameters); } std::shared_ptr<::Accumulators::AccumulatorBase> accumulator() override { @@ -129,17 +132,7 @@ class Correlator : public AccumulatorBase { std::shared_ptr m_obs1; std::shared_ptr m_obs2; - - std::string get_internal_state() const override { - return m_correlator->get_internal_state(); - } - - void set_internal_state(std::string const &state) override { - m_correlator->set_internal_state(state); - } }; } // namespace Accumulators -} /* namespace ScriptInterface */ - -#endif +} // namespace ScriptInterface diff --git a/src/script_interface/accumulators/MeanVarianceCalculator.hpp b/src/script_interface/accumulators/MeanVarianceCalculator.hpp index 81e1428b82d..d46a79e6431 100644 --- a/src/script_interface/accumulators/MeanVarianceCalculator.hpp +++ b/src/script_interface/accumulators/MeanVarianceCalculator.hpp @@ -19,14 +19,16 @@ * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_ACCUMULATORS_ACCUMULATOR_HPP -#define SCRIPT_INTERFACE_ACCUMULATORS_ACCUMULATOR_HPP +#pragma once #include "AccumulatorBase.hpp" -#include "core/accumulators/MeanVarianceCalculator.hpp" + #include "script_interface/ScriptInterface.hpp" #include "script_interface/observables/Observable.hpp" +#include "core/accumulators/MeanVarianceCalculator.hpp" +#include "core/system/System.hpp" + #include #include #include @@ -43,7 +45,8 @@ class MeanVarianceCalculator : public AccumulatorBase { if (m_obs) m_accumulator = std::make_shared<::Accumulators::MeanVarianceCalculator>( - m_obs->observable(), get_value_or(params, "delta_N", 1)); + get_core_system_pointer(params), + get_value_or(params, "delta_N", 1), m_obs->observable()); } std::shared_ptr<::Accumulators::MeanVarianceCalculator> @@ -61,6 +64,7 @@ class MeanVarianceCalculator : public AccumulatorBase { if (method == "update") { ObjectHandle::context()->parallel_try_catch( [&]() { mean_variance_calculator()->update(context()->get_comm()); }); + return {}; } if (method == "mean") return mean_variance_calculator()->mean(); @@ -68,7 +72,7 @@ class MeanVarianceCalculator : public AccumulatorBase { return mean_variance_calculator()->variance(); if (method == "std_error") return mean_variance_calculator()->std_error(); - return AccumulatorBase::call_method(method, parameters); + return AccumulatorBase::do_call_method(method, parameters); } std::shared_ptr<::Accumulators::AccumulatorBase> accumulator() override { @@ -85,17 +89,7 @@ class MeanVarianceCalculator : public AccumulatorBase { /* The actual accumulator */ std::shared_ptr<::Accumulators::MeanVarianceCalculator> m_accumulator; std::shared_ptr m_obs; - - std::string get_internal_state() const override { - return m_accumulator->get_internal_state(); - } - - void set_internal_state(std::string const &state) override { - m_accumulator->set_internal_state(state); - } }; } // namespace Accumulators -} /* namespace ScriptInterface */ - -#endif +} // namespace ScriptInterface diff --git a/src/script_interface/accumulators/TimeSeries.hpp b/src/script_interface/accumulators/TimeSeries.hpp index e9f83d8fe75..1539a5fdc7b 100644 --- a/src/script_interface/accumulators/TimeSeries.hpp +++ b/src/script_interface/accumulators/TimeSeries.hpp @@ -16,13 +16,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_ACCUMULATORS_TIME_SERIES_HPP -#define SCRIPT_INTERFACE_ACCUMULATORS_TIME_SERIES_HPP + +#pragma once #include "AccumulatorBase.hpp" #include "script_interface/observables/Observable.hpp" #include "core/accumulators/TimeSeries.hpp" +#include "core/system/System.hpp" #include #include @@ -42,7 +43,8 @@ class TimeSeries : public AccumulatorBase { if (m_obs) m_accumulator = std::make_shared<::Accumulators::TimeSeries>( - m_obs->observable(), get_value_or(params, "delta_N", 1)); + get_core_system_pointer(params), + get_value_or(params, "delta_N", 1), m_obs->observable()); } Variant do_call_method(std::string const &method, @@ -50,6 +52,7 @@ class TimeSeries : public AccumulatorBase { if (method == "update") { ObjectHandle::context()->parallel_try_catch( [&]() { m_accumulator->update(context()->get_comm()); }); + return {}; } if (method == "time_series") { auto const &series = m_accumulator->time_series(); @@ -65,7 +68,7 @@ class TimeSeries : public AccumulatorBase { m_accumulator->clear(); } - return AccumulatorBase::call_method(method, parameters); + return AccumulatorBase::do_call_method(method, parameters); } std::shared_ptr<::Accumulators::AccumulatorBase> accumulator() override { @@ -82,17 +85,7 @@ class TimeSeries : public AccumulatorBase { /* The actual accumulator */ std::shared_ptr<::Accumulators::TimeSeries> m_accumulator; std::shared_ptr m_obs; - - std::string get_internal_state() const override { - return m_accumulator->get_internal_state(); - } - - void set_internal_state(std::string const &state) override { - m_accumulator->set_internal_state(state); - } }; } // namespace Accumulators -} /* namespace ScriptInterface */ - -#endif // SCRIPT_INTERFACE_ACCUMULATORS_TIMESERIES_HPP +} // namespace ScriptInterface diff --git a/src/script_interface/analysis/Analysis.hpp b/src/script_interface/analysis/Analysis.hpp index c880936f96f..b7f599906aa 100644 --- a/src/script_interface/analysis/Analysis.hpp +++ b/src/script_interface/analysis/Analysis.hpp @@ -24,6 +24,7 @@ #include "script_interface/ScriptInterface.hpp" #include "script_interface/system/Leaf.hpp" +#include #include namespace ScriptInterface { diff --git a/src/script_interface/analysis/ObservableStat.cpp b/src/script_interface/analysis/ObservableStat.cpp index fc3dcd0298d..75423d19698 100644 --- a/src/script_interface/analysis/ObservableStat.cpp +++ b/src/script_interface/analysis/ObservableStat.cpp @@ -82,9 +82,9 @@ static auto get_summary(::System::System const &system, dict["total"] = get_obs_contrib({values.data(), obs_dim}); } - auto const n_bonds = static_cast(::bonded_ia_params.get_next_key()); + auto const n_bonds = static_cast(system.bonded_ias->get_next_key()); for (int bond_id = 0; bond_id < n_bonds; ++bond_id) { - if (::bonded_ia_params.get_zero_based_type(bond_id) != 0) { + if (system.bonded_ias->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/bond_breakage/BreakageSpecs.hpp b/src/script_interface/bond_breakage/BreakageSpecs.hpp index f05bcf61fe4..35574840737 100644 --- a/src/script_interface/bond_breakage/BreakageSpecs.hpp +++ b/src/script_interface/bond_breakage/BreakageSpecs.hpp @@ -47,8 +47,9 @@ class BreakageSpecs private: std::shared_ptr<::BondBreakage::BondBreakage> m_bond_breakage; - void before_do_construct() override { + void do_construct(VariantMap const ¶ms) override { m_bond_breakage = std::make_shared<::BondBreakage::BondBreakage>(); + restore_from_checkpoint(params); } void on_bind_system(::System::System &system) override { diff --git a/src/script_interface/cell_system/CellSystem.cpp b/src/script_interface/cell_system/CellSystem.cpp index 8b88ed16a7d..1efd2ed3b4f 100644 --- a/src/script_interface/cell_system/CellSystem.cpp +++ b/src/script_interface/cell_system/CellSystem.cpp @@ -20,6 +20,8 @@ #include "CellSystem.hpp" #include "script_interface/ScriptInterface.hpp" +#include "script_interface/particle_data/ParticleHandle.hpp" +#include "script_interface/particle_data/ParticleSlice.hpp" #include "core/BoxGeometry.hpp" #include "core/bonded_interactions/bonded_interaction_data.hpp" @@ -156,7 +158,8 @@ CellSystem::CellSystem() { }}, {"max_cut_nonbonded", AutoParameter::read_only, [this]() { return get_system().nonbonded_ias->maximal_cutoff(); }}, - {"max_cut_bonded", AutoParameter::read_only, maximal_cutoff_bonded}, + {"max_cut_bonded", AutoParameter::read_only, + [this]() { return get_system().bonded_ias->maximal_cutoff(); }}, {"interaction_range", AutoParameter::read_only, [this]() { return get_system().get_interaction_range(); }}, }); @@ -316,5 +319,13 @@ void CellSystem::initialize(CellStructureType const &cs_type, } } +void CellSystem::configure(Particles::ParticleHandle &particle) { + particle.attach(m_system); +} + +void CellSystem::configure(Particles::ParticleSlice &slice) { + slice.attach(m_system); +} + } // namespace CellSystem } // namespace ScriptInterface diff --git a/src/script_interface/cell_system/CellSystem.hpp b/src/script_interface/cell_system/CellSystem.hpp index daa8f0e8c3f..df3e952fe43 100644 --- a/src/script_interface/cell_system/CellSystem.hpp +++ b/src/script_interface/cell_system/CellSystem.hpp @@ -36,6 +36,10 @@ #include namespace ScriptInterface { +namespace Particles { +class ParticleHandle; +class ParticleSlice; +} // namespace Particles namespace CellSystem { class CellSystem : public AutoParameters { @@ -78,6 +82,11 @@ class CellSystem : public AutoParameters { Variant do_call_method(std::string const &name, VariantMap const ¶ms) override; + auto &get_cell_structure() const { return *m_cell_structure; } + + void configure(Particles::ParticleHandle &); + void configure(Particles::ParticleSlice &); + private: /** * @brief Resort the particles. @@ -92,8 +101,6 @@ class CellSystem : public AutoParameters { void initialize(CellStructureType const &cs_type, VariantMap const ¶ms); - auto &get_cell_structure() const { return *m_cell_structure; } - auto const &get_regular_decomposition() const { return dynamic_cast( std::as_const(get_cell_structure()).decomposition()); diff --git a/src/script_interface/cluster_analysis/Cluster.hpp b/src/script_interface/cluster_analysis/Cluster.hpp index 8306de47fa9..8fa097d8f1d 100644 --- a/src/script_interface/cluster_analysis/Cluster.hpp +++ b/src/script_interface/cluster_analysis/Cluster.hpp @@ -19,12 +19,13 @@ * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_CLUSTER_ANALYSIS_CLUSTER_HPP -#define SCRIPT_INTERFACE_CLUSTER_ANALYSIS_CLUSTER_HPP +#pragma once +#include "core/BoxGeometry.hpp" #include "core/cluster_analysis/Cluster.hpp" #include "script_interface/ScriptInterface.hpp" +#include "script_interface/particle_data/ParticleList.hpp" #include #include @@ -41,6 +42,10 @@ class Cluster : public AutoParameters { if (method == "particle_ids") { return m_cluster->particles; } + if (method == "particles") { + return m_particle_list.lock()->call_method( + "by_ids", {{"id_selection", m_cluster->particles}}); + } if (method == "size") { return (int)m_cluster->particles.size(); } @@ -60,15 +65,17 @@ class Cluster : public AutoParameters { } return {}; } - void set_cluster(std::shared_ptr<::ClusterAnalysis::Cluster> &c) { + void set_cluster(std::shared_ptr<::ClusterAnalysis::Cluster> const &c) { m_cluster = c; } + void set_particle_list(std::weak_ptr const &handle) { + m_particle_list = handle; + } private: std::shared_ptr<::ClusterAnalysis::Cluster> m_cluster; + std::weak_ptr m_particle_list; }; } /* namespace ClusterAnalysis */ } /* namespace ScriptInterface */ - -#endif diff --git a/src/script_interface/cluster_analysis/ClusterStructure.hpp b/src/script_interface/cluster_analysis/ClusterStructure.hpp index 8fd1925b0e3..4f8d3ff2751 100644 --- a/src/script_interface/cluster_analysis/ClusterStructure.hpp +++ b/src/script_interface/cluster_analysis/ClusterStructure.hpp @@ -19,14 +19,17 @@ * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_CLUSTER_ANALYSIS_CLUSTER_STRUCTURE_HPP -#define SCRIPT_INTERFACE_CLUSTER_ANALYSIS_CLUSTER_STRUCTURE_HPP +#pragma once +#include "core/BoxGeometry.hpp" #include "core/cluster_analysis/ClusterStructure.hpp" +#include "core/system/System.hpp" #include "script_interface/ScriptInterface.hpp" #include "script_interface/cluster_analysis/Cluster.hpp" #include "script_interface/pair_criteria/PairCriterion.hpp" +#include "script_interface/particle_data/ParticleList.hpp" +#include "script_interface/system/System.hpp" #include #include @@ -49,16 +52,29 @@ class ClusterStructure : public AutoParameters { }, [this]() { return m_pc; }}}); } + + void do_construct(VariantMap const ¶ms) override { + auto local_params = params; + local_params.erase("system"); + ObjectHandle::do_construct(local_params); + auto system_si = + get_value>(params, "system"); + m_particle_list = get_value>( + system_si->get_parameter("part")); + m_cluster_structure.attach(system_si->get_system().box_geo); + } + Variant do_call_method(std::string const &method, VariantMap const ¶meters) override { if (method == "get_cluster") { + auto const cluster_id = get_value(parameters.at("id")); // Note: Cluster objects are generated on the fly, to avoid having to // store a script interface object for all clusters (which can be // thousands) auto c = std::dynamic_pointer_cast( context()->make_shared("ClusterAnalysis::Cluster", {})); - c->set_cluster( - m_cluster_structure.clusters.at(get_value(parameters.at("id")))); + c->set_cluster(m_cluster_structure.clusters.at(cluster_id)); + c->set_particle_list(m_particle_list); return c; } @@ -91,9 +107,8 @@ class ClusterStructure : public AutoParameters { private: ::ClusterAnalysis::ClusterStructure m_cluster_structure; std::shared_ptr m_pc; + std::weak_ptr m_particle_list; }; } /* namespace ClusterAnalysis */ } /* namespace ScriptInterface */ - -#endif diff --git a/src/script_interface/collision_detection/CollisionDetection.hpp b/src/script_interface/collision_detection/CollisionDetection.hpp index b523c3d96f9..bb1727fd0d8 100644 --- a/src/script_interface/collision_detection/CollisionDetection.hpp +++ b/src/script_interface/collision_detection/CollisionDetection.hpp @@ -19,18 +19,24 @@ * along with this program. If not, see . */ -#ifndef SCRIPT_INTERFACE_COLLISION_DETECTION_COLLISION_DETECTION_HPP -#define SCRIPT_INTERFACE_COLLISION_DETECTION_COLLISION_DETECTION_HPP +#pragma once #include "config/config.hpp" #ifdef COLLISION_DETECTION #include "script_interface/ScriptInterface.hpp" +#include "script_interface/interactions/BondedInteraction.hpp" +#include "script_interface/interactions/BondedInteractions.hpp" +#include "script_interface/system/Leaf.hpp" +#include "script_interface/system/System.hpp" +#include "core/bonded_interactions/bonded_interaction_data.hpp" #include "core/collision.hpp" #include +#include +#include #include #include #include @@ -41,7 +47,12 @@ namespace ScriptInterface { namespace CollisionDetection { -class CollisionDetection : public AutoParameters { +class CollisionDetection + : public AutoParameters { + std::shared_ptr<::CollisionDetection> m_handle; + std::unique_ptr m_params; + std::weak_ptr m_bonded_ias; + std::unordered_map const cd_mode_to_name = { {CollisionModeType::OFF, "off"}, {CollisionModeType::BIND_CENTERS, "bind_centers"}, @@ -63,55 +74,129 @@ class CollisionDetection : public AutoParameters { "distance_glued_particle_to_vs"}}, }; + auto find_bond_id(Variant const &v) const { + auto &system = get_system(); + if (is_type(v)) { + auto const bond_id = get_value(v); + std::optional retval = std::nullopt; + if (system.bonded_ias->contains(bond_id)) { + retval = bond_id; + } + return retval; + } + auto obj = get_value>(v); + return system.bonded_ias->find_bond_id(obj->bonded_ia()); + } + public: CollisionDetection() { for (auto const &kv : cd_mode_to_name) { cd_name_to_mode[kv.second] = kv.first; } - add_parameters( - {{"mode", - [this](Variant const &v) { - auto const name = get_value(v); - check_mode_name(name); - collision_params.mode = cd_name_to_mode.at(name); - }, - [this]() { return cd_mode_to_name.at(collision_params.mode); }}, - - {"bond_centers", collision_params.bond_centers}, - {"bond_vs", collision_params.bond_vs}, - - {"distance", collision_params.distance}, - {"distance_glued_particle_to_vs", - collision_params.dist_glued_part_to_vs}, - {"vs_placement", collision_params.vs_placement}, - - {"part_type_vs", collision_params.vs_particle_type}, - {"part_type_to_be_glued", collision_params.part_type_to_be_glued}, - {"part_type_to_attach_vs_to", - collision_params.part_type_to_attach_vs_to}, - {"part_type_after_glueing", - collision_params.part_type_after_glueing}}); + add_parameters({ + {"mode", + [this](Variant const &v) { + auto const name = get_value(v); + check_mode_name(name); + m_handle->mode = cd_name_to_mode.at(name); + }, + [this]() { return cd_mode_to_name.at(m_handle->mode); }}, + {"bond_centers", + [this](Variant const &v) { + auto const bond_id = find_bond_id(v); + if (not bond_id) { + throw std::invalid_argument("Bond in parameter 'bond_centers' was " + "not added to the system"); + } + m_handle->bond_centers = bond_id.value(); + }, + [this]() { return m_handle->bond_centers; }}, + {"bond_vs", + [this](Variant const &v) { + auto const bond_id = find_bond_id(v); + if (not bond_id) { + throw std::invalid_argument( + "Bond in parameter 'bond_vs' was not added to the system"); + } + m_handle->bond_vs = bond_id.value(); + }, + [this]() { return m_handle->bond_vs; }}, + {"distance", + [this](Variant const &v) { + m_handle->distance = get_value(v); + }, + [this]() { return m_handle->distance; }}, + {"distance_glued_particle_to_vs", + [this](Variant const &v) { + m_handle->dist_glued_part_to_vs = get_value(v); + }, + [this]() { return m_handle->dist_glued_part_to_vs; }}, + {"vs_placement", + [this](Variant const &v) { + m_handle->vs_placement = get_value(v); + }, + [this]() { return m_handle->vs_placement; }}, + {"part_type_vs", + [this](Variant const &v) { + m_handle->vs_particle_type = get_value(v); + }, + [this]() { return m_handle->vs_particle_type; }}, + {"part_type_to_be_glued", + [this](Variant const &v) { + m_handle->part_type_to_be_glued = get_value(v); + }, + [this]() { return m_handle->part_type_to_be_glued; }}, + {"part_type_to_attach_vs_to", + [this](Variant const &v) { + m_handle->part_type_to_attach_vs_to = get_value(v); + }, + [this]() { return m_handle->part_type_to_attach_vs_to; }}, + {"part_type_after_glueing", + [this](Variant const &v) { + m_handle->part_type_after_glueing = get_value(v); + }, + [this]() { return m_handle->part_type_after_glueing; }}, + }); + } + + void do_construct(VariantMap const ¶ms) override { + m_params = std::make_unique(params); + if (params.empty()) { + (*m_params)["mode"] = std::string("off"); + } else { + // Assume we are reloading from a checkpoint file. + // This else branch can be removed once issue #4483 is fixed. + m_params = std::make_unique(); + auto const name = get_value(params, "mode"); + check_mode_name(name); + for (auto const &name : + cd_mode_to_parameters.at(cd_name_to_mode.at(name))) { + (*m_params)[name] = params.at(name); + } + (*m_params)["mode"] = params.at("mode"); + } } Variant do_call_method(const std::string &name, const VariantMap ¶ms) override { - if (name == "instantiate") { + if (name == "set_params") { context()->parallel_try_catch([this, ¶ms]() { - auto collision_params_backup = ::collision_params; + auto const backup = std::make_shared<::CollisionDetection>(*m_handle); + auto &system = get_system(); try { // check provided parameters check_input_parameters(params); // set parameters - ::collision_params = Collision_parameters(); for (auto const &kv : params) { do_set_parameter(get_value(kv.first), kv.second); } // sanitize parameters and calculate derived parameters - ::collision_params.initialize(); + m_handle->initialize(); return none; } catch (...) { // restore original parameters and re-throw exception - ::collision_params = collision_params_backup; + m_handle = system.collision_detection = backup; + m_handle->initialize(); throw; } }); @@ -122,16 +207,35 @@ class CollisionDetection : public AutoParameters { auto const mode = cd_name_to_mode.at(name); return make_vector_of_variants(cd_mode_to_parameters.at(mode)); } + if (name == "get_bond_by_id") { + if (not context()->is_head_node()) { + return {}; + } + return m_bonded_ias.lock()->call_method("get_bond", params); + } return none; } + void attach(std::weak_ptr bonded_ias) { + m_bonded_ias = bonded_ias; + } + private: void check_mode_name(std::string const &name) const { - if (cd_name_to_mode.count(name) == 0) { + if (not cd_name_to_mode.contains(name)) { throw std::invalid_argument("Unknown collision mode '" + name + "'"); } } + void on_bind_system(::System::System &system) override { + m_handle = system.collision_detection; + m_handle->bind_system(m_system.lock()); + if (not m_params->empty()) { + do_call_method("set_params", *m_params); + } + m_params.reset(); + } + void check_input_parameters(VariantMap const ¶ms) const { auto const name = get_value(params, "mode"); check_mode_name(name); @@ -141,8 +245,8 @@ class CollisionDetection : public AutoParameters { std::set{expected_params.begin(), expected_params.end()}; std::set input_parameter_names = {}; for (auto const &kv : params) { - auto const param_name = get_value(kv.first); - if (expected_param_names.count(param_name) == 0) { + auto const ¶m_name = kv.first; + if (not expected_param_names.contains(param_name)) { // throw if parameter is unknown std::ignore = get_parameter(param_name); // throw if parameter is known but doesn't match the selected mode @@ -152,7 +256,7 @@ class CollisionDetection : public AutoParameters { input_parameter_names.insert(param_name); } for (auto const ¶m_name : expected_param_names) { - if (input_parameter_names.count(param_name) == 0) { + if (not input_parameter_names.contains(param_name)) { throw std::runtime_error("Parameter '" + param_name + "' is " + "required for mode '" + name + "'"); } @@ -164,4 +268,3 @@ class CollisionDetection : public AutoParameters { } /* namespace ScriptInterface */ #endif // COLLISION_DETECTION -#endif diff --git a/src/script_interface/constraints/Constraint.hpp b/src/script_interface/constraints/Constraint.hpp index 2bc827bd82a..8f6e28e5343 100644 --- a/src/script_interface/constraints/Constraint.hpp +++ b/src/script_interface/constraints/Constraint.hpp @@ -21,9 +21,14 @@ #pragma once -#include "core/constraints/Constraint.hpp" #include "script_interface/ScriptInterface.hpp" +#include "core/constraints/Constraint.hpp" + +namespace System { +class System; +} + namespace ScriptInterface { namespace Constraints { @@ -32,7 +37,8 @@ class Constraint : public AutoParameters { virtual std::shared_ptr constraint() const = 0; virtual std::shared_ptr<::Constraints::Constraint> constraint() = 0; + virtual void bind_system(std::shared_ptr<::System::System const> const &) {} }; -} /* namespace Constraints */ -} /* namespace ScriptInterface */ +} // namespace Constraints +} // namespace ScriptInterface diff --git a/src/script_interface/constraints/Constraints.hpp b/src/script_interface/constraints/Constraints.hpp index 9e56946b411..29346612cac 100644 --- a/src/script_interface/constraints/Constraints.hpp +++ b/src/script_interface/constraints/Constraints.hpp @@ -23,28 +23,52 @@ #include "Constraint.hpp" -#include "core/constraints.hpp" - #include "script_interface/ObjectList.hpp" #include "script_interface/ScriptInterface.hpp" +#include "script_interface/system/Leaf.hpp" +#include "script_interface/system/System.hpp" + +#include "core/constraints/Constraints.hpp" +#include "core/system/System.hpp" + +#include namespace ScriptInterface { namespace Constraints { -class Constraints : public ObjectList { + +using Constraints_t = + ObjectList, + System::Leaf>>; + +class Constraints : public Constraints_t { + using Base = Constraints_t; + std::shared_ptr<::Constraints::Constraints> m_handle; + std::unique_ptr m_params; + bool has_in_core(std::shared_ptr const &obj_ptr) const override { - return ::Constraints::constraints.contains(obj_ptr->constraint()); + return m_handle->contains(obj_ptr->constraint()); } void add_in_core(std::shared_ptr const &obj_ptr) override { - ::Constraints::constraints.add(obj_ptr->constraint()); + m_handle->add(obj_ptr->constraint()); + obj_ptr->bind_system(m_system.lock()); } void remove_in_core(std::shared_ptr const &obj_ptr) override { - ::Constraints::constraints.remove(obj_ptr->constraint()); + m_handle->remove(obj_ptr->constraint()); + } + + void do_construct(VariantMap const ¶ms) override { + m_handle = std::make_shared<::Constraints::Constraints>(); + m_handle->bind_system(::System::get_system().shared_from_this()); + m_params = std::make_unique(params); } -private: - // disable serialization: pickling done by the python interface - std::string get_internal_state() const override { return {}; } - void set_internal_state(std::string const &) override {} + void on_bind_system(::System::System &system) override { + m_handle = system.constraints; + m_handle->bind_system(m_system.lock()); + Base::do_construct(*m_params); + m_params.reset(); + } }; -} /* namespace Constraints */ -} /* namespace ScriptInterface */ + +} // namespace Constraints +} // namespace ScriptInterface diff --git a/src/script_interface/constraints/ShapeBasedConstraint.hpp b/src/script_interface/constraints/ShapeBasedConstraint.hpp index 83128b964d3..12f2c9d3185 100644 --- a/src/script_interface/constraints/ShapeBasedConstraint.hpp +++ b/src/script_interface/constraints/ShapeBasedConstraint.hpp @@ -22,6 +22,8 @@ #pragma once #include "Constraint.hpp" + +#include "core/BoxGeometry.hpp" #include "core/cell_system/CellStructure.hpp" #include "core/constraints/Constraint.hpp" #include "core/constraints/ShapeBasedConstraint.hpp" @@ -36,11 +38,16 @@ namespace ScriptInterface { namespace Constraints { class ShapeBasedConstraint : public Constraint { + std::unique_ptr m_params; + std::weak_ptr<::System::System const> m_system; + public: ShapeBasedConstraint() - : m_constraint(std::make_shared<::Constraints::ShapeBasedConstraint>( - ::System::get_system())), + : m_constraint(std::make_shared<::Constraints::ShapeBasedConstraint>()), m_shape(nullptr) { + auto system = ::System::get_system().shared_from_this(); + m_constraint->bind_system(system); + m_system = system; add_parameters({{"only_positive", m_constraint->only_positive()}, {"penetrable", m_constraint->penetrable()}, {"particle_type", @@ -65,9 +72,10 @@ class ShapeBasedConstraint : public Constraint { return shape_based_constraint()->total_force(); } if (name == "min_dist") { - auto const &system = ::System::get_system(); + auto const system = m_system.lock(); + assert(system); return shape_based_constraint()->min_dist( - *system.box_geo, system.cell_structure->local_particles()); + *system->box_geo, system->cell_structure->local_particles()); } if (name == "total_normal_force") { return shape_based_constraint()->total_normal_force(); @@ -87,6 +95,25 @@ class ShapeBasedConstraint : public Constraint { return m_constraint; } + void do_construct(VariantMap const ¶ms) override { + m_params = std::make_unique(params); + for (auto const &kv : *m_params) { + do_set_parameter(kv.first, kv.second); + } + } + + void + bind_system(std::shared_ptr<::System::System const> const &system) override { + assert(m_params); + assert(system); + m_system = system; + shape_based_constraint()->bind_system(system); + for (auto const &kv : *m_params) { + do_set_parameter(kv.first, kv.second); + } + m_params.reset(); + } + private: /* The actual constraint */ std::shared_ptr<::Constraints::ShapeBasedConstraint> m_constraint; @@ -95,5 +122,5 @@ class ShapeBasedConstraint : public Constraint { std::shared_ptr m_shape; }; -} /* namespace Constraints */ -} /* namespace ScriptInterface */ +} // namespace Constraints +} // namespace ScriptInterface diff --git a/src/script_interface/integrators/IntegratorHandle.cpp b/src/script_interface/integrators/IntegratorHandle.cpp index 109369935dc..b2c19182fc4 100644 --- a/src/script_interface/integrators/IntegratorHandle.cpp +++ b/src/script_interface/integrators/IntegratorHandle.cpp @@ -96,7 +96,7 @@ IntegratorHandle::IntegratorHandle() { void IntegratorHandle::on_bind_system(::System::System &system) { auto const ¶ms = *m_params; for (auto const &key : get_parameter_insertion_order()) { - if (params.count(key) != 0ul) { + if (params.contains(key)) { // NOLINTNEXTLINE(readability-simplify-boolean-expr) if (not(key == "time_step" and system.propagation->integ_switch == INTEG_METHOD_NVT and @@ -107,7 +107,15 @@ void IntegratorHandle::on_bind_system(::System::System &system) { } } } + auto use_default_integrator = not params.contains("integrator"); m_params.reset(); + if (use_default_integrator) { + if (not context()->is_head_node()) { + return; + } + set_parameter("integrator", + context()->make_shared("Integrators::VelocityVerlet", {})); + } } } // namespace Integrators diff --git a/src/script_interface/interactions/BondedInteraction.hpp b/src/script_interface/interactions/BondedInteraction.hpp index 9843ed82ff5..34d565769fc 100644 --- a/src/script_interface/interactions/BondedInteraction.hpp +++ b/src/script_interface/interactions/BondedInteraction.hpp @@ -26,7 +26,7 @@ */ #include "core/bonded_interactions/bonded_interaction_data.hpp" -#include "core/immersed_boundaries.hpp" +#include "core/immersed_boundary/ImmersedBoundaries.hpp" #include "core/thermostat.hpp" #include "script_interface/ScriptInterface.hpp" @@ -46,6 +46,8 @@ #include #include #include +#include +#include #include namespace ScriptInterface { @@ -78,12 +80,12 @@ class BondedInteraction : public AutoParameters { 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) { + if (not params.contains(std::string(key))) { throw std::runtime_error("Parameter '" + key + "' is missing"); } } for (auto const &kv : params) { - if (valid_keys.count(kv.first) == 0) { + if (not valid_keys.contains(kv.first)) { throw std::runtime_error("Parameter '" + kv.first + "' is not recognized"); } @@ -91,23 +93,10 @@ class BondedInteraction : public AutoParameters { } void do_construct(VariantMap const ¶ms) override { - // Check if initialization "by id" or "by parameters" - if (params.find("bond_id") != params.end()) { - 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 { - context()->parallel_try_catch([&]() { - check_valid_parameters(params); - construct_bond(params); - }); - } + context()->parallel_try_catch([&]() { + check_valid_parameters(params); + construct_bond(params); + }); } virtual void construct_bond(VariantMap const ¶ms) = 0; @@ -128,10 +117,6 @@ class BondedInteraction : public AutoParameters { 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 {}; } @@ -451,6 +436,12 @@ class IBMTriel : public BondedInteractionImpl<::IBMTriel> { add_parameters({ {"k1", AutoParameter::read_only, [this]() { return get_struct().k1; }}, {"k2", AutoParameter::read_only, [this]() { return get_struct().k2; }}, + {"ind1", AutoParameter::read_only, + [this]() { return std::get<0>(get_struct().p_ids); }}, + {"ind2", AutoParameter::read_only, + [this]() { return std::get<1>(get_struct().p_ids); }}, + {"ind3", AutoParameter::read_only, + [this]() { return std::get<2>(get_struct().p_ids); }}, {"maxDist", AutoParameter::read_only, [this]() { return get_struct().maxDist; }}, {"elasticLaw", AutoParameter::read_only, @@ -460,6 +451,14 @@ class IBMTriel : public BondedInteractionImpl<::IBMTriel> { } return std::string("Skalak"); }}, + {"is_initialized", AutoParameter::read_only, + [this]() { return get_struct().is_initialized; }}, + {"_cache", AutoParameter::read_only, + [this]() { + auto &s = get_struct(); + return std::vector{{s.l0, s.lp0, s.sinPhi0, s.cosPhi0, + s.area0, s.a1, s.a2, s.b1, s.b2}}; + }}, }); } @@ -475,21 +474,26 @@ class IBMTriel : public BondedInteractionImpl<::IBMTriel> { 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"), elastic_law, - get_value(params, "k1"), get_value(params, "k2"))); - } - - std::set get_valid_parameters() const override { - auto names = - BondedInteractionImpl::get_valid_parameters(); - names.insert("ind1"); - names.insert("ind2"); - names.insert("ind3"); - return names; + auto bond = CoreBondedInteraction( + get_value(params, "ind1"), get_value(params, "ind2"), + get_value(params, "ind3"), get_value(params, "maxDist"), + elastic_law, get_value(params, "k1"), + get_value(params, "k2")); + if (get_value_or(params, "is_initialized", false)) { + auto const cache = get_value>(params, "_cache"); + assert(cache.size() == 9ul); + bond.l0 = cache[0]; + bond.lp0 = cache[1]; + bond.sinPhi0 = cache[2]; + bond.cosPhi0 = cache[3]; + bond.area0 = cache[4]; + bond.a1 = cache[5]; + bond.a2 = cache[6]; + bond.b1 = cache[7]; + bond.b2 = cache[8]; + bond.is_initialized = true; + } + m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>(std::move(bond)); } }; @@ -498,7 +502,7 @@ class IBMVolCons : public BondedInteractionImpl<::IBMVolCons> { IBMVolCons() { add_parameters({ {"softID", AutoParameter::read_only, - [this]() { return get_struct().softID; }}, + [this]() { return static_cast(get_struct().softID); }}, {"kappaV", AutoParameter::read_only, [this]() { return get_struct().kappaV; }}, }); @@ -507,7 +511,7 @@ class IBMVolCons : public BondedInteractionImpl<::IBMVolCons> { 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 get_struct().get_current_volume(); } return BondedInteraction::do_call_method(name, params); } @@ -525,43 +529,46 @@ class IBMTribend : public BondedInteractionImpl<::IBMTribend> { IBMTribend() { add_parameters({ {"kb", AutoParameter::read_only, [this]() { return get_struct().kb; }}, + {"ind1", AutoParameter::read_only, + [this]() { return std::get<0>(get_struct().p_ids); }}, + {"ind2", AutoParameter::read_only, + [this]() { return std::get<1>(get_struct().p_ids); }}, + {"ind3", AutoParameter::read_only, + [this]() { return std::get<2>(get_struct().p_ids); }}, + {"ind4", AutoParameter::read_only, + [this]() { return std::get<3>(get_struct().p_ids); }}, {"refShape", AutoParameter::read_only, [this]() { - return (m_flat) ? std::string("Flat") : std::string("Initial"); + return std::string((get_struct().flat) ? "Flat" : "Initial"); }}, {"theta0", AutoParameter::read_only, [this]() { return get_struct().theta0; }}, + {"is_initialized", AutoParameter::read_only, + [this]() { return get_struct().is_initialized; }}, }); } private: - bool m_flat; void construct_bond(VariantMap const ¶ms) override { auto const shape_name = get_value(params, "refShape"); + bool flat; if (shape_name == "Flat") { - m_flat = true; + flat = true; } else if (shape_name == "Initial") { - m_flat = false; + 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)); - } - - 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; + auto bond = CoreBondedInteraction( + get_value(params, "ind1"), get_value(params, "ind2"), + get_value(params, "ind3"), get_value(params, "ind4"), + get_value(params, "kb"), flat); + if (get_value_or(params, "is_initialized", false)) { + bond.theta0 = get_value(params, "theta0"); + bond.is_initialized = true; + } + m_bonded_ia = std::make_shared<::Bonded_IA_Parameters>(std::move(bond)); } }; diff --git a/src/script_interface/interactions/BondedInteractions.hpp b/src/script_interface/interactions/BondedInteractions.hpp index 4862612c1fa..ac9f9788757 100644 --- a/src/script_interface/interactions/BondedInteractions.hpp +++ b/src/script_interface/interactions/BondedInteractions.hpp @@ -25,6 +25,8 @@ #include "script_interface/ObjectMap.hpp" #include "script_interface/ScriptInterface.hpp" +#include "script_interface/auto_parameters/AutoParameters.hpp" +#include "script_interface/system/Leaf.hpp" #include #include @@ -36,7 +38,13 @@ namespace ScriptInterface { namespace Interactions { -class BondedInteractions : public ObjectMap { + +using BondedInteractionsBase_t = ObjectMap< + BondedInteraction, + AutoParameters, System::Leaf>>; + +class BondedInteractions : public BondedInteractionsBase_t { + using Base = BondedInteractionsBase_t; using container_type = std::unordered_map>; @@ -44,35 +52,43 @@ class BondedInteractions : public ObjectMap { using key_type = typename container_type::key_type; using mapped_type = typename container_type::mapped_type; - BondedInteractions() : ObjectMap::ObjectMap() { - add_parameters({ - {"_objects", AutoParameter::read_only, - []() { - // deactivate serialization (done at the Python level) - return make_unordered_map_of_variants(container_type{}); - }}, - }); - } - private: container_type m_bonds; + std::shared_ptr<::BondedInteractionsMap> m_handle; + std::unique_ptr m_params; - void before_do_construct() override {} + void do_construct(VariantMap const ¶ms) override { + m_handle = std::make_shared<::BondedInteractionsMap>(); + m_params = std::make_unique(params); + } + + void on_bind_system(::System::System &system) override { + system.bonded_ias = m_handle; + m_handle->bind_system(m_system.lock()); + m_handle->on_ia_change(); + if (m_params and not m_params->empty()) { + restore_from_checkpoint(*m_params); + } + m_params.reset(); + } key_type insert_in_core(mapped_type const &obj_ptr) override { - auto const key = ::bonded_ia_params.insert(obj_ptr->bonded_ia()); + key_type key{}; + context()->parallel_try_catch( + [&]() { key = m_handle->insert(obj_ptr->bonded_ia()); }); m_bonds[key] = std::move(obj_ptr); return key; } void insert_in_core(key_type const &key, mapped_type const &obj_ptr) override { - ::bonded_ia_params.insert(key, obj_ptr->bonded_ia()); + context()->parallel_try_catch( + [&]() { m_handle->insert(key, obj_ptr->bonded_ia()); }); m_bonds[key] = std::move(obj_ptr); } void erase_in_core(key_type const &key) override { - ::bonded_ia_params.erase(key); + m_handle->erase(key); m_bonds.erase(key); } @@ -80,29 +96,29 @@ class BondedInteractions : public ObjectMap { Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { if (name == "get_size") { - return {static_cast(::bonded_ia_params.size())}; + return {static_cast(m_handle->size())}; } if (name == "get_bond_ids") { std::vector bond_ids; - for (auto const &kv : ::bonded_ia_params) + for (auto const &kv : *m_handle) bond_ids.push_back(kv.first); return bond_ids; } if (name == "has_bond") { auto const bond_id = get_key(params.at("bond_id")); - return {m_bonds.count(bond_id) != 0}; + return {m_bonds.contains(bond_id)}; } if (name == "get_bond") { auto const bond_id = get_key(params.at("bond_id")); // core and script interface must agree - assert(m_bonds.count(bond_id) == ::bonded_ia_params.count(bond_id)); + assert(m_bonds.count(bond_id) == m_handle->count(bond_id)); if (not context()->is_head_node()) return {}; // bond must exist - if (m_bonds.count(bond_id) == 0) { + if (not m_bonds.contains(bond_id)) { throw std::out_of_range("The bond with id " + std::to_string(bond_id) + " is not yet defined."); } @@ -111,11 +127,12 @@ class BondedInteractions : public ObjectMap { if (name == "get_zero_based_type") { auto const bond_id = get_key(params.at("bond_id")); - return ::bonded_ia_params.get_zero_based_type(bond_id); + return m_handle->get_zero_based_type(bond_id); } - return ObjectMap::do_call_method(name, params); + return Base::do_call_method(name, params); } }; + } // namespace Interactions } // namespace ScriptInterface diff --git a/src/script_interface/interactions/NonBondedInteraction.hpp b/src/script_interface/interactions/NonBondedInteraction.hpp index a93ae994b1f..6988412c615 100644 --- a/src/script_interface/interactions/NonBondedInteraction.hpp +++ b/src/script_interface/interactions/NonBondedInteraction.hpp @@ -22,8 +22,7 @@ * structs from the core are defined here. */ -#ifndef SCRIPT_INTERFACE_INTERACTIONS_NONBONDED_INTERACTION_HPP -#define SCRIPT_INTERFACE_INTERACTIONS_NONBONDED_INTERACTION_HPP +#pragma once #include "script_interface/ScriptInterface.hpp" #include "script_interface/auto_parameters/AutoParameters.hpp" @@ -46,12 +45,11 @@ namespace ScriptInterface { namespace Interactions { +class NonBondedInteractionHandle; + template class InteractionPotentialInterface : public AutoParameters> { - /** @brief Particle type pair. */ - std::array m_types = {-1, -1}; - public: using CoreInteraction = CoreIA; @@ -61,7 +59,13 @@ class InteractionPotentialInterface using BaseClass::get_valid_parameters; /** @brief Managed object. */ - std::shared_ptr m_ia_si; + std::shared_ptr m_handle; + /** @brief Handle to the container whose members have to be synchronized. */ + std::weak_ptr<::IA_parameters> m_core_struct; + /** @brief Handle to the interface used to synchronize data members. */ + std::weak_ptr m_si_struct; + /** @brief Callback to notify changes to the interaction range. */ + std::weak_ptr> m_notify_non_bonded_ia_change; /** @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. */ @@ -74,7 +78,7 @@ class InteractionPotentialInterface template auto make_autoparameter(T CoreInteraction::*ptr, char const *name) { return AutoParameter{name, AutoParameter::read_only, - [this, ptr]() { return m_ia_si.get()->*ptr; }}; + [this, ptr]() { return m_handle.get()->*ptr; }}; } private: @@ -101,54 +105,26 @@ class InteractionPotentialInterface check_valid_parameters(params); make_new_instance(params); }); - if (m_types[0] != -1) { - copy_si_to_core(); - System::get_system().on_non_bonded_ia_change(); - } + // copying the new value to the core may queue a runtime error message, + // but we can't detect it to roll back to the last valid state + update_core(); return {}; } if (name == "deactivate") { - m_ia_si = std::make_shared(); - if (m_types[0] != -1) { - copy_si_to_core(); - System::get_system().on_non_bonded_ia_change(); - } - return {}; - } - if (name == "is_registered") { - return m_types[0] != -1; - } - if (name == "bind_types") { - auto types = get_value>(params, "_types"); - if (types[0] > types[1]) { - std::swap(types[0], types[1]); - } - if (m_types[0] == -1 or - (m_types[0] == types[0] and m_types[1] == types[1])) { - m_types[0] = types[0]; - m_types[1] = types[1]; - } else { - context()->parallel_try_catch([this]() { - throw std::runtime_error( - "Non-bonded interaction is already bound to interaction pair [" + - std::to_string(m_types[0]) + ", " + std::to_string(m_types[1]) + - "]"); - }); - } + m_handle = std::make_shared(); + update_core(get_value_or(params, "notify", true)); return {}; } return {}; } void do_construct(VariantMap const ¶ms) final { - if (params.count("_types") != 0) { - do_call_method("bind_types", params); - m_ia_si = std::make_shared(); - copy_core_to_si(); + if (params.empty()) { + m_handle = std::make_shared(); } else { if (std::abs(get_value(params, inactive_parameter()) - inactive_cutoff()) < 1e-9) { - m_ia_si = std::make_shared(); + m_handle = std::make_shared(); } else { context()->parallel_try_catch([this, ¶ms]() { check_valid_parameters(params); @@ -158,17 +134,13 @@ class InteractionPotentialInterface } } - void copy_si_to_core() { - assert(m_ia_si != nullptr); - auto &core_ias = *System::get_system().nonbonded_ias; - core_ias.get_ia_param(m_types[0], m_types[1]).*get_ptr_offset() = *m_ia_si; + void attach(std::weak_ptr si_struct, + std::weak_ptr<::IA_parameters> core_struct) { + m_si_struct = si_struct; + m_core_struct = core_struct; } - void copy_core_to_si() { - assert(m_ia_si != nullptr); - auto const &core_ias = *System::get_system().nonbonded_ias; - *m_ia_si = core_ias.get_ia_param(m_types[0], m_types[1]).*get_ptr_offset(); - } + void update_core(bool notify = true); }; #ifdef WCA @@ -191,7 +163,7 @@ class InteractionWCA : public InteractionPotentialInterface<::WCA_Parameters> { double inactive_cutoff() const override { return 0.; } void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = make_shared_from_args( + m_handle = make_shared_from_args( params, "epsilon", "sigma"); } @@ -199,7 +171,7 @@ class InteractionWCA : public InteractionPotentialInterface<::WCA_Parameters> { Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { if (name == "get_cutoff") { - return m_ia_si.get()->cut; + return m_handle.get()->cut; } return InteractionPotentialInterface::do_call_method( name, params); @@ -237,11 +209,11 @@ class InteractionLJ : public InteractionPotentialInterface<::LJ_Parameters> { } new_params["shift"] = 0.; } - m_ia_si = make_shared_from_args( + m_handle = 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(); + m_handle->shift = m_handle->get_auto_shift(); } } }; @@ -285,19 +257,19 @@ class InteractionLJGen } new_params["shift"] = 0.; } - m_ia_si = make_shared_from_args( + double, double, double, double>( 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(); + m_handle->shift = m_handle->get_auto_shift(); } } }; @@ -323,7 +295,7 @@ class InteractionLJcos private: void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = + m_handle = make_shared_from_args( params, "epsilon", "sigma", "cutoff", "offset"); } @@ -353,7 +325,7 @@ class InteractionLJcos2 double inactive_cutoff() const override { return 0.; } void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = + m_handle = make_shared_from_args( params, "epsilon", "sigma", "offset", "width"); } @@ -362,7 +334,7 @@ class InteractionLJcos2 Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { if (name == "get_cutoff") { - return m_ia_si.get()->cut; + return m_handle.get()->cut; } return InteractionPotentialInterface::do_call_method( name, params); @@ -390,7 +362,7 @@ class InteractionHertzian std::string inactive_parameter() const override { return "sig"; } void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = make_shared_from_args( + m_handle = make_shared_from_args( params, "eps", "sig"); } }; @@ -415,7 +387,7 @@ class InteractionGaussian private: void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = make_shared_from_args( + m_handle = make_shared_from_args( params, "eps", "sig", "cutoff"); } }; @@ -443,8 +415,8 @@ class InteractionBMHTF private: void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = make_shared_from_args( + m_handle = make_shared_from_args( params, "a", "b", "c", "d", "sig", "cutoff"); } }; @@ -470,7 +442,7 @@ class InteractionMorse private: void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = + m_handle = make_shared_from_args( params, "eps", "alpha", "rmin", "cutoff"); } @@ -500,8 +472,8 @@ class InteractionBuckingham private: void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = make_shared_from_args( + m_handle = make_shared_from_args( params, "a", "b", "c", "d", "cutoff", "discont", "shift"); } }; @@ -527,7 +499,7 @@ class InteractionSoftSphere private: void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = + m_handle = make_shared_from_args( params, "a", "n", "cutoff", "offset"); } @@ -551,7 +523,7 @@ class InteractionHat : public InteractionPotentialInterface<::Hat_Parameters> { private: void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = make_shared_from_args( + m_handle = make_shared_from_args( params, "F_max", "cutoff"); } }; @@ -582,8 +554,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( + m_handle = make_shared_from_args( params, "eps", "sig", "cut", "k1", "k2", "mu", "nu"); } }; @@ -611,8 +583,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>( + m_handle = make_shared_from_args, std::vector>( params, "min", "max", "force", "energy"); } @@ -620,7 +592,7 @@ class InteractionTabulated Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { if (name == "get_cutoff") { - return m_ia_si.get()->cutoff(); + return m_handle.get()->cutoff(); } return InteractionPotentialInterface::do_call_method( name, params); @@ -639,19 +611,19 @@ class InteractionDPD : public InteractionPotentialInterface<::DPD_Parameters> { InteractionDPD() { add_parameters({ {"weight_function", AutoParameter::read_only, - [this]() { return m_ia_si.get()->radial.wf; }}, + [this]() { return m_handle.get()->radial.wf; }}, {"gamma", AutoParameter::read_only, - [this]() { return m_ia_si.get()->radial.gamma; }}, + [this]() { return m_handle.get()->radial.gamma; }}, {"k", AutoParameter::read_only, - [this]() { return m_ia_si.get()->radial.k; }}, + [this]() { return m_handle.get()->radial.k; }}, {"r_cut", AutoParameter::read_only, - [this]() { return m_ia_si.get()->radial.cutoff; }}, + [this]() { return m_handle.get()->radial.cutoff; }}, {"trans_weight_function", AutoParameter::read_only, - [this]() { return m_ia_si.get()->trans.wf; }}, + [this]() { return m_handle.get()->trans.wf; }}, {"trans_gamma", AutoParameter::read_only, - [this]() { return m_ia_si.get()->trans.gamma; }}, + [this]() { return m_handle.get()->trans.gamma; }}, {"trans_r_cut", AutoParameter::read_only, - [this]() { return m_ia_si.get()->trans.cutoff; }}, + [this]() { return m_handle.get()->trans.cutoff; }}, }); std::ignore = get_ptr_offset(); // for code coverage } @@ -660,15 +632,15 @@ class InteractionDPD : public InteractionPotentialInterface<::DPD_Parameters> { std::string inactive_parameter() const override { return "r_cut"; } void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = make_shared_from_args( + m_handle = make_shared_from_args( params, "gamma", "k", "r_cut", "weight_function", "trans_gamma", "trans_r_cut", "trans_weight_function"); - if (m_ia_si->radial.wf != 0 and m_ia_si->radial.wf != 1) { + if (m_handle->radial.wf != 0 and m_handle->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) { + if (m_handle->trans.wf != 0 and m_handle->trans.wf != 1) { throw std::domain_error( "DPDInteraction parameter 'trans_weight_function' must be 0 or 1"); } @@ -697,7 +669,7 @@ class InteractionThole double inactive_cutoff() const override { return 0.; } void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = make_shared_from_args( + m_handle = make_shared_from_args( params, "scaling_coeff", "q1q2"); } }; @@ -725,8 +697,8 @@ class InteractionSmoothStep private: void make_new_instance(VariantMap const ¶ms) override { - m_ia_si = make_shared_from_args( + m_handle = make_shared_from_args( params, "eps", "sig", "cutoff", "d", "n", "k0"); } }; @@ -734,8 +706,9 @@ class InteractionSmoothStep class NonBondedInteractionHandle : public AutoParameters { - std::array m_types = {-1, -1}; - std::shared_ptr<::IA_parameters> m_interaction; + std::shared_ptr<::IA_parameters> m_handle; + std::shared_ptr m_self; + std::weak_ptr> m_notify_cutoff_change; #ifdef WCA std::shared_ptr m_wca; #endif @@ -788,180 +761,146 @@ class NonBondedInteractionHandle std::shared_ptr m_smooth_step; #endif - template - auto make_autoparameter(std::shared_ptr &member, const char *key) const { - auto const setter = [this, &member](Variant const &v) { - member = get_value>(v); - if (m_types[0] != -1) { - auto const types = Variant{std::vector{{m_types[0], m_types[1]}}}; - member->do_call_method("bind_types", VariantMap{{"_types", types}}); - member->copy_si_to_core(); - System::get_system().on_non_bonded_ia_change(); - } - }; - return AutoParameter{key, setter, [&member]() { return member; }}; - } - public: NonBondedInteractionHandle() { - add_parameters({ -#ifdef WCA - make_autoparameter(m_wca, "wca"), -#endif -#ifdef LENNARD_JONES - make_autoparameter(m_lj, "lennard_jones"), -#endif -#ifdef LENNARD_JONES_GENERIC - make_autoparameter(m_ljgen, "generic_lennard_jones"), -#endif -#ifdef LJCOS - make_autoparameter(m_ljcos, "lennard_jones_cos"), -#endif -#ifdef LJCOS2 - make_autoparameter(m_ljcos2, "lennard_jones_cos2"), -#endif -#ifdef HERTZIAN - make_autoparameter(m_hertzian, "hertzian"), -#endif -#ifdef GAUSSIAN - make_autoparameter(m_gaussian, "gaussian"), -#endif -#ifdef BMHTF_NACL - make_autoparameter(m_bmhtf, "bmhtf"), -#endif -#ifdef MORSE - make_autoparameter(m_morse, "morse"), -#endif -#ifdef BUCKINGHAM - make_autoparameter(m_buckingham, "buckingham"), -#endif -#ifdef SOFT_SPHERE - make_autoparameter(m_soft_sphere, "soft_sphere"), -#endif -#ifdef HAT - make_autoparameter(m_hat, "hat"), -#endif -#ifdef GAY_BERNE - make_autoparameter(m_gay_berne, "gay_berne"), -#endif -#ifdef TABULATED - make_autoparameter(m_tabulated, "tabulated"), -#endif -#ifdef DPD - make_autoparameter(m_dpd, "dpd"), -#endif -#ifdef THOLE - make_autoparameter(m_thole, "thole"), -#endif -#ifdef SMOOTH_STEP - make_autoparameter(m_smooth_step, "smooth_step"), -#endif + m_self = std::make_shared(this); + std::vector params; + apply([this, ¶ms](std::shared_ptr &member, + std::string const &name, + std::string const &) { + auto const setter = [this, &member](Variant const &v) { + member = get_value>(v); + member->attach(m_self, m_handle); + // copying the new value to the core may queue a runtime error message, + // but we can't detect it to roll back to the last valid state + member->update_core(); + }; + params.emplace_back(name.c_str(), setter, [&member]() { return member; }); }); - } - -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(); - } + add_parameters(std::move(params)); } public: Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { - assert(params.empty()); - if (name == "get_types") { - return std::vector{{m_types[0], m_types[1]}}; + if (name == "reset") { + if (not context()->is_head_node()) { + return {}; + } + auto const new_params = VariantMap{{"notify", false}}; + apply([&new_params](auto &so, std::string const &, std::string const &) { + so->call_method("deactivate", new_params); + }); + if (get_value_or(params, "notify", true)) { + call_method("on_non_bonded_ia_change", {}); + } + return {}; + } + if (name == "on_non_bonded_ia_change") { + on_non_bonded_ia_change(); + return {}; } return {}; } void do_construct(VariantMap const ¶ms) override { - assert(params.count("_types") != 0); - auto &nonbonded_ias = *System::get_system().nonbonded_ias; - 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]); - nonbonded_ias.make_particle_type_exist(m_types[1]); - // create interface objects - m_interaction = - nonbonded_ias.get_ia_param_ref_counted(m_types[0], m_types[1]); + m_handle = std::make_shared<::IA_parameters>(); + if (not context()->is_head_node()) { + return; + } + apply([this, ¶ms](std::shared_ptr &member, + std::string const &name, + std::string const &so_name) { + auto so = (params.contains(name)) + ? get_value>(params.at(name)) + : std::dynamic_pointer_cast( + context()->make_shared(so_name, {})); + set_parameter(name, so); + }); + } + + void attach( + std::function const &)> cb_register, + std::weak_ptr> cb_notify_cutoff_change) { + cb_register(m_handle); + m_notify_cutoff_change = cb_notify_cutoff_change; + } + +private: + void apply(auto const &&fun) { #ifdef WCA - set_member(m_wca, "wca", "Interactions::InteractionWCA", params); + fun(m_wca, "wca", "Interactions::InteractionWCA"); #endif #ifdef LENNARD_JONES - set_member(m_lj, "lennard_jones", "Interactions::InteractionLJ", params); + fun(m_lj, "lennard_jones", "Interactions::InteractionLJ"); #endif #ifdef LENNARD_JONES_GENERIC - set_member(m_ljgen, "generic_lennard_jones", - "Interactions::InteractionLJGen", params); + fun(m_ljgen, "generic_lennard_jones", "Interactions::InteractionLJGen"); #endif #ifdef LJCOS - set_member(m_ljcos, "lennard_jones_cos", "Interactions::InteractionLJcos", - params); + fun(m_ljcos, "lennard_jones_cos", "Interactions::InteractionLJcos"); #endif #ifdef LJCOS2 - set_member(m_ljcos2, "lennard_jones_cos2", - "Interactions::InteractionLJcos2", params); + fun(m_ljcos2, "lennard_jones_cos2", "Interactions::InteractionLJcos2"); #endif #ifdef HERTZIAN - set_member(m_hertzian, "hertzian", "Interactions::InteractionHertzian", - params); + fun(m_hertzian, "hertzian", "Interactions::InteractionHertzian"); #endif #ifdef GAUSSIAN - set_member(m_gaussian, "gaussian", "Interactions::InteractionGaussian", - params); + fun(m_gaussian, "gaussian", "Interactions::InteractionGaussian"); #endif #ifdef BMHTF_NACL - set_member(m_bmhtf, "bmhtf", "Interactions::InteractionBMHTF", params); + fun(m_bmhtf, "bmhtf", "Interactions::InteractionBMHTF"); #endif #ifdef MORSE - set_member(m_morse, "morse", "Interactions::InteractionMorse", params); + fun(m_morse, "morse", "Interactions::InteractionMorse"); #endif #ifdef BUCKINGHAM - set_member(m_buckingham, "buckingham", - "Interactions::InteractionBuckingham", params); + fun(m_buckingham, "buckingham", "Interactions::InteractionBuckingham"); #endif #ifdef SOFT_SPHERE - set_member(m_soft_sphere, "soft_sphere", - "Interactions::InteractionSoftSphere", params); + fun(m_soft_sphere, "soft_sphere", "Interactions::InteractionSoftSphere"); #endif #ifdef HAT - set_member(m_hat, "hat", "Interactions::InteractionHat", params); + fun(m_hat, "hat", "Interactions::InteractionHat"); #endif #ifdef GAY_BERNE - set_member(m_gay_berne, "gay_berne", "Interactions::InteractionGayBerne", - params); + fun(m_gay_berne, "gay_berne", "Interactions::InteractionGayBerne"); #endif #ifdef TABULATED - set_member(m_tabulated, "tabulated", "Interactions::InteractionTabulated", - params); + fun(m_tabulated, "tabulated", "Interactions::InteractionTabulated"); #endif #ifdef DPD - set_member(m_dpd, "dpd", "Interactions::InteractionDPD", params); + fun(m_dpd, "dpd", "Interactions::InteractionDPD"); #endif #ifdef THOLE - set_member(m_thole, "thole", "Interactions::InteractionThole", params); + fun(m_thole, "thole", "Interactions::InteractionThole"); #endif #ifdef SMOOTH_STEP - set_member(m_smooth_step, "smooth_step", - "Interactions::InteractionSmoothStep", params); + fun(m_smooth_step, "smooth_step", "Interactions::InteractionSmoothStep"); #endif } - auto get_ia() const { return m_interaction; } +public: + void on_non_bonded_ia_change() { + if (auto callback = m_notify_cutoff_change.lock()) { + (*callback)(); + } + } }; +template +void InteractionPotentialInterface::update_core(bool notify) { + assert(m_handle); + if (auto core_struct = m_core_struct.lock()) { + core_struct.get()->*get_ptr_offset() = *m_handle; + if (notify) { + if (auto si_struct = m_si_struct.lock()) { + (**si_struct).on_non_bonded_ia_change(); + } + } + } +} + } // namespace Interactions } // namespace ScriptInterface - -#endif diff --git a/src/script_interface/interactions/NonBondedInteractions.hpp b/src/script_interface/interactions/NonBondedInteractions.hpp index 772e8e42429..0b0027c6ce9 100644 --- a/src/script_interface/interactions/NonBondedInteractions.hpp +++ b/src/script_interface/interactions/NonBondedInteractions.hpp @@ -26,6 +26,9 @@ #include "script_interface/ObjectMap.hpp" #include "script_interface/ScriptInterface.hpp" +#include "script_interface/system/Leaf.hpp" + +#include #include #include @@ -38,7 +41,7 @@ namespace ScriptInterface { namespace Interactions { -class NonBondedInteractions : public ObjectHandle { +class NonBondedInteractions : public System::Leaf { using container_type = std::unordered_map>; @@ -49,38 +52,25 @@ class NonBondedInteractions : public ObjectHandle { private: container_type m_nonbonded_ia_params; - decltype(System::System::nonbonded_ias) m_nonbonded_ias; - - auto make_interaction(int i, int j) { - assert(j <= i); - auto const types = std::vector{{i, j}}; - return std::dynamic_pointer_cast( - context()->make_shared_local("Interactions::NonBondedInteractionHandle", - {{"_types", Variant{types}}})); - } + std::shared_ptr<::InteractionsNonBonded> m_handle; + std::shared_ptr> m_notify_cutoff_change; - void reset() { - auto const max_type = m_nonbonded_ias->get_max_seen_particle_type(); - for (int i = 0; i <= max_type; i++) { - for (int j = 0; j <= i; j++) { - auto const key = m_nonbonded_ias->get_ia_param_key(i, j); - m_nonbonded_ias->set_ia_param(i, j, - std::make_shared<::IA_parameters>()); - m_nonbonded_ia_params[key] = make_interaction(i, j); - } - } - System::get_system().on_non_bonded_ia_change(); + void do_construct(VariantMap const ¶ms) override { + m_handle = std::make_shared<::InteractionsNonBonded>(); + m_notify_cutoff_change = std::make_shared>([]() {}); } - void do_construct(VariantMap const &) override { - m_nonbonded_ias = System::get_system().nonbonded_ias; - auto const max_type = m_nonbonded_ias->get_max_seen_particle_type(); - for (int i = 0; i <= max_type; i++) { - for (int j = 0; j <= i; j++) { - auto const key = m_nonbonded_ias->get_ia_param_key(i, j); - m_nonbonded_ia_params[key] = make_interaction(i, j); + void on_bind_system(::System::System &system) override { + auto const max_type = m_handle->get_max_seen_particle_type(); + system.nonbonded_ias = m_handle; + m_handle->make_particle_type_exist(max_type); + m_handle->bind_system(m_system.lock()); + m_handle->on_non_bonded_ia_change(); + *m_notify_cutoff_change = [this]() { + if (m_handle and not m_system.expired()) { + m_handle->on_non_bonded_ia_change(); } - } + }; } std::pair get_key(Variant const &key) const { @@ -89,7 +79,7 @@ class NonBondedInteractions : public ObjectHandle { if (types.size() != 2ul or types[0] < 0 or types[1] < 0) { throw Exception("need two particle types"); } - return {std::max(types[0], types[1]), std::min(types[0], types[1])}; + return {std::min(types[0], types[1]), std::max(types[0], types[1])}; } catch (...) { if (context()->is_head_node()) { throw std::invalid_argument( @@ -102,31 +92,104 @@ class NonBondedInteractions : public ObjectHandle { protected: Variant do_call_method(std::string const &method, VariantMap const ¶ms) override { - if (method == "get_n_types") { - return Variant{m_nonbonded_ias->get_max_seen_particle_type() + 1}; - } if (method == "reset") { - reset(); + if (not context()->is_head_node()) { + return {}; + } + auto const max_type = m_handle->get_max_seen_particle_type(); + auto const obj_params = VariantMap{{"notify", false}}; + for (int i = 0; i <= max_type; i++) { + for (int j = 0; j <= i; j++) { + auto const key = m_handle->get_ia_param_key(i, j); + if (m_nonbonded_ia_params.contains(key)) { + m_nonbonded_ia_params.at(key)->call_method("reset", obj_params); + } + } + } + get_system().on_non_bonded_ia_change(); return {}; } - if (method == "insert") { + if (method == "get_handle") { + auto ctx = context(); auto const [type_min, type_max] = get_key(params.at("key")); - m_nonbonded_ias->make_particle_type_exist(type_max); - auto const key = m_nonbonded_ias->get_ia_param_key(type_min, type_max); - auto obj_ptr = get_value>( - params.at("object")); - m_nonbonded_ias->set_ia_param(type_min, type_max, obj_ptr->get_ia()); - m_nonbonded_ia_params[key] = obj_ptr; - System::get_system().on_non_bonded_ia_change(); + if (type_max > m_handle->get_max_seen_particle_type()) { + m_handle->make_particle_type_exist(type_max); + } + if (not ctx->is_head_node()) { + return {}; + } + auto const key = m_handle->get_ia_param_key(type_min, type_max); + if (m_nonbonded_ia_params.contains(key)) { + return m_nonbonded_ia_params.at(key); + } + auto so = std::dynamic_pointer_cast( + ctx->make_shared("Interactions::NonBondedInteractionHandle", {})); + m_nonbonded_ia_params[key] = so; + call_method("internal_attach", {{"key", params.at("key")}, {"obj", so}}); + return so; + } + if (method == "internal_set_max_type") { + m_handle->make_particle_type_exist(get_value(params, "max_type")); return {}; } - if (method == "check_key") { - static_cast(get_key(params.at("key"))); + if (method == "internal_attach") { + auto so = std::dynamic_pointer_cast( + get_value(params, "obj")); + auto const [i, j] = get_key(params.at("key")); + auto const cb_register = + [this, i, j](std::shared_ptr<::IA_parameters> const &core_ia) { + m_handle->set_ia_param(i, j, core_ia); + }; + so->attach(cb_register, m_notify_cutoff_change); return {}; } return {}; } + + std::string get_internal_state() const override { + auto const max_type = m_handle->get_max_seen_particle_type(); + std::vector object_states; + object_states.emplace_back(Utils::pack(max_type)); + for (int i = 0; i <= max_type; i++) { + for (int j = 0; j <= i; j++) { + auto const key = m_handle->get_ia_param_key(i, j); + if (m_nonbonded_ia_params.contains(key)) { + object_states.emplace_back( + m_nonbonded_ia_params.at(key)->serialize()); + } else { + object_states.emplace_back(""); + } + } + } + + return Utils::pack(object_states); + } + + void set_internal_state(std::string const &state) override { + auto const object_states = Utils::unpack>(state); + auto const max_type = Utils::unpack(object_states.front()); + call_method("internal_set_max_type", {{"max_type", max_type}}); + auto const end = object_states.end(); + auto it = object_states.begin() + 1; + for (int i = 0; i <= max_type; i++) { + for (int j = 0; j <= i; j++) { + auto const key = m_handle->get_ia_param_key(i, j); + auto const &buffer = *it; + if (not buffer.empty()) { + auto so = std::dynamic_pointer_cast( + ObjectHandle::deserialize(buffer, *context())); + m_nonbonded_ia_params[key] = so; + call_method("internal_attach", + {{"key", std::vector{{j, i}}}, {"obj", so}}); + } + ++it; + if (it == end) { + break; + } + } + } + } }; } // namespace Interactions diff --git a/src/script_interface/mpiio/mpiio.hpp b/src/script_interface/mpiio/mpiio.hpp index fd37189ee67..b520ad3b404 100644 --- a/src/script_interface/mpiio/mpiio.hpp +++ b/src/script_interface/mpiio/mpiio.hpp @@ -19,28 +19,36 @@ * along with this program. If not, see . */ -#ifndef ESPRESSO_SCRIPT_INTERFACE_MPIIO_MPIIO_HPP -#define ESPRESSO_SCRIPT_INTERFACE_MPIIO_MPIIO_HPP +#pragma once +#include "script_interface/ObjectHandle.hpp" #include "script_interface/ScriptInterface.hpp" -#include "script_interface/auto_parameters/AutoParameters.hpp" #include "script_interface/get_value.hpp" +#include "script_interface/system/System.hpp" +#include "core/bonded_interactions/bonded_interaction_data.hpp" #include "core/cell_system/CellStructure.hpp" #include "core/io/mpiio/mpiio.hpp" #include "core/system/System.hpp" +#include #include namespace ScriptInterface { namespace MPIIO { -class MPIIOScript : public AutoParameters { +class MPIIOScript : public ObjectHandle { + std::weak_ptr m_system; + std::shared_ptr m_buffers; + public: - MPIIOScript() { add_parameters({}); } + void do_construct(VariantMap const ¶meters) override { + m_system = get_value>(parameters, "system"); + m_buffers = std::make_shared(); + } - Variant do_call_method(const std::string &name, - const VariantMap ¶meters) override { + Variant do_call_method(std::string const &name, + VariantMap const ¶meters) override { auto prefix = get_value(parameters.at("prefix")); auto pos = get_value(parameters.at("pos")); @@ -54,12 +62,18 @@ class MPIIOScript : public AutoParameters { ((bnd) ? Mpiio::MPIIO_OUT_BND : Mpiio::MPIIO_OUT_NON); if (name == "write") { - auto const &system = ::System::get_system(); + auto const system_si = m_system.lock(); + auto &system = system_si->get_system(); auto &cell_structure = *system.cell_structure; - Mpiio::mpi_mpiio_common_write(prefix, fields, - cell_structure.local_particles()); + auto &bonded_ias = *system.bonded_ias; + Mpiio::mpi_mpiio_common_write(prefix, fields, bonded_ias, + cell_structure.local_particles(), + *m_buffers); } else if (name == "read") { - Mpiio::mpi_mpiio_common_read(prefix, fields); + auto const system_si = m_system.lock(); + auto &system = system_si->get_system(); + auto &cell_structure = *system.cell_structure; + Mpiio::mpi_mpiio_common_read(prefix, fields, cell_structure); } return {}; @@ -68,5 +82,3 @@ class MPIIOScript : public AutoParameters { } // namespace MPIIO } // namespace ScriptInterface - -#endif diff --git a/src/script_interface/particle_data/ParticleHandle.cpp b/src/script_interface/particle_data/ParticleHandle.cpp index c253831b99c..a6b3089a9d6 100644 --- a/src/script_interface/particle_data/ParticleHandle.cpp +++ b/src/script_interface/particle_data/ParticleHandle.cpp @@ -22,7 +22,10 @@ #include "ParticleHandle.hpp" #include "script_interface/Variant.hpp" +#include "script_interface/cell_system/CellSystem.hpp" #include "script_interface/get_value.hpp" +#include "script_interface/interactions/BondedInteractions.hpp" +#include "script_interface/system/System.hpp" #include "core/BoxGeometry.hpp" #include "core/PropagationMode.hpp" @@ -116,12 +119,11 @@ static auto get_gamma_safe(Variant const &value) { } #endif // THERMOSTAT_PER_PARTICLE -static auto get_real_particle(boost::mpi::communicator const &comm, int p_id) { +static auto get_real_particle(boost::mpi::communicator const &comm, int p_id, + ::CellStructure &cell_structure) { if (p_id < 0) { throw std::domain_error("Invalid particle id: " + std::to_string(p_id)); } - auto const &system = ::System::get_system(); - auto &cell_structure = *system.cell_structure; auto ptr = cell_structure.get_local_particle(p_id); if (ptr != nullptr and ptr->is_ghost()) { ptr = nullptr; @@ -137,8 +139,11 @@ static auto get_real_particle(boost::mpi::communicator const &comm, int p_id) { template T ParticleHandle::get_particle_property(F const &fun) const { + auto cell_structure_si = get_cell_structure(); + auto &cell_structure = cell_structure_si->get_cell_structure(); auto const &comm = context()->get_comm(); - auto const ptr = const_cast(get_real_particle(comm, m_pid)); + auto const ptr = const_cast( + get_real_particle(comm, m_pid, cell_structure)); std::optional ret; if (ptr == nullptr) { ret = {}; @@ -157,12 +162,14 @@ T ParticleHandle::get_particle_property(T const &(Particle::*getter)() template void ParticleHandle::set_particle_property(F const &fun) const { + auto cell_structure_si = get_cell_structure(); + auto &cell_structure = cell_structure_si->get_cell_structure(); auto const &comm = context()->get_comm(); - auto const ptr = get_real_particle(comm, m_pid); + auto const ptr = get_real_particle(comm, m_pid, cell_structure); if (ptr != nullptr) { fun(*ptr); } - System::get_system().on_particle_change(); + get_system()->on_particle_change(); } template @@ -188,7 +195,7 @@ ParticleHandle::ParticleHandle() { throw std::domain_error( error_msg("type", "must be an integer >= 0")); } - System::get_system().nonbonded_ias->make_particle_type_exist(new_type); + get_system()->nonbonded_ias->make_particle_type_exist(new_type); on_particle_type_change(m_pid, old_type, new_type); set_particle_property(&Particle::type, value); }, @@ -203,7 +210,7 @@ ParticleHandle::ParticleHandle() { auto const p = get_particle_data(m_pid); auto const pos = p.pos(); auto const image_box = p.image_box(); - return System::get_system().box_geo->unfolded_position(pos, image_box); + return get_system()->box_geo->unfolded_position(pos, image_box); }}, {"v", [this](Variant const &value) { @@ -387,7 +394,7 @@ ParticleHandle::ParticleHandle() { #endif // THERMOSTAT_PER_PARTICLE {"pos_folded", AutoParameter::read_only, [this]() { - auto const &box_geo = *System::get_system().box_geo; + auto const &box_geo = *get_system()->box_geo; return box_geo.folded_position(get_particle_data(m_pid).pos()); }}, {"lees_edwards_offset", @@ -399,7 +406,7 @@ ParticleHandle::ParticleHandle() { [this]() { return get_particle_data(m_pid).lees_edwards_flag(); }}, {"image_box", AutoParameter::read_only, [this]() { - auto const &box_geo = *System::get_system().box_geo; + auto const &box_geo = *get_system()->box_geo; auto const p = get_particle_data(m_pid); return box_geo.folded_image_box(p.pos(), p.image_box()); }}, @@ -497,10 +504,10 @@ ParticleHandle::ParticleHandle() { * @brief Locally add an exclusion to a particle. * @param pid1 the identity of the first exclusion partner * @param pid2 the identity of the second exclusion partner + * @param cell_structure the cell structure */ -static void local_add_exclusion(int pid1, int pid2) { - auto const &system = ::System::get_system(); - auto &cell_structure = *system.cell_structure; +static void local_add_exclusion(int pid1, int pid2, + ::CellStructure &cell_structure) { if (auto p1 = cell_structure.get_local_particle(pid1)) { add_exclusion(*p1, pid2); } @@ -513,10 +520,10 @@ static void local_add_exclusion(int pid1, int pid2) { * @brief Locally remove an exclusion to a particle. * @param pid1 the identity of the first exclusion partner * @param pid2 the identity of the second exclusion partner + * @param cell_structure the cell structure */ -static void local_remove_exclusion(int pid1, int pid2) { - auto const &system = ::System::get_system(); - auto &cell_structure = *system.cell_structure; +static void local_remove_exclusion(int pid1, int pid2, + ::CellStructure &cell_structure) { if (auto p1 = cell_structure.get_local_particle(pid1)) { delete_exclusion(*p1, pid2); } @@ -531,8 +538,10 @@ void ParticleHandle::particle_exclusion_sanity_checks(int pid1, throw std::runtime_error("Particles cannot exclude themselves (id " + std::to_string(pid1) + ")"); } - std::ignore = get_real_particle(context()->get_comm(), pid1); - std::ignore = get_real_particle(context()->get_comm(), pid2); + auto cell_structure_si = get_cell_structure(); + auto &cell_structure = cell_structure_si->get_cell_structure(); + std::ignore = get_real_particle(context()->get_comm(), pid1, cell_structure); + std::ignore = get_real_particle(context()->get_comm(), pid2, cell_structure); } #endif // EXCLUSIONS @@ -548,14 +557,20 @@ Variant ParticleHandle::do_call_method(std::string const &name, [&]() { do_set_parameter(param_name, value); }); return {}; } + if (name == "get_bond_by_id") { + if (not context()->is_head_node()) { + return {}; + } + return get_bonded_ias()->call_method("get_bond", params); + } if (name == "get_bonds_view") { if (not context()->is_head_node()) { return {}; } auto const bond_list = get_particle_data(m_pid).bonds(); - std::vector> bonds_flat; + std::vector> bonds_flat; for (auto const &&bond_view : bond_list) { - std::vector bond_flat; + std::vector bond_flat; bond_flat.emplace_back(bond_view.bond_id()); for (auto const pid : bond_view.partner_ids()) { bond_flat.emplace_back(pid); @@ -588,11 +603,14 @@ Variant ParticleHandle::do_call_method(std::string const &name, set_particle_property([&](Particle &p) { p.bonds().clear(); }); } else if (name == "is_valid_bond_id") { auto const bond_id = get_value(params, "bond_id"); - return ::bonded_ia_params.get_zero_based_type(bond_id) != 0; + return get_system()->bonded_ias->get_zero_based_type(bond_id) != 0; } if (name == "remove_particle") { context()->parallel_try_catch([&]() { - std::ignore = get_real_particle(context()->get_comm(), m_pid); + auto cell_structure_si = get_cell_structure(); + auto &cell_structure = cell_structure_si->get_cell_structure(); + std::ignore = + get_real_particle(context()->get_comm(), m_pid, cell_structure); remove_particle(m_pid); }); } else if (name == "is_virtual") { @@ -615,7 +633,7 @@ Variant ParticleHandle::do_call_method(std::string const &name, throw std::domain_error("Invalid particle id: " + std::to_string(other_pid)); } - auto const &system = ::System::get_system(); + auto const system = get_system(); /* note: this code can be rewritten as parallel code, but only with a call * to `cells_update_ghosts(DATA_PART_POSITION | DATA_PART_PROPERTIES)`, as * there is no guarantee the virtual site has visibility of the relative @@ -628,7 +646,7 @@ Variant ParticleHandle::do_call_method(std::string const &name, auto const &p_current = get_particle_data(m_pid); auto const &p_relate_to = get_particle_data(other_pid); auto const [quat, dist] = calculate_vs_relate_to_params( - p_current, p_relate_to, *system.box_geo, system.get_min_global_cut(), + p_current, p_relate_to, *system->box_geo, system->get_min_global_cut(), override_cutoff_check); set_parameter("vs_relative", Variant{std::vector{ {other_pid, dist, quat2vector(quat)}}}); @@ -639,23 +657,30 @@ Variant ParticleHandle::do_call_method(std::string const &name, #ifdef EXCLUSIONS } else if (name == "has_exclusion") { auto const other_pid = get_value(params, "pid"); - auto const p = get_real_particle(context()->get_comm(), m_pid); + auto cell_structure_si = get_cell_structure(); + auto &cell_structure = cell_structure_si->get_cell_structure(); + auto const p = + get_real_particle(context()->get_comm(), m_pid, cell_structure); if (p != nullptr) { return p->has_exclusion(other_pid); } } if (name == "add_exclusion") { auto const other_pid = get_value(params, "pid"); + auto cell_structure_si = get_cell_structure(); + auto &cell_structure = cell_structure_si->get_cell_structure(); context()->parallel_try_catch( [&]() { particle_exclusion_sanity_checks(m_pid, other_pid); }); - local_add_exclusion(m_pid, other_pid); - System::get_system().on_particle_change(); + local_add_exclusion(m_pid, other_pid, cell_structure); + get_system()->on_particle_change(); } else if (name == "del_exclusion") { auto const other_pid = get_value(params, "pid"); + auto cell_structure_si = get_cell_structure(); + auto &cell_structure = cell_structure_si->get_cell_structure(); context()->parallel_try_catch( [&]() { particle_exclusion_sanity_checks(m_pid, other_pid); }); - local_remove_exclusion(m_pid, other_pid); - System::get_system().on_particle_change(); + local_remove_exclusion(m_pid, other_pid, cell_structure); + get_system()->on_particle_change(); } else if (name == "set_exclusions") { std::vector exclusion_list; try { @@ -670,12 +695,14 @@ Variant ParticleHandle::do_call_method(std::string const &name, } }); set_particle_property([this, &exclusion_list](Particle &p) { + auto cell_structure_si = get_cell_structure(); + auto &cell_structure = cell_structure_si->get_cell_structure(); for (auto const pid : p.exclusions()) { - local_remove_exclusion(m_pid, pid); + local_remove_exclusion(m_pid, pid, cell_structure); } for (auto const pid : exclusion_list) { if (!p.has_exclusion(pid)) { - local_add_exclusion(m_pid, pid); + local_add_exclusion(m_pid, pid, cell_structure); } } }); @@ -729,7 +756,9 @@ static auto const contradicting_arguments_quat = std::vector< #endif // ROTATION void ParticleHandle::do_construct(VariantMap const ¶ms) { - auto const n_extra_args = params.size() - params.count("id"); + auto const n_extra_args = params.size() - params.count("id") - + params.count("__cell_structure") - + params.count("__bonded_ias"); m_pid = (params.contains("id")) ? get_value(params, "id") : get_maximal_particle_id() + 1; @@ -741,6 +770,17 @@ void ParticleHandle::do_construct(VariantMap const ¶ms) { } #endif + if (params.contains("__cell_structure")) { + auto so = get_value>( + params, "__cell_structure"); + so->configure(*this); + m_cell_structure = so; + } + if (params.contains("__bonded_ias")) { + m_bonded_ias = get_value>( + params, "__bonded_ias"); + } + // create a new particle if extra arguments were passed if (n_extra_args == 0) { return; @@ -749,8 +789,8 @@ void ParticleHandle::do_construct(VariantMap const ¶ms) { auto const pos = get_value(params, "pos"); context()->parallel_try_catch([&]() { particle_checks(m_pid, pos); - auto const &system = ::System::get_system(); - auto const &cell_structure = *system.cell_structure; + auto cell_structure_si = get_cell_structure(); + auto &cell_structure = cell_structure_si->get_cell_structure(); auto ptr = cell_structure.get_local_particle(m_pid); if (ptr != nullptr) { throw std::invalid_argument("Particle " + std::to_string(m_pid) + diff --git a/src/script_interface/particle_data/ParticleHandle.hpp b/src/script_interface/particle_data/ParticleHandle.hpp index 5c6bb460fca..ef73fb0f25c 100644 --- a/src/script_interface/particle_data/ParticleHandle.hpp +++ b/src/script_interface/particle_data/ParticleHandle.hpp @@ -21,16 +21,43 @@ #include "script_interface/ScriptInterface.hpp" #include "script_interface/auto_parameters/AutoParameters.hpp" +#include "script_interface/interactions/BondedInteractions.hpp" #include "core/Particle.hpp" +#include "core/system/System.hpp" +#include +#include +#include #include namespace ScriptInterface { +namespace CellSystem { +class CellSystem; +} namespace Particles { class ParticleHandle : public AutoParameters { + std::function cb_get_bond; int m_pid; + mutable std::weak_ptr m_cell_structure; + mutable std::weak_ptr m_bonded_ias; + mutable std::weak_ptr<::System::System> m_system; + auto get_cell_structure() const { + auto ptr = m_cell_structure.lock(); + assert(ptr != nullptr); + return ptr; + } + auto get_bonded_ias() const { + auto ptr = m_bonded_ias.lock(); + assert(ptr != nullptr); + return ptr; + } + auto get_system() const { + auto ptr = m_system.lock(); + assert(ptr != nullptr); + return ptr; + } template T get_particle_property(T const &(Particle::*getter)() const) const; @@ -52,6 +79,11 @@ class ParticleHandle : public AutoParameters { VariantMap const ¶ms) override; void do_construct(VariantMap const ¶ms) override; + + void attach(std::weak_ptr<::System::System> system) { + assert(m_system.expired()); + m_system = system; + } }; } // namespace Particles diff --git a/src/script_interface/particle_data/ParticleList.cpp b/src/script_interface/particle_data/ParticleList.cpp index e9b2ccd7a30..9bb11034303 100644 --- a/src/script_interface/particle_data/ParticleList.cpp +++ b/src/script_interface/particle_data/ParticleList.cpp @@ -19,9 +19,11 @@ #include "ParticleList.hpp" #include "ParticleHandle.hpp" +#include "ParticleSlice.hpp" #include "script_interface/ObjectState.hpp" #include "script_interface/ScriptInterface.hpp" +#include "script_interface/system/Leaf.hpp" #include "core/cell_system/CellStructure.hpp" #include "core/exclusions.hpp" @@ -47,80 +49,6 @@ namespace ScriptInterface { namespace Particles { -#ifdef EXCLUSIONS -static void set_exclusions(ParticleHandle &p, Variant const &exclusions) { - p.call_method("set_exclusions", {{"p_ids", exclusions}}); -} -#endif // EXCLUSIONS - -static void set_bonds(ParticleHandle &p, Variant const &bonds) { - auto const bond_list_flat = get_value>>(bonds); - for (auto const &bond_flat : bond_list_flat) { - auto const bond_id = bond_flat[0]; - auto const part_id = - std::vector{bond_flat.begin() + 1, bond_flat.end()}; - p.call_method("add_bond", - {{"bond_id", bond_id}, {"part_id", std::move(part_id)}}); - } -} - -std::string ParticleList::get_internal_state() const { - auto const p_ids = get_particle_ids(); - std::vector object_states(p_ids.size()); - - std::ranges::transform(p_ids, object_states.begin(), [this](auto const p_id) { - auto p_obj = - context()->make_shared("Particles::ParticleHandle", {{"id", p_id}}); - auto &p_handle = dynamic_cast(*p_obj); - auto const packed_state = p_handle.serialize(); - // custom particle serialization - auto state = Utils::unpack(packed_state); - state.name = "Particles::ParticleHandle"; - auto const bonds_view = p_handle.call_method("get_bonds_view", {}); - state.params.emplace_back(std::string{"bonds"}, pack(bonds_view)); -#ifdef EXCLUSIONS - auto const exclusions = p_handle.call_method("get_exclusions", {}); - state.params.emplace_back(std::string{"exclusions"}, pack(exclusions)); -#endif // EXCLUSIONS - state.params.emplace_back(std::string{"__cpt_sentinel"}, pack(None{})); - return Utils::pack(state); - }); - - return Utils::pack(object_states); -} - -void ParticleList::set_internal_state(std::string const &state) { - auto const object_states = Utils::unpack>(state); -#ifdef EXCLUSIONS - std::unordered_map exclusions = {}; -#endif // EXCLUSIONS - std::unordered_map bonds = {}; - - for (auto const &packed_object : object_states) { - auto state = Utils::unpack(packed_object); - VariantMap params = {}; - for (auto const &kv : state.params) { - params[kv.first] = unpack(kv.second, {}); - } - auto const p_id = get_value(params.at("id")); - bonds[p_id] = params.extract("bonds").mapped(); -#ifdef EXCLUSIONS - exclusions[p_id] = params.extract("exclusions").mapped(); -#endif // EXCLUSIONS - context()->make_shared("Particles::ParticleHandle", params); - } - - for (auto const p_id : get_particle_ids()) { - auto p_obj = - context()->make_shared("Particles::ParticleHandle", {{"id", p_id}}); - auto &p_handle = dynamic_cast(*p_obj); - set_bonds(p_handle, bonds[p_id]); -#ifdef EXCLUSIONS - set_exclusions(p_handle, exclusions[p_id]); -#endif // EXCLUSIONS - } -} - #ifdef EXCLUSIONS /** * @brief Use the bond topology to automatically add exclusions between @@ -231,6 +159,20 @@ Variant ParticleList::do_call_method(std::string const &name, if (not context()->is_head_node()) { return {}; } + if (name == "by_id") { + return std::dynamic_pointer_cast( + context()->make_shared("Particles::ParticleHandle", + {{"id", get_value(params, "p_id")}, + {"__cell_structure", m_cell_structure.lock()}, + {"__bonded_ias", m_bonded_ias.lock()}})); + } + if (name == "by_ids") { + return context()->make_shared( + "Particles::ParticleSlice", + {{"id_selection", get_value>(params, "id_selection")}, + {"__cell_structure", m_cell_structure.lock()}, + {"__bonded_ias", m_bonded_ias.lock()}}); + } if (name == "get_n_part") { return get_n_part(); } @@ -241,15 +183,18 @@ Variant ParticleList::do_call_method(std::string const &name, return particle_exists(get_value(params, "p_id")); } if (name == "add_particle") { - assert(params.count("bonds") == 0); - auto obj = context()->make_shared("Particles::ParticleHandle", params); - auto &p_handle = dynamic_cast(*obj); + assert(not params.contains("bonds")); + VariantMap local_params = params; + local_params["__cell_structure"] = m_cell_structure.lock(); + local_params["__bonded_ias"] = m_bonded_ias.lock(); + auto so = std::dynamic_pointer_cast( + context()->make_shared("Particles::ParticleHandle", local_params)); #ifdef EXCLUSIONS if (params.count("exclusions")) { - set_exclusions(p_handle, params.at("exclusions")); + so->call_method("set_exclusions", {{"p_ids", params.at("exclusions")}}); } #endif // EXCLUSIONS - return p_handle.get_parameter("id"); + return so->get_parameter("id"); } return {}; } diff --git a/src/script_interface/particle_data/ParticleList.hpp b/src/script_interface/particle_data/ParticleList.hpp index 7f8cbdc6d80..b9d48bcc4ec 100644 --- a/src/script_interface/particle_data/ParticleList.hpp +++ b/src/script_interface/particle_data/ParticleList.hpp @@ -20,13 +20,25 @@ #pragma once #include "script_interface/ScriptInterface.hpp" +#include "script_interface/cell_system/CellSystem.hpp" +#include "script_interface/interactions/BondedInteractions.hpp" +#include "script_interface/system/Leaf.hpp" +#include #include namespace ScriptInterface { namespace Particles { -class ParticleList : public ObjectHandle { +class ParticleList : public System::Leaf { + std::weak_ptr m_cell_structure; + std::weak_ptr m_bonded_ias; + + auto get_cell_structure() { + auto ptr = m_cell_structure.lock(); + assert(ptr != nullptr); + return ptr; + } public: Variant do_call_method(std::string const &name, @@ -34,9 +46,11 @@ class ParticleList : public ObjectHandle { void do_construct(VariantMap const &) override {} -private: - std::string get_internal_state() const override; - void set_internal_state(std::string const &state) override; + void attach(std::weak_ptr cell_structure, + std::weak_ptr bonded_ias) { + m_cell_structure = cell_structure; + m_bonded_ias = bonded_ias; + } }; } // namespace Particles diff --git a/src/script_interface/particle_data/ParticleSlice.cpp b/src/script_interface/particle_data/ParticleSlice.cpp index 4b0da0aa56e..efd9d95761a 100644 --- a/src/script_interface/particle_data/ParticleSlice.cpp +++ b/src/script_interface/particle_data/ParticleSlice.cpp @@ -18,11 +18,14 @@ */ #include "ParticleSlice.hpp" +#include "ParticleHandle.hpp" #include "script_interface/ScriptInterface.hpp" #include "core/particle_node.hpp" +#include +#include #include #include @@ -30,17 +33,45 @@ namespace ScriptInterface { namespace Particles { void ParticleSlice::do_construct(VariantMap const ¶ms) { + if (params.contains("__cell_structure")) { + auto so = get_value>( + params, "__cell_structure"); + so->configure(*this); + m_cell_structure = so; + } + if (params.contains("__bonded_ias")) { + m_bonded_ias = get_value>( + params, "__bonded_ias"); + } m_id_selection = get_value>(params, "id_selection"); m_chunk_size = get_value_or(params, "prefetch_chunk_size", 10000); + if (not context()->is_head_node()) { + return; + } + for (auto const pid : m_id_selection) { + if (not particle_exists(pid)) { + throw std::out_of_range("Particle does not exist: " + + std::to_string(pid)); + } + } } Variant ParticleSlice::do_call_method(std::string const &name, VariantMap const ¶ms) { + if (not context()->is_head_node()) { + return {}; + } if (name == "prefetch_particle_data") { auto p_ids = get_value>(params, "chunk"); prefetch_particle_data(p_ids); - } else if (name == "particle_exists") { - return particle_exists(get_value(params, "p_id")); + return {}; + } + if (name == "get_particle") { + return context()->make_shared( + "Particles::ParticleHandle", + {{"id", get_value(params, "p_id")}, + {"__cell_structure", m_cell_structure.lock()}, + {"__bonded_ias", m_bonded_ias.lock()}}); } return {}; } diff --git a/src/script_interface/particle_data/ParticleSlice.hpp b/src/script_interface/particle_data/ParticleSlice.hpp index 471a6dcecac..12b67e79c27 100644 --- a/src/script_interface/particle_data/ParticleSlice.hpp +++ b/src/script_interface/particle_data/ParticleSlice.hpp @@ -23,7 +23,12 @@ #include "script_interface/ScriptInterface.hpp" #include "script_interface/auto_parameters/AutoParameters.hpp" +#include "script_interface/cell_system/CellSystem.hpp" +#include "script_interface/interactions/BondedInteractions.hpp" +#include "core/system/System.hpp" + +#include #include #include @@ -33,6 +38,9 @@ namespace Particles { class ParticleSlice : public AutoParameters { std::vector m_id_selection; int m_chunk_size; + std::weak_ptr m_cell_structure; + std::weak_ptr m_bonded_ias; + std::weak_ptr<::System::System> m_system; public: ParticleSlice() { @@ -48,6 +56,11 @@ class ParticleSlice : public AutoParameters { Variant do_call_method(std::string const &name, VariantMap const ¶ms) override; + + void attach(std::weak_ptr<::System::System> system) { + assert(m_system.expired()); + m_system = system; + } }; } // namespace Particles diff --git a/src/script_interface/system/System.cpp b/src/script_interface/system/System.cpp index dc5015e66e1..214d91ee642 100644 --- a/src/script_interface/system/System.cpp +++ b/src/script_interface/system/System.cpp @@ -28,6 +28,7 @@ #include "core/cell_system/CellStructureType.hpp" #include "core/cells.hpp" #include "core/communication.hpp" +#include "core/exclusions.hpp" #include "core/nonbonded_interactions/nonbonded_interaction_data.hpp" #include "core/object-in-fluid/oif_global_forces.hpp" #include "core/particle_node.hpp" @@ -36,19 +37,29 @@ #include "core/system/System.hpp" #include "core/system/System.impl.hpp" +#include "script_interface/ObjectState.hpp" +#include "script_interface/accumulators/AutoUpdateAccumulators.hpp" #include "script_interface/analysis/Analysis.hpp" #include "script_interface/bond_breakage/BreakageSpecs.hpp" #include "script_interface/cell_system/CellSystem.hpp" +#include "script_interface/collision_detection/CollisionDetection.hpp" +#include "script_interface/constraints/Constraints.hpp" #include "script_interface/electrostatics/Container.hpp" #include "script_interface/galilei/ComFixed.hpp" #include "script_interface/galilei/Galilei.hpp" #include "script_interface/integrators/IntegratorHandle.hpp" +#include "script_interface/interactions/BondedInteractions.hpp" +#include "script_interface/interactions/NonBondedInteractions.hpp" #include "script_interface/lees_edwards/LeesEdwards.hpp" #include "script_interface/magnetostatics/Container.hpp" +#include "script_interface/particle_data/ParticleHandle.hpp" +#include "script_interface/particle_data/ParticleList.hpp" #include "script_interface/thermostat/thermostat.hpp" #include +#include #include +#include #include @@ -57,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -68,23 +80,50 @@ namespace System { static bool system_created = false; +#ifdef EXCLUSIONS +static void set_exclusions(Particles::ParticleHandle &p, + Variant const &exclusions) { + p.call_method("set_exclusions", {{"p_ids", exclusions}}); +} +#endif // EXCLUSIONS + +static void set_bonds(Particles::ParticleHandle &p, Variant const &bonds) { + auto const bond_list_flat = get_value>>(bonds); + for (auto const &bond_flat : bond_list_flat) { + auto const bond_id = bond_flat[0]; + auto const part_id = + std::vector{bond_flat.begin() + 1, bond_flat.end()}; + p.call_method("add_bond", + {{"bond_id", bond_id}, {"part_id", std::move(part_id)}}); + } +} + /** @brief Container for leaves of the system class. */ struct System::Leaves { Leaves() = default; std::shared_ptr cell_system; std::shared_ptr integrator; + std::shared_ptr bonded_interactions; +#ifdef COLLISION_DETECTION + std::shared_ptr collision_detection; +#endif std::shared_ptr thermostat; std::shared_ptr analysis; std::shared_ptr comfixed; std::shared_ptr galilei; std::shared_ptr bond_breakage; std::shared_ptr lees_edwards; + std::shared_ptr + auto_update_accumulators; + std::shared_ptr constraints; + std::shared_ptr non_bonded_inter; #ifdef ELECTROSTATICS std::shared_ptr electrostatics; #endif #ifdef DIPOLES std::shared_ptr magnetostatics; #endif + std::shared_ptr part; }; System::System() : m_instance{}, m_leaves{std::make_shared()} { @@ -142,22 +181,43 @@ System::System() : m_instance{}, m_leaves{std::make_shared()} { }); }, [this]() { return m_instance->get_min_global_cut(); }}, - {"max_oif_objects", ::max_oif_objects}, + {"max_oif_objects", + [this](Variant const &v) { + m_instance->oif_global->max_oif_objects = get_value(v); + }, + [this]() { return m_instance->oif_global->max_oif_objects; }}, }); + // note: the order of leaves matters! e.g. bonds depend on thermostats, + // and thus a thermostat object must be instantiated before the bonds add_parameter("cell_system", &Leaves::cell_system); add_parameter("integrator", &Leaves::integrator); add_parameter("thermostat", &Leaves::thermostat); add_parameter("analysis", &Leaves::analysis); add_parameter("comfixed", &Leaves::comfixed); add_parameter("galilei", &Leaves::galilei); + add_parameter("bonded_inter", &Leaves::bonded_interactions); +#ifdef COLLISION_DETECTION + add_parameter("collision_detection", &Leaves::collision_detection); +#endif add_parameter("bond_breakage", &Leaves::bond_breakage); add_parameter("lees_edwards", &Leaves::lees_edwards); + add_parameter("auto_update_accumulators", &Leaves::auto_update_accumulators); + add_parameter("constraints", &Leaves::constraints); + add_parameter("non_bonded_inter", &Leaves::non_bonded_inter); #ifdef ELECTROSTATICS add_parameter("electrostatics", &Leaves::electrostatics); #endif #ifdef DIPOLES add_parameter("magnetostatics", &Leaves::magnetostatics); #endif + add_parameter("part", &Leaves::part); +} + +template +void System::do_set_default_parameter(std::string const &name) { + assert(context()->is_head_node()); + auto const so_name = Utils::demangle().substr(17); + set_parameter(name, Variant{context()->make_shared(so_name, {})}); } void System::do_construct(VariantMap const ¶ms) { @@ -170,6 +230,15 @@ void System::do_construct(VariantMap const ¶ms) { * runtime errors about the local geometry being smaller * than the interaction range would be raised. */ + context()->parallel_try_catch([&]() { + if (not params.contains("box_l")) { + throw std::domain_error("Required argument 'box_l' not provided."); + } + if (params.contains("_regular_constructor") and system_created) { + throw std::runtime_error( + "You can only have one instance of the system class at a time"); + } + }); m_instance = ::System::System::create(); ::System::set_system(m_instance); @@ -181,11 +250,75 @@ void System::do_construct(VariantMap const ¶ms) { m_instance->lb.bind_system(m_instance); m_instance->ek.bind_system(m_instance); - for (auto const &key : get_parameter_insertion_order()) { - if (key != "box_l" and params.count(key) != 0ul) { - do_set_parameter(key, params.at(key)); + if (params.contains("_regular_constructor")) { + std::set const setable_properties = { + "box_l", "min_global_cut", + "periodicity", "time", + "time_step", "force_cap", + "max_oif_objects", "_regular_constructor"}; + for (auto const &kv : params) { + if (not setable_properties.contains(kv.first)) { + context()->parallel_try_catch([&kv]() { + throw std::domain_error( + "Property '" + kv.first + + "' cannot be set via argument to System class"); + }); + } + } + for (std::string attr : + {"min_global_cut", "periodicity", "max_oif_objects"}) { + if (params.contains(attr)) { + do_set_parameter(attr, params.at(attr)); + } + } + if (not context()->is_head_node()) { + return; } + auto integrator = std::dynamic_pointer_cast( + context()->make_shared("Integrators::IntegratorHandle", {})); + set_parameter("integrator", integrator); + for (std::string attr : {"time", "time_step", "force_cap"}) { + if (params.contains(attr)) { + integrator->set_parameter(attr, params.at(attr)); + } + } + // note: the order of leaves matters! e.g. bonds depend on thermostats, + // and thus a thermostat object must be instantiated before the bonds + do_set_default_parameter("cell_system"); + do_set_default_parameter("thermostat"); + do_set_default_parameter("bonded_inter"); +#ifdef COLLISION_DETECTION + do_set_default_parameter( + "collision_detection"); +#endif + do_set_default_parameter("analysis"); + do_set_default_parameter("comfixed"); + do_set_default_parameter("galilei"); + do_set_default_parameter("bond_breakage"); + do_set_default_parameter("lees_edwards"); + do_set_default_parameter( + "auto_update_accumulators"); + do_set_default_parameter("constraints"); + do_set_default_parameter( + "non_bonded_inter"); +#ifdef ELECTROSTATICS + do_set_default_parameter("electrostatics"); +#endif +#ifdef DIPOLES + do_set_default_parameter("magnetostatics"); +#endif + do_set_default_parameter("part"); + } else { + for (auto const &key : get_parameter_insertion_order()) { + if (key != "box_l" and params.contains(key)) { + do_set_parameter(key, params.at(key)); + } + } + } + if (not context()->is_head_node()) { + return; } + call_method("internal_attach_leaves", {}); } static void rotate_system(CellStructure &cell_structure, double phi, @@ -246,9 +379,6 @@ static void rescale_particles(CellStructure &cell_structure, int dir, Variant System::do_call_method(std::string const &name, VariantMap const ¶meters) { - if (name == "is_system_created") { - return system_created; - } if (name == "lock_system_creation") { system_created = true; return {}; @@ -333,8 +463,82 @@ Variant System::do_call_method(std::string const &name, } return {}; } + if (name == "internal_attach_leaves") { + m_leaves->part->attach(m_leaves->cell_system, + m_leaves->bonded_interactions); +#ifdef COLLISION_DETECTION + m_leaves->collision_detection->attach(m_leaves->bonded_interactions); +#endif + return {}; + } return {}; } +/** + * @brief Serialize particles. + * Particles need to be serialized here to reduce overhead, + * and also to guarantee particles get instantiated after the cell structure + * was instantiated (since they store a weak pointer to it). + */ +std::string System::get_internal_state() const { + auto const p_ids = get_particle_ids(); + std::vector object_states(p_ids.size()); + + std::ranges::transform(p_ids, object_states.begin(), [this](auto const p_id) { + auto p_obj = context()->make_shared( + "Particles::ParticleHandle", + {{"id", p_id}, {"__cell_structure", m_leaves->cell_system}}); + auto &p_handle = dynamic_cast(*p_obj); + auto const packed_state = p_handle.serialize(); + // custom particle serialization + auto state = Utils::unpack(packed_state); + state.name = "Particles::ParticleHandle"; + auto const bonds_view = p_handle.call_method("get_bonds_view", {}); + state.params.emplace_back(std::string{"bonds"}, pack(bonds_view)); +#ifdef EXCLUSIONS + auto const exclusions = p_handle.call_method("get_exclusions", {}); + state.params.emplace_back(std::string{"exclusions"}, pack(exclusions)); +#endif // EXCLUSIONS + state.params.emplace_back(std::string{"__cpt_sentinel"}, pack(None{})); + return Utils::pack(state); + }); + + return Utils::pack(object_states); +} + +void System::set_internal_state(std::string const &state) { + auto const object_states = Utils::unpack>(state); +#ifdef EXCLUSIONS + std::unordered_map exclusions = {}; +#endif // EXCLUSIONS + std::unordered_map bonds = {}; + + for (auto const &packed_object : object_states) { + auto state = Utils::unpack(packed_object); + VariantMap params = {}; + for (auto const &kv : state.params) { + params[kv.first] = unpack(kv.second, {}); + } + auto const p_id = get_value(params.at("id")); + bonds[p_id] = params.extract("bonds").mapped(); +#ifdef EXCLUSIONS + exclusions[p_id] = params.extract("exclusions").mapped(); +#endif // EXCLUSIONS + params["__cell_structure"] = get_parameter("cell_system"); + context()->make_shared("Particles::ParticleHandle", params); + } + + for (auto const p_id : get_particle_ids()) { + auto p_obj = context()->make_shared( + "Particles::ParticleHandle", + {{"id", p_id}, {"__cell_structure", m_leaves->cell_system}}); + auto &p_handle = dynamic_cast(*p_obj); + set_bonds(p_handle, bonds[p_id]); +#ifdef EXCLUSIONS + set_exclusions(p_handle, exclusions[p_id]); +#endif // EXCLUSIONS + } +} + } // namespace System } // namespace ScriptInterface diff --git a/src/script_interface/system/System.hpp b/src/script_interface/system/System.hpp index 284383f17ab..e21487b36b9 100644 --- a/src/script_interface/system/System.hpp +++ b/src/script_interface/system/System.hpp @@ -46,6 +46,14 @@ class System : public AutoParameters { void do_construct(VariantMap const ¶ms) override; Variant do_call_method(std::string const &name, VariantMap const ¶meters) override; + + auto const &get_system() const { return *m_instance; } + +private: + template + void do_set_default_parameter(std::string const &name); + std::string get_internal_state() const override; + void set_internal_state(std::string const &state) override; }; } // namespace System diff --git a/src/script_interface/system/SystemFacade.hpp b/src/script_interface/system/SystemFacade.hpp deleted file mode 100644 index a6e30168d75..00000000000 --- a/src/script_interface/system/SystemFacade.hpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2013-2022 The ESPResSo project - * - * This file is part of ESPResSo. - * - * ESPResSo is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ESPResSo is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "System.hpp" - -#include "script_interface/ScriptInterface.hpp" - -#include -#include -#include - -namespace ScriptInterface { -namespace System { - -/** - * @brief Facade for the real @ref System class. - * All calls are forwarded to the real class. - * This construct is needed to satisfy the requirements - * of the checkpointing mechanism, which instantiates - * the @c SystemFacade after all other script interface - * objects have been instantiated. - */ -class SystemFacade : public ObjectHandle { - std::shared_ptr m_instance; - -public: - SystemFacade() { - // create a dummy system to be able to read the list of parameters - m_instance = std::make_shared(); - } - void do_construct(VariantMap const &) override {}; - Variant get_parameter(const std::string &name) const override { - return m_instance->get_parameter(name); - } - void do_set_parameter(const std::string &name, const Variant &v) override { - m_instance->do_set_parameter(name, v); - } - std::span valid_parameters() const override { - return m_instance->valid_parameters(); - } - Variant do_call_method(std::string const &name, - VariantMap const ¶ms) override { - if (name == "get_system_handle") { - return m_instance; - } - if (name == "set_system_handle") { - m_instance = std::dynamic_pointer_cast( - get_value(params, "obj")); - return {}; - } - return m_instance->do_call_method(name, params); - } -}; - -} // namespace System -} // namespace ScriptInterface diff --git a/src/script_interface/system/initialize.cpp b/src/script_interface/system/initialize.cpp index a89cd46d335..536cf22659a 100644 --- a/src/script_interface/system/initialize.cpp +++ b/src/script_interface/system/initialize.cpp @@ -21,14 +21,12 @@ #include "CudaInitHandle.hpp" #include "System.hpp" -#include "SystemFacade.hpp" namespace ScriptInterface { namespace System { void initialize(Utils::Factory *om) { om->register_new("System::CudaInitHandle"); - om->register_new("System::SystemFacade"); om->register_new("System::System"); } diff --git a/src/script_interface/tests/ObjectList_test.cpp b/src/script_interface/tests/ObjectList_test.cpp index 2ab36c60d1d..cd778859125 100644 --- a/src/script_interface/tests/ObjectList_test.cpp +++ b/src/script_interface/tests/ObjectList_test.cpp @@ -52,53 +52,61 @@ struct ObjectListImpl : ObjectList { } }; +static auto factory = []() { + Utils::Factory factory; + factory.register_new("ObjectHandle"); + factory.register_new("ObjectListImpl"); + return factory; +}(); + BOOST_AUTO_TEST_CASE(default_construction) { - // A defaulted ObjectList has no elements. + // A default-constructed ObjectList has no elements. BOOST_CHECK(ObjectListImpl{}.elements().empty()); } BOOST_AUTO_TEST_CASE(adding_elements) { - // Added elements are on the back of the list of elements. - auto e = ObjectRef{}; - ObjectListImpl list; - list.add(e); - BOOST_CHECK(list.elements().back() == e); + boost::mpi::communicator comm; + auto ctx = std::make_shared(factory, comm); + auto e = std::make_shared(); + auto list = std::dynamic_pointer_cast( + ctx->make_shared("ObjectListImpl", {})); + list->add(e); + BOOST_CHECK(list->elements().back() == e); // And is added to the core - BOOST_CHECK(std::ranges::count(list.mock_core, e) == 1); + BOOST_CHECK(std::ranges::count(list->mock_core, e) == 1); } BOOST_AUTO_TEST_CASE(removing_elements) { - // An element that is removed from the list is - // no longer an element of the list. - auto e = ObjectRef{}; - ObjectListImpl list; - list.add(e); - list.remove(e); - BOOST_CHECK(std::ranges::count(list.elements(), e) == 0); - // And is removed from the core - BOOST_CHECK(std::ranges::count(list.mock_core, e) == 0); + boost::mpi::communicator comm; + auto ctx = std::make_shared(factory, comm); + auto e = std::make_shared(); + auto list = std::dynamic_pointer_cast( + ctx->make_shared("ObjectListImpl", {})); + list->add(e); + list->remove(e); + BOOST_CHECK(std::ranges::count(list->elements(), e) == 0); + BOOST_CHECK(std::ranges::count(list->mock_core, e) == 0); } BOOST_AUTO_TEST_CASE(clearing_elements) { + boost::mpi::communicator comm; + auto ctx = std::make_shared(factory, comm); // A cleared list is empty. - ObjectListImpl list; - list.add(std::make_shared()); - list.add(std::make_shared()); - list.clear(); - BOOST_CHECK(list.elements().empty()); - BOOST_CHECK(list.mock_core.empty()); + auto list = std::dynamic_pointer_cast( + ctx->make_shared("ObjectListImpl", {})); + list->add(std::make_shared()); + list->add(std::make_shared()); + list->clear(); + BOOST_CHECK(list->elements().empty()); + BOOST_CHECK(list->mock_core.empty()); } BOOST_AUTO_TEST_CASE(serialization) { - // In a context - Utils::Factory f; - f.register_new("ObjectHandle"); - f.register_new("ObjectList"); boost::mpi::communicator comm; - auto ctx = std::make_shared(f, comm); + auto ctx = std::make_shared(factory, comm); // A list of some elements auto list = std::dynamic_pointer_cast( - ctx->make_shared("ObjectList", {})); + ctx->make_shared("ObjectListImpl", {})); // with a bunch of elements list->add(ctx->make_shared("ObjectHandle", {})); diff --git a/src/script_interface/tests/ObjectMap_test.cpp b/src/script_interface/tests/ObjectMap_test.cpp index 8c54ec4f47a..843b3ef3f92 100644 --- a/src/script_interface/tests/ObjectMap_test.cpp +++ b/src/script_interface/tests/ObjectMap_test.cpp @@ -43,7 +43,9 @@ struct ObjectMapImpl : ObjectMap { std::unordered_map mock_core; private: - void before_do_construct() override {} + void do_construct(VariantMap const ¶ms) override { + restore_from_checkpoint(params); + } void insert_in_core(KeyType const &key, ObjectRef const &obj_ptr) override { next_key = std::max(next_key, key + 1); mock_core[key] = obj_ptr; diff --git a/testsuite/python/CMakeLists.txt b/testsuite/python/CMakeLists.txt index 73618834b1c..e09ee314478 100644 --- a/testsuite/python/CMakeLists.txt +++ b/testsuite/python/CMakeLists.txt @@ -375,6 +375,7 @@ python_test(FILE elc.py MAX_NUM_PROC 2 GPU_SLOTS 1) python_test(FILE elc_vs_analytic.py MAX_NUM_PROC 2 GPU_SLOTS 1) python_test(FILE rotation.py MAX_NUM_PROC 1) python_test(FILE shapes.py MAX_NUM_PROC 1) +python_test(FILE system.py MAX_NUM_PROC 2) python_test(FILE h5md.py MAX_NUM_PROC 2) python_test(FILE h5md.py MAX_NUM_PROC 1 SUFFIX 1_core) python_test(FILE p3m_fft.py MAX_NUM_PROC 6) diff --git a/testsuite/python/accumulator_correlator.py b/testsuite/python/accumulator_correlator.py index 1882ef9d598..19ac1cfa29d 100644 --- a/testsuite/python/accumulator_correlator.py +++ b/testsuite/python/accumulator_correlator.py @@ -33,7 +33,12 @@ class CorrelatorTest(ut.TestCase): Test class for the Correlator accumulator. """ - # Handle for espresso system + # create an accumulator bound to the default-constructed system + expired_system_obs = espressomd.observables.ParticleVelocities(ids=(0,)) + expired_system_acc = espressomd.accumulators.Correlator( + obs1=expired_system_obs, tau_lin=10, tau_max=2., delta_N=1, + corr_operation="scalar_product") + # create a new system to replace the default-constructed system system = espressomd.System(box_l=[10, 10, 10]) system.cell_system.skin = 0.4 system.time_step = 0.01 @@ -71,6 +76,8 @@ def check_sizes(self, acc, steps, linear=False): i = j def check_pickling(self, acc): + from espressomd.script_interface import ScriptInterfaceHelper + acc.__reduce__ = lambda: ScriptInterfaceHelper.__reduce__(acc) corr = acc.result() lags = acc.lag_times() sizes = acc.sample_sizes() @@ -86,7 +93,7 @@ def test_square_distance_componentwise(self): obs = espressomd.observables.ParticlePositions(ids=(p.id,)) acc = espressomd.accumulators.Correlator( - obs1=obs, tau_lin=10, tau_max=2, delta_N=1, + obs1=obs, tau_lin=10, tau_max=2, delta_N=1, system=s, corr_operation="square_distance_componentwise") s.integrator.run(1000) @@ -109,7 +116,7 @@ def test_tensor_product(self): obs = espressomd.observables.ParticleVelocities(ids=(p.id,)) acc = espressomd.accumulators.Correlator( - obs1=obs, tau_lin=12, tau_max=2, delta_N=1, + obs1=obs, tau_lin=12, tau_max=2, delta_N=1, system=s, corr_operation="tensor_product") s.auto_update_accumulators.add(acc) @@ -132,7 +139,7 @@ def test_componentwise_product(self): obs = espressomd.observables.ParticleVelocities(ids=(p.id,)) acc = espressomd.accumulators.Correlator( - obs1=obs, tau_lin=10, tau_max=2, delta_N=1, + obs1=obs, tau_lin=10, tau_max=2, delta_N=1, system=s, corr_operation="componentwise_product") s.auto_update_accumulators.add(acc) @@ -154,7 +161,7 @@ def test_scalar_product(self): obs = espressomd.observables.ParticleVelocities(ids=(p.id,)) acc = espressomd.accumulators.Correlator( - obs1=obs, tau_lin=10, tau_max=2, delta_N=1, + obs1=obs, tau_lin=10, tau_max=2, delta_N=1, system=s, corr_operation="scalar_product") s.auto_update_accumulators.add(acc) @@ -178,7 +185,7 @@ def test_fcs(self): obs = espressomd.observables.ParticlePositions(ids=(p.id,)) acc = espressomd.accumulators.Correlator( obs1=obs, tau_lin=10, tau_max=9.9 * self.system.time_step, - delta_N=1, corr_operation="fcs_acf", args=w) + delta_N=1, system=s, corr_operation="fcs_acf", args=w) s.auto_update_accumulators.add(acc) s.integrator.run(1000) @@ -215,7 +222,7 @@ def test_correlator_compression(self): acc = espressomd.accumulators.Correlator( obs1=obs, tau_lin=tau_lin, tau_max=2, delta_N=1, compress1=compression, compress2=compression, - corr_operation="scalar_product") + corr_operation="scalar_product", system=self.system) accumulators[compression] = acc self.system.auto_update_accumulators.add(acc) # record oscillating data with frequency of 1/time_step @@ -352,6 +359,12 @@ def create_accumulator(obs1=obs, **kwargs): obs2=obs2, corr_operation="fcs_acf", args=[1, 1, 1]) acc.update() + # check checkpointing mechanism + with self.assertRaisesRegex(RuntimeError, "This accumulator is bound to another system"): + self.system.auto_update_accumulators.add(self.expired_system_acc) + with self.assertRaisesRegex(RuntimeError, "auto_update_accumulators property"): + pickle.dumps(acc) + if __name__ == "__main__": ut.main() diff --git a/testsuite/python/accumulator_mean_variance.py b/testsuite/python/accumulator_mean_variance.py index 2afd15312e5..8b2224d2922 100644 --- a/testsuite/python/accumulator_mean_variance.py +++ b/testsuite/python/accumulator_mean_variance.py @@ -26,8 +26,6 @@ import espressomd.observables import espressomd.accumulators -N_PART = 4 - class MeanVarianceCalculatorTest(ut.TestCase): @@ -50,12 +48,13 @@ def test_accumulator(self): """Check that accumulator results are the same as the respective numpy result. """ + n_part = 4 system = self.system - system.part.add(pos=np.zeros((N_PART, 3))) - obs = espressomd.observables.ParticlePositions(ids=range(N_PART)) + system.part.add(pos=np.zeros((n_part, 3))) + obs = espressomd.observables.ParticlePositions(ids=range(n_part)) acc = espressomd.accumulators.MeanVarianceCalculator(obs=obs) system.auto_update_accumulators.add(acc) - positions = np.copy(system.box_l) * np.random.random((10, N_PART, 3)) + positions = np.copy(system.box_l) * np.random.random((10, n_part, 3)) for pos in positions: system.part.all().pos = pos @@ -71,6 +70,8 @@ def test_accumulator(self): np.testing.assert_allclose(acc.std_error(), pos_sem, atol=1e-12) # Check pickling + from espressomd.script_interface import ScriptInterfaceHelper + acc.__reduce__ = lambda: ScriptInterfaceHelper.__reduce__(acc) acc_unpkl = pickle.loads(pickle.dumps(acc)) np.testing.assert_allclose(acc_unpkl.mean(), pos_mean, atol=1e-12) np.testing.assert_allclose(acc_unpkl.variance(), pos_var, atol=1e-12) diff --git a/testsuite/python/accumulator_time_series.py b/testsuite/python/accumulator_time_series.py index 1a27c52a1d4..29af30c8b25 100644 --- a/testsuite/python/accumulator_time_series.py +++ b/testsuite/python/accumulator_time_series.py @@ -26,8 +26,6 @@ import espressomd.observables import espressomd.accumulators -N_PART = 4 - class TimeSeriesTest(ut.TestCase): @@ -50,12 +48,13 @@ def test_accumulator(self): """Check that accumulator results are the same as the respective numpy result. """ + n_part = 4 system = self.system - system.part.add(pos=np.zeros((N_PART, 3))) - obs = espressomd.observables.ParticlePositions(ids=range(N_PART)) + system.part.add(pos=np.zeros((n_part, 3))) + obs = espressomd.observables.ParticlePositions(ids=range(n_part)) acc = espressomd.accumulators.TimeSeries(obs=obs) system.auto_update_accumulators.add(acc) - positions = np.copy(system.box_l) * np.random.random((10, N_PART, 3)) + positions = np.copy(system.box_l) * np.random.random((10, n_part, 3)) for pos in positions: system.part.all().pos = pos @@ -66,6 +65,8 @@ def test_accumulator(self): np.testing.assert_array_equal(acc.time_series(), positions) # Check pickling + from espressomd.script_interface import ScriptInterfaceHelper + acc.__reduce__ = lambda: ScriptInterfaceHelper.__reduce__(acc) acc_unpickled = pickle.loads(pickle.dumps(acc)) np.testing.assert_array_equal(acc_unpickled.time_series(), positions) diff --git a/testsuite/python/cluster_analysis.py b/testsuite/python/cluster_analysis.py index 8f708016527..d28e86a6499 100644 --- a/testsuite/python/cluster_analysis.py +++ b/testsuite/python/cluster_analysis.py @@ -37,7 +37,7 @@ class ClusterAnalysis(ut.TestCase): fene = espressomd.interactions.FeneBond(k=1, d_r_max=0.05) system.bonded_inter.add(fene) - cs = espressomd.cluster_analysis.ClusterStructure() + cs = espressomd.cluster_analysis.ClusterStructure(system=system) np.random.seed(1) @@ -66,6 +66,8 @@ def test_00_fails_without_criterion_set(self): self.set_two_clusters() with self.assertRaises(Exception): self.cs.run_for_all_pairs() + with self.assertRaisesRegex(RuntimeError, "Parameter 'system' is missing"): + espressomd.cluster_analysis.ClusterStructure() def test_set_criterion(self): self.set_two_clusters() diff --git a/testsuite/python/collision_detection_interface.py b/testsuite/python/collision_detection_interface.py index 25df82dad93..3e2e5dbe7a9 100644 --- a/testsuite/python/collision_detection_interface.py +++ b/testsuite/python/collision_detection_interface.py @@ -104,7 +104,7 @@ def test_bind_centers(self): self.set_coldet("bind_centers", distance=-2.) with self.assertRaisesRegex(ValueError, "Parameter 'distance' must be > 0"): self.set_coldet("bind_centers", distance=0.) - with self.assertRaisesRegex(RuntimeError, "Bond in parameter 'bond_centers' was not added to the system"): + with self.assertRaisesRegex(ValueError, "Bond in parameter 'bond_centers' was not added to the system"): bond = espressomd.interactions.HarmonicBond(k=1., r_0=0.1) self.set_coldet("bind_centers", bond_centers=bond) with self.assertRaisesRegex(RuntimeError, "The bond type to be used for binding particle centers needs to be a pair bond"): @@ -128,7 +128,7 @@ def test_bind_at_point_of_collision(self): self.set_coldet("bind_at_point_of_collision", vs_placement=-0.01) with self.assertRaisesRegex(ValueError, "Parameter 'vs_placement' must be between 0 and 1"): self.set_coldet("bind_at_point_of_collision", vs_placement=1.01) - with self.assertRaisesRegex(RuntimeError, "Bond in parameter 'bond_vs' was not added to the system"): + with self.assertRaisesRegex(ValueError, "Bond in parameter 'bond_vs' was not added to the system"): bond = espressomd.interactions.HarmonicBond(k=1., r_0=0.1) self.set_coldet("bind_at_point_of_collision", bond_vs=bond) with self.assertRaisesRegex(RuntimeError, "bond type to be used for binding virtual sites needs to be a pair bond"): diff --git a/testsuite/python/constraint_shape_based.py b/testsuite/python/constraint_shape_based.py index 88adf51a073..ed99a89c062 100644 --- a/testsuite/python/constraint_shape_based.py +++ b/testsuite/python/constraint_shape_based.py @@ -22,6 +22,7 @@ import unittest_decorators as utx import numpy as np import math +import pickle import espressomd import espressomd.math @@ -1092,6 +1093,8 @@ def test_exceptions(self): 0.5 * system.box_l[0], "xyz") np.testing.assert_allclose( np.copy(system.box_l), 0.75 * 0.5 * box_l, atol=1e-7) + with self.assertRaisesRegex(RuntimeError, "constraints property"): + pickle.dumps(constraint) if __name__ == "__main__": diff --git a/testsuite/python/ibm.py b/testsuite/python/ibm.py index 5c1b5ebaec2..e160a976d7e 100644 --- a/testsuite/python/ibm.py +++ b/testsuite/python/ibm.py @@ -18,6 +18,7 @@ # import numpy as np import itertools +import pickle import unittest as ut import espressomd @@ -46,6 +47,10 @@ def compute_dihedral_angle(self, pos0, pos1, pos2, pos3): cos_alpha = min(1, np.dot(n1, n2)) alpha = np.arccos(cos_alpha) + desc = np.dot((pos1 - pos0), np.cross(n1, n2)) + if desc > 0.: + alpha = 2. * np.pi - alpha + return alpha def test_tribend(self): @@ -55,7 +60,7 @@ def test_tribend(self): system = self.system system.thermostat.set_langevin(kT=0, gamma=10, seed=1) - # Add four particles + # Add four particles arranged in a U-shape, dihedral angle is zero p0 = system.part.add(pos=[4, 4, 4]) p1 = system.part.add(pos=[4, 4, 5]) p2 = system.part.add(pos=[4, 5, 5]) @@ -84,8 +89,9 @@ def test_tribend(self): # Perform integration system.integrator.run(200) - angle = self.compute_dihedral_angle(p0.pos, p1.pos, p2.pos, p3.pos) - self.assertLess(angle, 2E-2) + theta = self.compute_dihedral_angle(p0.pos, p1.pos, p2.pos, p3.pos) + theta = np.fmod(theta + 0.1, 2. * np.pi) - 0.1 + self.assertLess(np.abs(theta), 2E-2) # IBM doesn't implement energy and pressure kernels. energy = self.system.analysis.energy() @@ -161,10 +167,14 @@ def test_volcons(self): KAPPA_V = 0.01 volCons = espressomd.interactions.IBM_VolCons( softID=15, kappaV=KAPPA_V) + self.assertEqual(volCons.current_volume(), 0.) system.bonded_inter.add(volCons) for p in system.part: p.add_bond((volCons,)) + # The default volume is zero + self.assertEqual(volCons.current_volume(), 0.) + # Run the integrator to initialize the mesh reference volume. system.integrator.run(0, recalc_forces=True) self.assertAlmostEqual(volCons.current_volume(), 1., delta=1e-10) @@ -226,6 +236,71 @@ def test_volcons(self): mesh_center = np.mean(partcls.pos, axis=0) np.testing.assert_allclose(mesh_center, mesh_center_ref, rtol=1e-3) + def test_tribend_checkpointing(self): + system = self.system + # Add four particles arranged in a U-shape, dihedral angle is non-zero + p1 = system.part.add(pos=[3.5, 3.5, 4.0]) + p2 = system.part.add(pos=[4.5, 3.5, 4.0]) + p3 = system.part.add(pos=[4.5, 4.5, 4.0]) + p4 = system.part.add(pos=[3.5, 4.5, 4.2]) + theta0 = self.compute_dihedral_angle(p1.pos, p2.pos, p3.pos, p4.pos) + assert abs(theta0 - 6.) < 0.1 + tribend_original = espressomd.interactions.IBM_Tribend( + ind1=p1.id, ind2=p2.id, ind3=p3.id, ind4=p4.id, kb=1., refShape="Initial") + tribend_unpickled = pickle.loads(pickle.dumps(tribend_original)) + for tribend in [tribend_original, tribend_unpickled]: + self.assertFalse(tribend.is_initialized) + self.assertEqual(tribend.theta0, 0.) + self.assertEqual(tribend.ind1, p1.id) + self.assertEqual(tribend.ind2, p2.id) + self.assertEqual(tribend.ind3, p3.id) + self.assertEqual(tribend.ind4, p4.id) + system.bonded_inter.add(tribend_original) + system.bonded_inter.add(tribend_unpickled) + tribend_skip_init = pickle.loads(pickle.dumps(tribend_original)) + for tribend in [tribend_original, tribend_unpickled, tribend_skip_init]: + self.assertTrue(tribend.is_initialized) + self.assertAlmostEqual(tribend.theta0, theta0, delta=1e-6) + self.assertEqual(tribend.ind1, p1.id) + self.assertEqual(tribend.ind2, p2.id) + self.assertEqual(tribend.ind3, p3.id) + self.assertEqual(tribend.ind4, p4.id) + + def test_triel_checkpointing(self): + system = self.system + p1 = system.part.add(pos=[1, 4, 4]) + p2 = system.part.add(pos=[1, 4, 5]) + p3 = system.part.add(pos=[1, 5, 5]) + sqrt2 = np.sqrt(2.) + cache = [sqrt2, 1., sqrt2 / 2., sqrt2 / 2., 0.5, -1., 1., 0., -1.] + triel_original = espressomd.interactions.IBM_Triel( + ind1=p1.id, ind2=p2.id, ind3=p3.id, elasticLaw="Skalak", k1=15., + k2=0.5, maxDist=2.4) + triel_unpickled = pickle.loads(pickle.dumps(triel_original)) + for triel in [triel_original, triel_unpickled]: + self.assertFalse(triel.is_initialized) + np.testing.assert_allclose(np.copy(triel._cache), 0.) + self.assertEqual(triel.ind1, p1.id) + self.assertEqual(triel.ind2, p2.id) + self.assertEqual(triel.ind3, p3.id) + self.assertEqual(triel.elasticLaw, "Skalak") + self.assertEqual(triel.k1, 15.) + self.assertEqual(triel.k2, 0.5) + self.assertEqual(triel.maxDist, 2.4) + system.bonded_inter.add(triel_original) + system.bonded_inter.add(triel_unpickled) + triel_skip_init = pickle.loads(pickle.dumps(triel_original)) + for triel in [triel_original, triel_unpickled, triel_skip_init]: + self.assertTrue(triel.is_initialized) + np.testing.assert_allclose(np.copy(triel._cache), cache) + self.assertEqual(triel.ind1, p1.id) + self.assertEqual(triel.ind2, p2.id) + self.assertEqual(triel.ind3, p3.id) + self.assertEqual(triel.elasticLaw, "Skalak") + self.assertEqual(triel.k1, 15.) + self.assertEqual(triel.k2, 0.5) + self.assertEqual(triel.maxDist, 2.4) + if __name__ == "__main__": ut.main() diff --git a/testsuite/python/interactions_bonded_interface.py b/testsuite/python/interactions_bonded_interface.py index 57062a5bed3..38c16218d83 100644 --- a/testsuite/python/interactions_bonded_interface.py +++ b/testsuite/python/interactions_bonded_interface.py @@ -224,10 +224,6 @@ 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) @@ -253,6 +249,8 @@ def test_exceptions(self): with self.assertRaisesRegex(ValueError, "Invalid value for parameter 'elasticLaw': 'Unknown'"): espressomd.interactions.IBM_Triel( ind1=0, ind2=1, ind3=2, k1=1.1, k2=1.2, maxDist=1.6, elasticLaw='Unknown') + with self.assertRaisesRegex(ValueError, "IBMVolCons parameter 'softID' has to be >= 0"): + espressomd.interactions.IBM_VolCons(softID=-1, kappaV=0.) # sanity checks when removing bonds self.system.bonded_inter.clear() diff --git a/testsuite/python/interactions_non-bonded_interface.py b/testsuite/python/interactions_non-bonded_interface.py index 88119cb5f22..f6a119b1d8f 100644 --- a/testsuite/python/interactions_non-bonded_interface.py +++ b/testsuite/python/interactions_non-bonded_interface.py @@ -165,8 +165,11 @@ def test_set_params(self): 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 + wca_new = espressomd.interactions.WCAInteraction(epsilon=3., sigma=1.) + self.system.non_bonded_inter[0, 0].wca = wca_new + wca_cur = self.system.non_bonded_inter[0, 0].wca + self.assertEqual(wca_cur.get_params()["epsilon"], 3.) + self.assertEqual(wca_cur.get_params()["sigma"], 1.) @utx.skipIfMissingFeatures("LENNARD_JONES") def test_exceptions(self): @@ -185,6 +188,10 @@ def test_exceptions(self): 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") + with self.assertRaisesRegex(ValueError, r"NonBondedInteractions\[\] expects two particle types as indices"): + self.system.non_bonded_inter[0, 0, 1].lennard_jones + with self.assertRaisesRegex(ValueError, r"NonBondedInteractions\[\] expects two particle types as indices"): + self.system.non_bonded_inter[-1, 0].lennard_jones skin = self.system.cell_system.skin box_l = self.system.box_l @@ -199,11 +206,10 @@ def test_exceptions(self): 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")) with self.assertRaisesRegex(ValueError, r"NonBondedInteractions\[\] expects two particle types as indices"): self.system.non_bonded_inter[0, 0.] - with self.assertRaisesRegex(ValueError, r"NonBondedInteractions\[\] expects two particle types as indices"): + with self.assertRaisesRegex(TypeError, "'NonBondedInteractions' object does not support item assignment"): self.system.non_bonded_inter[0, -1] = None def test_feature_checks(self): diff --git a/testsuite/python/lb_lees_edwards_particle_coupling.py b/testsuite/python/lb_lees_edwards_particle_coupling.py index 713a43e5fea..cac3bb5a2d0 100644 --- a/testsuite/python/lb_lees_edwards_particle_coupling.py +++ b/testsuite/python/lb_lees_edwards_particle_coupling.py @@ -57,7 +57,7 @@ def coupling_weight(pos_lb_units, node_idx, lb_shape): # If the coupling point is >=1 lattice constant away from the node, # no coupling. Otherwise, distance pos to node via module with lattice # constant 1 - weight = 0. if np.any(dx >= 1.) else np.product(1. - dx) + weight = 0. if np.any(dx >= 1.) else np.prod(1. - dx) return weight diff --git a/testsuite/python/mpiio.py b/testsuite/python/mpiio.py index 1491b74a85c..4f3a7d707f0 100644 --- a/testsuite/python/mpiio.py +++ b/testsuite/python/mpiio.py @@ -159,7 +159,7 @@ def test_mpiio(self): 'velocities': True, 'bonds': True} prefix = self.generate_prefix(self.id()) - mpiio = espressomd.io.mpiio.Mpiio() + mpiio = espressomd.io.mpiio.Mpiio(system=self.system) self.add_particles() mpiio.write(prefix, **fields) @@ -171,7 +171,7 @@ def test_mpiio(self): def test_mpiio_without_positions(self): prefix = self.generate_prefix(self.id()) - mpiio = espressomd.io.mpiio.Mpiio() + mpiio = espressomd.io.mpiio.Mpiio(system=self.system) self.add_particles() mpiio.write(prefix, types=True, positions=False) self.system.part.clear() @@ -180,7 +180,7 @@ def test_mpiio_without_positions(self): def test_mpiio_without_types(self): prefix = self.generate_prefix(self.id()) - mpiio = espressomd.io.mpiio.Mpiio() + mpiio = espressomd.io.mpiio.Mpiio(system=self.system) self.add_particles() mpiio.write(prefix, types=False, positions=True) self.system.part.clear() @@ -200,8 +200,8 @@ def test_mpiio_multiple_instances(self): 'bonds': False} prefix1 = self.generate_prefix(self.id()) + '.1' prefix2 = self.generate_prefix(self.id()) + '.2' - mpiio1 = espressomd.io.mpiio.Mpiio() - mpiio2 = espressomd.io.mpiio.Mpiio() + mpiio1 = espressomd.io.mpiio.Mpiio(system=self.system) + mpiio2 = espressomd.io.mpiio.Mpiio(system=self.system) self.add_particles() mpiio1.write(prefix1, **fields1) @@ -218,7 +218,7 @@ def test_mpiio_multiple_instances(self): self.check_sample_system(**fields2) def test_mpiio_exceptions(self): - mpiio = espressomd.io.mpiio.Mpiio() + mpiio = espressomd.io.mpiio.Mpiio(system=self.system) prefix = self.generate_prefix(self.id()) msg_prefix = "Need to supply output prefix via the 'prefix' argument." with self.assertRaisesRegex(ValueError, msg_prefix): diff --git a/testsuite/python/mpiio_exceptions.py b/testsuite/python/mpiio_exceptions.py index 7f3595cfa12..e6aa3b7a1dd 100644 --- a/testsuite/python/mpiio_exceptions.py +++ b/testsuite/python/mpiio_exceptions.py @@ -75,7 +75,7 @@ def tearDownClass(cls): @ut.skipIf(n_nodes != 1, "only works on 1 MPI rank") def test_exceptions(self): generator = MPIIOMockGenerator(self.temp_dir.name) - mpiio = espressomd.io.mpiio.Mpiio() + mpiio = espressomd.io.mpiio.Mpiio(system=self.system) # generate reference data self.system.part.add(pos=[0, 0, 0]) @@ -87,6 +87,9 @@ def test_exceptions(self): mpiio.read(path_ref, types=True) self.system.part.clear() + with self.assertRaisesRegex(RuntimeError, "Parameter 'system' is missing"): + espressomd.io.mpiio.Mpiio() + # exception when the metadata cannot be written path, fn = generator.create('head', read_only=True) with self.assertRaisesRegex(RuntimeError, f'Could not open file "{fn}"'): diff --git a/testsuite/python/particle.py b/testsuite/python/particle.py index cc2e40f11a8..76d9710e698 100644 --- a/testsuite/python/particle.py +++ b/testsuite/python/particle.py @@ -260,10 +260,8 @@ def test_contradicting_properties_quat(self): ] # check all methods that can instantiate particles for kwargs in invalid_combinations: - for make_new_particle in ( - self.system.part.add, espressomd.particle_data.ParticleHandle): - with self.assertRaises(ValueError): - make_new_particle(pos=[0., 0., 0.], **kwargs) + with self.assertRaises(ValueError): + self.system.part.add(pos=[0., 0., 0.], **kwargs) @utx.skipIfMissingFeatures(["ROTATION"]) def test_invalid_quat(self): @@ -358,11 +356,6 @@ def test_invalid_particle_ids(self): with self.assertRaisesRegex(RuntimeError, "Particle node for id 42 not found"): handle_to_non_existing_particle.type for i in range(1, 10): - p = espressomd.particle_data.ParticleHandle(id=-i) - with self.assertRaisesRegex(ValueError, f"Invalid particle id: {-i}"): - p.node - with self.assertRaisesRegex(ValueError, f"Invalid particle id: {-i}"): - p.remove() with self.assertRaisesRegex(ValueError, f"Invalid particle id: {-i}"): self.system.part.add(pos=[0., 0., 0.], id=-i) diff --git a/testsuite/python/save_checkpoint.py b/testsuite/python/save_checkpoint.py index 6e13e2a152c..c2c72434005 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -50,7 +50,7 @@ modes = config.get_modes() # use a box with 3 different dimensions, unless DipolarP3M is used -system = espressomd.System(box_l=[12.0, 8.0, 16.0]) +system = espressomd.System(box_l=[12.0, 8.0, 16.0], time_step=0.01) if 'DP3M' in modes: system.box_l = 3 * [float(np.max(system.box_l))] system.cell_system.skin = 0.1 @@ -263,8 +263,9 @@ 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) - handle_ia = espressomd.interactions.NonBondedInteractionHandle( - _types=(0, 0)) + handle_ia = espressomd.interactions.NonBondedInteractionHandle() + handle_ia.lennard_jones.set_params( + epsilon=1.2, sigma=1.3, cutoff=2.0, shift=0.1) checkpoint.register("handle_ia") if espressomd.has_features(['DPD']): dpd_params = {"weight_function": 1, "gamma": 2., "trans_r_cut": 2., "k": 2., @@ -306,19 +307,16 @@ ibm_triel_bond = espressomd.interactions.IBM_Triel( ind1=p1.id, ind2=p2.id, ind3=p3.id, k1=1.1, k2=1.2, maxDist=1.6, elasticLaw="NeoHookean") +system.bonded_inter.add(ibm_tribend_bond) break_spec = espressomd.bond_breakage.BreakageSpec( breakage_length=5., action_type="delete_bond") system.bond_breakage[strong_harmonic_bond._bond_id] = break_spec checkpoint.register("system") -checkpoint.register("acc_mean_variance") -checkpoint.register("acc_time_series") -checkpoint.register("acc_correlator") checkpoint.register("ibm_volcons_bond") checkpoint.register("ibm_tribend_bond") checkpoint.register("ibm_triel_bond") checkpoint.register("break_spec") -checkpoint.register("p_slice") # calculate forces system.integrator.run(0) @@ -507,6 +505,10 @@ def test_checkpointing(self): local_obj = "local" # pylint: disable=unused-variable checkpoint.register("local_obj") + for obj_name in ["p_slice", "p3"]: + with self.assertRaisesRegex(TypeError, "cannot be checkpointed"): + checkpoint.register(obj_name) + @ut.skipIf(lbf_class is None, "Skipping test due to missing mode.") def test_lb_checkpointing_exceptions(self): ''' diff --git a/testsuite/python/script_interface.py b/testsuite/python/script_interface.py index 8848e45e18c..b93130a60fe 100644 --- a/testsuite/python/script_interface.py +++ b/testsuite/python/script_interface.py @@ -104,8 +104,7 @@ def test_autoparameter_exceptions(self): def test_objectlist_exceptions(self): """Check ObjectList framework""" - wall = espressomd.shapes.Wall(normal=[-1, 0, 0]) - constraint = espressomd.constraints.ShapeBasedConstraint(shape=wall) + constraint = espressomd.constraints.Gravity(g=[0., 0., 1.]) constraints = espressomd.constraints.Constraints() self.assertEqual(len(constraints), 0) constraints.add(constraint) diff --git a/testsuite/python/system.py b/testsuite/python/system.py new file mode 100644 index 00000000000..8cd0ac86255 --- /dev/null +++ b/testsuite/python/system.py @@ -0,0 +1,39 @@ +# +# Copyright (C) 2024 The ESPResSo project +# +# This file is part of ESPResSo. +# +# ESPResSo is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ESPResSo is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import unittest as ut +import espressomd + + +class Test(ut.TestCase): + + def test_system(self): + with self.assertRaisesRegex(ValueError, "Required argument 'box_l' not provided"): + espressomd.System() + with self.assertRaisesRegex(ValueError, "Property 'unknown' cannot be set via argument to System class"): + espressomd.System(box_l=[1., 1., 1.], unknown=1) + system = espressomd.System(box_l=[1., 1., 1.], min_global_cut=0.01) + self.assertEqual(system.min_global_cut, 0.01) + self.assertEqual(system.time_step, -1.) + with self.assertRaisesRegex(RuntimeError, "You can only have one instance of the system class at a time"): + espressomd.System(box_l=[1., 1., 1.]) + + +if __name__ == "__main__": + ut.main() diff --git a/testsuite/python/test_checkpoint.py b/testsuite/python/test_checkpoint.py index 9e58c6df973..7b49c2cb90a 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -475,11 +475,6 @@ def test_particle_properties(self): np.testing.assert_allclose(q[q_ind], r.as_quat(), atol=1e-10) np.testing.assert_allclose(np.copy(p2.vs_quat), [1., 0., 0., 0.]) - def test_part_slice(self): - np.testing.assert_allclose(np.copy(p_slice.id), [4, 1]) - np.testing.assert_allclose(np.copy(p_slice.pos), - np.copy(system.part.by_ids([4, 1]).pos)) - def test_bonded_interactions_serialization(self): ''' Check that particles at the interface between two MPI nodes still @@ -667,8 +662,6 @@ 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_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, @@ -677,13 +670,11 @@ def test_non_bonded_inter_lj(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) @@ -705,10 +696,12 @@ def test_bonded_inter(self): ibm_volcons_bond.params, {'softID': 15, 'kappaV': 0.01}) self.assertEqual( {**ibm_tribend_bond.params, **{'theta0': 0.}}, - {'kb': 2., 'theta0': 0., 'refShape': 'Initial'}) + {'kb': 2., 'refShape': 'Initial', 'is_initialized': True, + 'theta0': 0., 'ind1': 0, 'ind2': 1, 'ind3': 3, 'ind4': 4}) self.assertEqual( - ibm_triel_bond.params, - {'k1': 1.1, 'k2': 1.2, 'maxDist': 1.6, 'elasticLaw': 'NeoHookean'}) + {k: v for k, v in ibm_triel_bond.params.items() if k[0] != "_"}, + {'k1': 1.1, 'k2': 1.2, 'maxDist': 1.6, 'elasticLaw': 'NeoHookean', + 'ind1': 0, 'ind2': 1, 'ind3': 3, 'is_initialized': False}) # check new bonds can be added if not has_lb_mode: new_bond = espressomd.interactions.HarmonicBond(r_0=0.2, k=1.) @@ -764,30 +757,24 @@ def test_virtual_sites(self): p_real.vs_relative[2], [1., 0., 0., 0.], atol=1e-10) def test_mean_variance_calculator(self): + acc_mean_variance = system.auto_update_accumulators[0] np.testing.assert_array_equal( acc_mean_variance.mean(), np.array([[1.0, 1.5, 2.0], [1.0, 1.0, 2.0]])) np.testing.assert_array_equal( acc_mean_variance.variance(), np.array([[0., 0.5, 2.], [0., 0., 0.]])) - np.testing.assert_array_equal( - system.auto_update_accumulators[0].variance(), - np.array([[0., 0.5, 2.], [0., 0., 0.]])) def test_time_series(self): + acc_time_series = system.auto_update_accumulators[1] expected = [[[1, 1, 1], [1, 1, 2]], [[1, 2, 3], [1, 1, 2]]] np.testing.assert_array_equal(acc_time_series.time_series(), expected) - np.testing.assert_array_equal( - system.auto_update_accumulators[1].time_series(), - expected) def test_correlator(self): + acc_correlator = system.auto_update_accumulators[2] expected = np.zeros((36, 2, 3)) expected[0:2] = [[[1, 2.5, 5], [1, 1, 4]], [[1, 2, 3], [1, 1, 4]]] np.testing.assert_array_equal(acc_correlator.result(), expected) - np.testing.assert_array_equal( - system.auto_update_accumulators[2].result(), - expected) @utx.skipIfMissingFeatures('H5MD') @utx.skipIfMissingModules("h5py")