From 03f0ac02dacc8738d2ed595e5632bdc0238eb714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Tue, 4 Jul 2023 17:59:18 +0200 Subject: [PATCH 1/5] core: Encapsulate electrostatics globals The electrostatics solver is now a member of the System class. --- src/core/constraints/ShapeBasedConstraint.cpp | 7 +- src/core/electrostatics/actor.hpp | 6 +- src/core/electrostatics/coulomb.cpp | 79 +++++----- src/core/electrostatics/coulomb.hpp | 55 +------ src/core/electrostatics/coulomb_inline.hpp | 84 +++++------ src/core/electrostatics/icc.cpp | 18 +-- src/core/electrostatics/p3m.cpp | 21 +-- src/core/electrostatics/p3m_gpu.cpp | 3 +- src/core/electrostatics/registration.hpp | 26 ++-- src/core/electrostatics/solver.hpp | 136 ++++++++++++++++++ src/core/energy.cpp | 8 +- src/core/event.cpp | 42 +++--- src/core/forces.cpp | 14 +- src/core/interactions.cpp | 16 ++- src/core/magnetostatics/dipoles_inline.hpp | 1 + .../nonbonded_interaction_data.cpp | 3 +- src/core/nonbonded_interactions/thole.hpp | 2 +- src/core/npt.cpp | 3 +- src/core/pair_criteria/EnergyCriterion.hpp | 7 +- src/core/pressure.cpp | 8 +- src/core/system/System.cpp | 4 +- src/core/system/System.hpp | 3 + src/python/espressomd/system.py | 1 + 23 files changed, 336 insertions(+), 211 deletions(-) create mode 100644 src/core/electrostatics/solver.hpp diff --git a/src/core/constraints/ShapeBasedConstraint.cpp b/src/core/constraints/ShapeBasedConstraint.cpp index a8121142114..3d5ad6f286c 100644 --- a/src/core/constraints/ShapeBasedConstraint.cpp +++ b/src/core/constraints/ShapeBasedConstraint.cpp @@ -29,6 +29,7 @@ #include "forces_inline.hpp" #include "grid.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" +#include "system/System.hpp" #include "thermostat.hpp" #include @@ -80,7 +81,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 = Coulomb::pair_force_kernel(); + auto const &coulomb = System::get_system().coulomb; + auto const coulomb_kernel = coulomb.pair_force_kernel(); #ifdef DPD Utils::Vector3d dpd_force{}; @@ -137,7 +139,8 @@ void ShapeBasedConstraint::add_energy(const Particle &p, auto const &ia_params = get_ia_param(p.type(), part_rep.type()); if (checkIfInteraction(ia_params)) { - auto const coulomb_kernel = Coulomb::pair_energy_kernel(); + auto const &coulomb = System::get_system().coulomb; + auto const coulomb_kernel = coulomb.pair_energy_kernel(); double dist = 0.0; Utils::Vector3d vec; m_shape->calculate_dist(folded_pos, dist, vec); diff --git a/src/core/electrostatics/actor.hpp b/src/core/electrostatics/actor.hpp index 674c288ff0f..0db6dc56a4e 100644 --- a/src/core/electrostatics/actor.hpp +++ b/src/core/electrostatics/actor.hpp @@ -23,12 +23,12 @@ #ifdef ELECTROSTATICS +#include "electrostatics/solver.hpp" + #include namespace Coulomb { -void check_charge_neutrality(double excess_ratio); - template class Actor { public: static auto constexpr charge_neutrality_tolerance_default = 2e-12; @@ -51,7 +51,7 @@ template class Actor { void sanity_checks_charge_neutrality() const { if (charge_neutrality_tolerance != -1.) { - Coulomb::check_charge_neutrality(charge_neutrality_tolerance); + check_charge_neutrality(charge_neutrality_tolerance); } } }; diff --git a/src/core/electrostatics/coulomb.cpp b/src/core/electrostatics/coulomb.cpp index 0f4a5860306..ee6271af945 100644 --- a/src/core/electrostatics/coulomb.cpp +++ b/src/core/electrostatics/coulomb.cpp @@ -32,7 +32,7 @@ #include "errorhandling.hpp" #include "integrate.hpp" #include "npt.hpp" -#include "partCfg_global.hpp" +#include "system/System.hpp" #include #include @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include #include @@ -55,45 +57,42 @@ #include #include -boost::optional electrostatics_actor; -boost::optional electrostatics_extension; - namespace Coulomb { -void sanity_checks() { - if (electrostatics_actor) { - boost::apply_visitor([](auto &actor) { actor->sanity_checks(); }, - *electrostatics_actor); +Solver const &get_coulomb() { return System::get_system().coulomb; } + +void Solver::sanity_checks() const { + if (solver) { + boost::apply_visitor([](auto const &actor) { actor->sanity_checks(); }, + *solver); } } -void on_coulomb_change() { - visit_active_actor_try_catch([](auto &actor) { actor->init(); }, - electrostatics_actor); +void Solver::on_coulomb_change() { + reinit_on_observable_calc = true; + visit_active_actor_try_catch([](auto &actor) { actor->init(); }, solver); } -void on_boxl_change() { +void Solver::on_boxl_change() { visit_active_actor_try_catch([](auto &actor) { actor->on_boxl_change(); }, - electrostatics_actor); + solver); } -void on_node_grid_change() { - if (electrostatics_actor) { +void Solver::on_node_grid_change() { + if (solver) { boost::apply_visitor([](auto &actor) { actor->on_node_grid_change(); }, - *electrostatics_actor); + *solver); } } -void on_periodicity_change() { +void Solver::on_periodicity_change() { visit_active_actor_try_catch( - [](auto &actor) { actor->on_periodicity_change(); }, - electrostatics_actor); + [](auto &actor) { actor->on_periodicity_change(); }, solver); } -void on_cell_structure_change() { +void Solver::on_cell_structure_change() { visit_active_actor_try_catch( - [](auto &actor) { actor->on_cell_structure_change(); }, - electrostatics_actor); + [](auto &actor) { actor->on_cell_structure_change(); }, solver); } struct LongRangePressure : public boost::static_visitor { @@ -127,10 +126,10 @@ struct LongRangePressure : public boost::static_visitor { ParticleRange const &m_particles; }; -Utils::Vector9d calc_pressure_long_range(ParticleRange const &particles) { - if (electrostatics_actor) { - return boost::apply_visitor(LongRangePressure(particles), - *electrostatics_actor); +Utils::Vector9d +Solver::calc_pressure_long_range(ParticleRange const &particles) const { + if (solver) { + return boost::apply_visitor(LongRangePressure(particles), *solver); } return {}; } @@ -167,9 +166,9 @@ struct ShortRangeCutoff : public boost::static_visitor { } }; -double cutoff() { - if (electrostatics_actor) { - return boost::apply_visitor(ShortRangeCutoff(), *electrostatics_actor); +double Solver::cutoff() const { + if (solver) { + return boost::apply_visitor(ShortRangeCutoff(), *solver); } return -1.0; } @@ -188,9 +187,12 @@ struct EventOnObservableCalc : public boost::static_visitor { #endif // P3M }; -void on_observable_calc() { - if (electrostatics_actor) { - boost::apply_visitor(EventOnObservableCalc(), *electrostatics_actor); +void Solver::on_observable_calc() { + if (reinit_on_observable_calc) { + if (solver) { + boost::apply_visitor(EventOnObservableCalc(), *solver); + } + reinit_on_observable_calc = false; } } @@ -279,16 +281,15 @@ struct LongRangeEnergy : public boost::static_visitor { ParticleRange const &m_particles; }; -void calc_long_range_force(ParticleRange const &particles) { - if (electrostatics_actor) { - boost::apply_visitor(LongRangeForce(particles), *electrostatics_actor); +void Solver::calc_long_range_force(ParticleRange const &particles) const { + if (solver) { + boost::apply_visitor(LongRangeForce(particles), *solver); } } -double calc_energy_long_range(ParticleRange const &particles) { - if (electrostatics_actor) { - return boost::apply_visitor(LongRangeEnergy(particles), - *electrostatics_actor); +double Solver::calc_energy_long_range(ParticleRange const &particles) const { + if (solver) { + return boost::apply_visitor(LongRangeEnergy(particles), *solver); } return 0.; } diff --git a/src/core/electrostatics/coulomb.hpp b/src/core/electrostatics/coulomb.hpp index 1d0b831251e..b8370f2cc32 100644 --- a/src/core/electrostatics/coulomb.hpp +++ b/src/core/electrostatics/coulomb.hpp @@ -25,6 +25,8 @@ #include "actor/traits.hpp" +#include "electrostatics/solver.hpp" + #include "electrostatics/debye_hueckel.hpp" #include "electrostatics/elc.hpp" #include "electrostatics/icc.hpp" @@ -47,29 +49,6 @@ #include #include -using ElectrostaticsActor = - boost::variant, -#ifdef P3M - std::shared_ptr, -#ifdef CUDA - std::shared_ptr, -#endif // CUDA - std::shared_ptr, -#endif // P3M - std::shared_ptr, -#ifdef MMM1D_GPU - std::shared_ptr, -#endif // MMM1D_GPU -#ifdef SCAFACOS - std::shared_ptr, -#endif // SCAFACOS - std::shared_ptr>; - -using ElectrostaticsExtension = boost::variant>; - -extern boost::optional electrostatics_actor; -extern boost::optional electrostatics_extension; - /** Get the electrostatics prefactor. */ struct GetCoulombPrefactor : public boost::static_visitor { template @@ -79,7 +58,6 @@ struct GetCoulombPrefactor : public boost::static_visitor { }; namespace Coulomb { - namespace traits { #ifdef P3M @@ -113,35 +91,6 @@ template <> struct has_pressure : std::false_type {}; template <> struct has_pressure : std::false_type {}; } // namespace traits - -/** @brief Check if the system is charge-neutral. */ -void check_charge_neutrality(double relative_tolerance); - -Utils::Vector9d calc_pressure_long_range(ParticleRange const &particles); - -void sanity_checks(); -double cutoff(); - -void on_observable_calc(); -void on_coulomb_change(); -void on_boxl_change(); -void on_node_grid_change(); -void on_periodicity_change(); -void on_cell_structure_change(); - -void calc_long_range_force(ParticleRange const &particles); -double calc_energy_long_range(ParticleRange const &particles); - -namespace detail { -bool flag_all_reduce(bool flag); -} // namespace detail - -} // namespace Coulomb -#else // ELECTROSTATICS -#include -namespace Coulomb { -constexpr std::size_t pressure_n() { return 0; } -constexpr std::size_t energy_n() { return 0; } } // namespace Coulomb #endif // ELECTROSTATICS #endif diff --git a/src/core/electrostatics/coulomb_inline.hpp b/src/core/electrostatics/coulomb_inline.hpp index 75c8404b571..a032acf9771 100644 --- a/src/core/electrostatics/coulomb_inline.hpp +++ b/src/core/electrostatics/coulomb_inline.hpp @@ -23,6 +23,7 @@ #include "config/config.hpp" #include "electrostatics/coulomb.hpp" +#include "electrostatics/solver.hpp" #include "Particle.hpp" @@ -31,6 +32,9 @@ #include #include +#include +#include + #include #include #include @@ -39,8 +43,8 @@ namespace Coulomb { struct ShortRangeForceKernel - : public boost::static_visitor>> { + : public boost::static_visitor< + boost::optional> { using kernel_type = result_type::value_type; @@ -70,8 +74,8 @@ struct ShortRangeForceKernel }; struct ShortRangeForceCorrectionsKernel - : public boost::static_visitor>> { + : public boost::static_visitor< + boost::optional> { using kernel_type = result_type::value_type; @@ -91,19 +95,9 @@ struct ShortRangeForceCorrectionsKernel #endif // P3M }; -inline ShortRangeForceKernel::result_type pair_force_kernel() { -#ifdef ELECTROSTATICS - if (electrostatics_actor) { - auto const visitor = ShortRangeForceKernel(); - return boost::apply_visitor(visitor, *electrostatics_actor); - } -#endif // ELECTROSTATICS - return {}; -} - struct ShortRangePressureKernel - : public boost::static_visitor(double, Utils::Vector3d const &, double)>>> { + : public boost::static_visitor< + boost::optional> { using kernel_type = result_type::value_type; @@ -111,16 +105,11 @@ struct ShortRangePressureKernel template ::value> * = nullptr> result_type operator()(std::shared_ptr const &ptr) const { - result_type pressure_kernel = {}; - if (auto const force_kernel_opt = pair_force_kernel()) { - pressure_kernel = - kernel_type{[force_kernel = *force_kernel_opt]( - double q1q2, Utils::Vector3d const &d, double dist) { - auto const force = force_kernel(q1q2, d, dist); - return Utils::tensor_product(force, d); - }}; - } - return pressure_kernel; + auto const &actor = *ptr; + return kernel_type{ + [&actor](double q1q2, Utils::Vector3d const &d, double dist) { + return Utils::tensor_product(actor.pair_force(q1q2, d, dist), d); + }}; } template >> { + : public boost::static_visitor< + boost::optional> { using kernel_type = result_type::value_type; @@ -177,31 +165,45 @@ struct ShortRangeEnergyKernel #endif // ELECTROSTATICS }; -inline ShortRangeForceCorrectionsKernel::result_type pair_force_elc_kernel() { +inline boost::optional +Solver::pair_force_kernel() const { +#ifdef ELECTROSTATICS + if (solver) { + auto const visitor = Coulomb::ShortRangeForceKernel(); + return boost::apply_visitor(visitor, *solver); + } +#endif // ELECTROSTATICS + return {}; +} + +inline boost::optional +Solver::pair_force_elc_kernel() const { #ifdef ELECTROSTATICS - if (electrostatics_actor) { - auto const visitor = ShortRangeForceCorrectionsKernel(); - return boost::apply_visitor(visitor, *electrostatics_actor); + if (solver) { + auto const visitor = Coulomb::ShortRangeForceCorrectionsKernel(); + return boost::apply_visitor(visitor, *solver); } #endif // ELECTROSTATICS return {}; } -inline ShortRangePressureKernel::result_type pair_pressure_kernel() { +inline boost::optional +Solver::pair_pressure_kernel() const { #ifdef ELECTROSTATICS - if (electrostatics_actor) { - auto const visitor = ShortRangePressureKernel(); - return boost::apply_visitor(visitor, *electrostatics_actor); + if (solver) { + auto const visitor = Coulomb::ShortRangePressureKernel(); + return boost::apply_visitor(visitor, *solver); } #endif // ELECTROSTATICS return {}; } -inline ShortRangeEnergyKernel::result_type pair_energy_kernel() { +inline boost::optional +Solver::pair_energy_kernel() const { #ifdef ELECTROSTATICS - if (electrostatics_actor) { - auto const visitor = ShortRangeEnergyKernel(); - return boost::apply_visitor(visitor, *electrostatics_actor); + if (solver) { + auto const visitor = Coulomb::ShortRangeEnergyKernel(); + return boost::apply_visitor(visitor, *solver); } #endif // ELECTROSTATICS return {}; diff --git a/src/core/electrostatics/icc.cpp b/src/core/electrostatics/icc.cpp index 778c3dcf5bf..55eaf2a2b57 100644 --- a/src/core/electrostatics/icc.cpp +++ b/src/core/electrostatics/icc.cpp @@ -93,7 +93,7 @@ static void force_calc_icc( } }); - Coulomb::calc_long_range_force(particles); + Coulomb::get_coulomb().calc_long_range_force(particles); } void ICCStar::iteration(CellStructure &cell_structure, @@ -107,11 +107,11 @@ void ICCStar::iteration(CellStructure &cell_structure, return; } - auto const prefactor = - boost::apply_visitor(GetCoulombPrefactor(), *electrostatics_actor); + auto const prefactor = boost::apply_visitor(GetCoulombPrefactor(), + *(Coulomb::get_coulomb().solver)); auto const pref = 1. / (prefactor * 2. * Utils::pi()); - auto const kernel = Coulomb::pair_force_kernel(); - auto const elc_kernel = Coulomb::pair_force_elc_kernel(); + auto const kernel = Coulomb::get_coulomb().pair_force_kernel(); + auto const elc_kernel = Coulomb::get_coulomb().pair_force_elc_kernel(); icc_cfg.citeration = 0; auto global_max_rel_diff = 0.; @@ -273,17 +273,17 @@ void ICCStar::sanity_check() const { } void ICCStar::sanity_checks_active_solver() const { - if (electrostatics_actor) { - boost::apply_visitor(SanityChecksICC(), *electrostatics_actor); + if (Coulomb::get_coulomb().solver) { + boost::apply_visitor(SanityChecksICC(), *(Coulomb::get_coulomb().solver)); } else { throw std::runtime_error("An electrostatics solver is needed by ICC"); } } void update_icc_particles() { - if (electrostatics_extension) { + if (Coulomb::get_coulomb().extension) { if (auto icc = boost::get>( - electrostatics_extension.get_ptr())) { + Coulomb::get_coulomb().extension.get_ptr())) { (**icc).iteration(cell_structure, cell_structure.local_particles(), cell_structure.ghost_particles()); } diff --git a/src/core/electrostatics/p3m.cpp b/src/core/electrostatics/p3m.cpp index c954125b5ee..6eb12ae534b 100644 --- a/src/core/electrostatics/p3m.cpp +++ b/src/core/electrostatics/p3m.cpp @@ -243,10 +243,10 @@ void CoulombP3M::init() { sanity_checks(); + auto const &solver = Coulomb::get_coulomb().solver; double elc_layer = 0.; - if (auto elc_actor = get_actor_by_type( - electrostatics_actor)) { - elc_layer = elc_actor->elc.space_layer; + if (auto actor = get_actor_by_type(solver)) { + elc_layer = actor->elc.space_layer; } p3m.local_mesh.calc_local_ca_mesh(p3m.params, local_geo, skin, elc_layer); @@ -566,7 +566,8 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { void setup_logger(bool verbose) override { #ifdef CUDA - auto const on_gpu = has_actor_of_type(electrostatics_actor); + auto const &solver = Coulomb::get_coulomb().solver; + auto const on_gpu = has_actor_of_type(solver); #else auto const on_gpu = false; #endif @@ -580,9 +581,9 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { boost::optional layer_correction_veto_r_cut(double r_cut) const override { - if (auto elc_actor = get_actor_by_type( - electrostatics_actor)) { - return elc_actor->veto_r_cut(r_cut); + auto const &solver = Coulomb::get_coulomb().solver; + if (auto actor = get_actor_by_type(solver)) { + return actor->veto_r_cut(r_cut); } return {}; } @@ -612,7 +613,8 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { rs_err = p3m_real_space_error(m_prefactor, r_cut_iL, p3m.sum_qpart, p3m.sum_q2, alpha_L); #ifdef CUDA - if (has_actor_of_type(electrostatics_actor)) { + auto const &solver = Coulomb::get_coulomb().solver; + if (has_actor_of_type(solver)) { ks_err = p3mgpu_k_space_error(m_prefactor, mesh, cao, p3m.sum_qpart, p3m.sum_q2, alpha_L); } else @@ -659,6 +661,7 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { auto tuned_params = TuningAlgorithm::Parameters{}; auto time_best = time_sentinel; auto mesh_density = m_mesh_density_min; + auto const &solver = Coulomb::get_coulomb().solver; while (mesh_density <= m_mesh_density_max) { auto trial_params = TuningAlgorithm::Parameters{}; if (m_tune_mesh) { @@ -680,7 +683,7 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { if (trial_time >= 0.) { /* the optimum r_cut for this mesh is the upper limit for higher meshes, everything else is slower */ - if (has_actor_of_type(electrostatics_actor)) { + if (has_actor_of_type(solver)) { m_r_cut_iL_max = trial_params.r_cut_iL; } diff --git a/src/core/electrostatics/p3m_gpu.cpp b/src/core/electrostatics/p3m_gpu.cpp index 8e223f6b477..d085132ea8d 100644 --- a/src/core/electrostatics/p3m_gpu.cpp +++ b/src/core/electrostatics/p3m_gpu.cpp @@ -41,7 +41,8 @@ void CoulombP3MGPU::add_long_range_forces(ParticleRange const &) { } void CoulombP3MGPU::init() { - if (has_actor_of_type(electrostatics_actor)) { + if (has_actor_of_type( + Coulomb::get_coulomb().solver)) { init_cpu_kernels(); } p3m_gpu_init(p3m.params.cao, p3m.params.mesh.data(), p3m.params.alpha); diff --git a/src/core/electrostatics/registration.hpp b/src/core/electrostatics/registration.hpp index 7e14fab16a0..4ccb7ae23e5 100644 --- a/src/core/electrostatics/registration.hpp +++ b/src/core/electrostatics/registration.hpp @@ -16,6 +16,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + #ifndef ESPRESSO_SRC_CORE_ELECTROSTATICS_COULOMB_REGISTRATION_HPP #define ESPRESSO_SRC_CORE_ELECTROSTATICS_COULOMB_REGISTRATION_HPP @@ -29,6 +30,7 @@ #include "actor/visitors.hpp" #include "event.hpp" +#include "system/System.hpp" #include #include @@ -41,44 +43,48 @@ namespace Coulomb { template ::value> * = nullptr> void add_actor(std::shared_ptr const &actor) { - if (::electrostatics_actor) { - auto const name = get_actor_name(*::electrostatics_actor); + auto &system = System::get_system(); + if (system.coulomb.solver) { + auto const name = get_actor_name(*system.coulomb.solver); throw std::runtime_error("An electrostatics solver is already active (" + name + ")"); } - add_actor(::electrostatics_actor, actor, ::on_coulomb_change, + add_actor(system.coulomb.solver, actor, ::on_coulomb_change, detail::flag_all_reduce); } template ::value> * = nullptr> void remove_actor(std::shared_ptr const &actor) { - if (not is_already_stored(actor, electrostatics_actor)) { + auto &system = System::get_system(); + if (not is_already_stored(actor, system.coulomb.solver)) { throw std::runtime_error( "The given electrostatics solver is not currently active"); } - remove_actor(::electrostatics_actor, actor, ::on_coulomb_change); + remove_actor(system.coulomb.solver, actor, ::on_coulomb_change); } template ::value> * = nullptr> void add_actor(std::shared_ptr const &actor) { - if (::electrostatics_extension) { - auto const name = get_actor_name(*::electrostatics_extension); + auto &system = System::get_system(); + if (system.coulomb.extension) { + auto const name = get_actor_name(*system.coulomb.extension); throw std::runtime_error("An electrostatics extension is already active (" + name + ")"); } - add_actor(::electrostatics_extension, actor, ::on_coulomb_change, + add_actor(system.coulomb.extension, actor, ::on_coulomb_change, detail::flag_all_reduce); } template ::value> * = nullptr> void remove_actor(std::shared_ptr const &actor) { - if (not is_already_stored(actor, ::electrostatics_extension)) { + auto &system = System::get_system(); + if (not is_already_stored(actor, system.coulomb.extension)) { throw std::runtime_error( "The given electrostatics extension is not currently active"); } - remove_actor(::electrostatics_extension, actor, ::on_coulomb_change); + remove_actor(system.coulomb.extension, actor, ::on_coulomb_change); } } // namespace Coulomb diff --git a/src/core/electrostatics/solver.hpp b/src/core/electrostatics/solver.hpp new file mode 100644 index 00000000000..aeeea6ea09b --- /dev/null +++ b/src/core/electrostatics/solver.hpp @@ -0,0 +1,136 @@ +/* + * 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 "config/config.hpp" + +#include "actor/traits.hpp" + +#include "Particle.hpp" +#include "ParticleRange.hpp" + +#include + +#include +#include + +#include +#include +#include + +#ifdef ELECTROSTATICS +// forward declarations +struct DebyeHueckel; +struct ReactionField; +struct ICCStar; +#ifdef P3M +struct CoulombP3M; +#ifdef CUDA +struct CoulombP3MGPU; +#endif +struct ElectrostaticLayerCorrection; +#endif +struct CoulombMMM1D; +#ifdef MMM1D_GPU +class CoulombMMM1DGpu; +#endif +#ifdef SCAFACOS +struct CoulombScafacos; +#endif + +using ElectrostaticsActor = + boost::variant, +#ifdef P3M + std::shared_ptr, +#ifdef CUDA + std::shared_ptr, +#endif // CUDA + std::shared_ptr, +#endif // P3M + std::shared_ptr, +#ifdef MMM1D_GPU + std::shared_ptr, +#endif // MMM1D_GPU +#ifdef SCAFACOS + std::shared_ptr, +#endif // SCAFACOS + std::shared_ptr>; + +using ElectrostaticsExtension = boost::variant>; +#endif // ELECTROSTATICS + +namespace Coulomb { + +namespace detail { +bool flag_all_reduce(bool flag); +} // namespace detail + +struct Solver { +#ifdef ELECTROSTATICS + /// @brief Main electrostatics solver. + boost::optional solver; + /// @brief Extension that modifies the solver behavior. + boost::optional extension; + /// @brief Whether to reinitialize the solver on observable calculation. + bool reinit_on_observable_calc = false; + + Utils::Vector9d + calc_pressure_long_range(ParticleRange const &particles) const; + + void sanity_checks() const; + double cutoff() const; + + void on_observable_calc(); + void on_coulomb_change(); + void on_boxl_change(); + void on_node_grid_change(); + void on_periodicity_change(); + void on_cell_structure_change(); + void on_particle_change() { reinit_on_observable_calc = true; } + + void calc_long_range_force(ParticleRange const &particles) const; + double calc_energy_long_range(ParticleRange const &particles) const; +#endif // ELECTROSTATICS + + using ShortRangeForceKernel = + std::function; + using ShortRangeForceCorrectionsKernel = + std::function; + using ShortRangePressureKernel = std::function( + double, Utils::Vector3d const &, double)>; + using ShortRangeEnergyKernel = + std::function; + + inline boost::optional pair_force_kernel() const; + inline boost::optional pair_pressure_kernel() const; + inline boost::optional pair_energy_kernel() const; + inline boost::optional + pair_force_elc_kernel() const; +}; + +#ifdef ELECTROSTATICS +Solver const &get_coulomb(); +#endif + +/** @brief Check if the system is charge-neutral. */ +void check_charge_neutrality(double relative_tolerance); + +} // namespace Coulomb diff --git a/src/core/energy.cpp b/src/core/energy.cpp index 86f0a2578c3..9501282d385 100644 --- a/src/core/energy.cpp +++ b/src/core/energy.cpp @@ -64,7 +64,8 @@ std::shared_ptr calculate_energy() { obs_energy.kinetic[0] += calc_kinetic_energy(p); } - auto const coulomb_kernel = Coulomb::pair_energy_kernel(); + auto const &system = System::get_system(); + auto const coulomb_kernel = system.coulomb.pair_energy_kernel(); auto const dipoles_kernel = Dipoles::pair_energy_kernel(); short_range_loop( @@ -90,7 +91,7 @@ std::shared_ptr calculate_energy() { #ifdef ELECTROSTATICS /* calculate k-space part of electrostatic interaction. */ - obs_energy.coulomb[1] = Coulomb::calc_energy_long_range(local_parts); + obs_energy.coulomb[1] = system.coulomb.calc_energy_long_range(local_parts); #endif #ifdef DIPOLES @@ -133,7 +134,8 @@ double particle_short_range_energy_contribution(int pid) { } if (auto const p = cell_structure.get_local_particle(pid)) { - auto const coulomb_kernel = Coulomb::pair_energy_kernel(); + auto const &coulomb = System::get_system().coulomb; + auto const coulomb_kernel = coulomb.pair_energy_kernel(); auto kernel = [&ret, coulomb_kernel_ptr = coulomb_kernel.get_ptr()]( Particle const &p, Particle const &p1, Utils::Vector3d const &vec) { diff --git a/src/core/event.cpp b/src/core/event.cpp index 9c6997b3196..a1b248172f2 100644 --- a/src/core/event.cpp +++ b/src/core/event.cpp @@ -46,6 +46,7 @@ #include "npt.hpp" #include "partCfg_global.hpp" #include "particle_node.hpp" +#include "system/System.hpp" #include "thermostat.hpp" #include "virtual_sites.hpp" @@ -55,10 +56,6 @@ /** whether the thermostat has to be reinitialized before integration */ static bool reinit_thermo = true; -#ifdef ELECTROSTATICS -/** whether electrostatics actor has to be reinitialized on observable calc */ -static bool reinit_electrostatics = false; -#endif #ifdef DIPOLES /** whether magnetostatics actor has to be reinitialized on observable calc */ static bool reinit_magnetostatics = false; @@ -120,7 +117,7 @@ void on_integration_start(double time_step) { } #ifdef ELECTROSTATICS { - auto const &actor = electrostatics_actor; + auto const &actor = System::get_system().coulomb.solver; if (!Utils::Mpi::all_compare(comm_cart, static_cast(actor)) or (actor and !Utils::Mpi::all_compare(comm_cart, (*actor).which()))) runtimeErrorMsg() << "Nodes disagree about Coulomb long-range method"; @@ -144,12 +141,10 @@ void on_observable_calc() { * information */ cells_update_ghosts(global_ghost_flags()); update_dependent_particles(); + #ifdef ELECTROSTATICS - if (reinit_electrostatics) { - Coulomb::on_observable_calc(); - reinit_electrostatics = false; - } -#endif /* ELECTROSTATICS */ + System::get_system().coulomb.on_observable_calc(); +#endif #ifdef DIPOLES if (reinit_magnetostatics) { @@ -163,7 +158,7 @@ void on_observable_calc() { void on_particle_charge_change() { #ifdef ELECTROSTATICS - reinit_electrostatics = true; + System::get_system().coulomb.on_particle_change(); #endif /* the particle information is no longer valid */ @@ -178,7 +173,9 @@ void on_particle_change() { cell_structure.set_resort_particles(Cells::RESORT_LOCAL); } #ifdef ELECTROSTATICS - reinit_electrostatics = true; + if (System::is_system_set()) { + System::get_system().coulomb.on_particle_change(); + } #endif #ifdef DIPOLES reinit_magnetostatics = true; @@ -194,8 +191,7 @@ void on_particle_change() { void on_coulomb_and_dipoles_change() { #ifdef ELECTROSTATICS - reinit_electrostatics = true; - Coulomb::on_coulomb_change(); + System::get_system().coulomb.on_coulomb_change(); #endif #ifdef DIPOLES reinit_magnetostatics = true; @@ -206,8 +202,7 @@ void on_coulomb_and_dipoles_change() { void on_coulomb_change() { #ifdef ELECTROSTATICS - reinit_electrostatics = true; - Coulomb::on_coulomb_change(); + System::get_system().coulomb.on_coulomb_change(); #endif on_short_range_ia_change(); } @@ -243,7 +238,7 @@ void on_boxl_change(bool skip_method_adaption) { if (not skip_method_adaption) { /* Now give methods a chance to react to the change in box length */ #ifdef ELECTROSTATICS - Coulomb::on_boxl_change(); + System::get_system().coulomb.on_boxl_change(); #endif #ifdef DIPOLES @@ -267,12 +262,15 @@ void on_cell_structure_change() { /* Now give methods a chance to react to the change in cell structure. * Most ES methods need to reinitialize, as they depend on skin, * node grid and so on. */ +#if defined(ELECTROSTATICS) or defined(DIPOLES) + if (System::is_system_set()) { #ifdef ELECTROSTATICS - Coulomb::on_cell_structure_change(); + System::get_system().coulomb.on_cell_structure_change(); #endif - #ifdef DIPOLES - Dipoles::on_cell_structure_change(); + Dipoles::on_cell_structure_change(); +#endif + } #endif } @@ -284,7 +282,7 @@ void on_temperature_change() { void on_periodicity_change() { #ifdef ELECTROSTATICS - Coulomb::on_periodicity_change(); + System::get_system().coulomb.on_periodicity_change(); #endif #ifdef DIPOLES @@ -320,7 +318,7 @@ void on_forcecap_change() { recalc_forces = true; } void on_node_grid_change() { grid_changed_n_nodes(); #ifdef ELECTROSTATICS - Coulomb::on_node_grid_change(); + System::get_system().coulomb.on_node_grid_change(); #endif #ifdef DIPOLES Dipoles::on_node_grid_change(); diff --git a/src/core/forces.cpp b/src/core/forces.cpp index 21c6e9d1c6f..4a6bca2b7f4 100644 --- a/src/core/forces.cpp +++ b/src/core/forces.cpp @@ -145,8 +145,8 @@ void init_forces_ghosts(const ParticleRange &particles) { void force_calc(CellStructure &cell_structure, double time_step, double kT) { ESPRESSO_PROFILER_CXX_MARK_FUNCTION; -#ifdef CUDA auto &espresso_system = System::get_system(); +#ifdef CUDA espresso_system.gpu.update(); #endif @@ -157,9 +157,9 @@ void force_calc(CellStructure &cell_structure, double time_step, double kT) { auto particles = cell_structure.local_particles(); auto ghost_particles = cell_structure.ghost_particles(); #ifdef ELECTROSTATICS - if (electrostatics_extension) { + if (espresso_system.coulomb.extension) { if (auto icc = boost::get>( - electrostatics_extension.get_ptr())) { + espresso_system.coulomb.extension.get_ptr())) { (**icc).iteration(cell_structure, particles, ghost_particles); } } @@ -168,12 +168,12 @@ void force_calc(CellStructure &cell_structure, double time_step, double kT) { calc_long_range_forces(particles); - auto const elc_kernel = Coulomb::pair_force_elc_kernel(); - auto const coulomb_kernel = Coulomb::pair_force_kernel(); + auto const elc_kernel = espresso_system.coulomb.pair_force_elc_kernel(); + auto const coulomb_kernel = espresso_system.coulomb.pair_force_kernel(); auto const dipoles_kernel = Dipoles::pair_force_kernel(); #ifdef ELECTROSTATICS - auto const coulomb_cutoff = Coulomb::cutoff(); + auto const coulomb_cutoff = espresso_system.coulomb.cutoff(); #else auto const coulomb_cutoff = INACTIVE_CUTOFF; #endif @@ -255,7 +255,7 @@ void calc_long_range_forces(const ParticleRange &particles) { ESPRESSO_PROFILER_CXX_MARK_FUNCTION; #ifdef ELECTROSTATICS /* calculate k-space part of electrostatic interaction. */ - Coulomb::calc_long_range_force(particles); + Coulomb::get_coulomb().calc_long_range_force(particles); #endif // ELECTROSTATICS diff --git a/src/core/interactions.cpp b/src/core/interactions.cpp index 6ba4a77ee67..9e4482c8f07 100644 --- a/src/core/interactions.cpp +++ b/src/core/interactions.cpp @@ -27,6 +27,7 @@ #include "event.hpp" #include "magnetostatics/dipoles.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" +#include "system/System.hpp" #include @@ -34,12 +35,16 @@ static double recalc_long_range_cutoff() { auto max_cut_long_range = INACTIVE_CUTOFF; +#if defined(ELECTROSTATICS) or defined(DIPOLES) + if (System::is_system_set()) { + auto &system = System::get_system(); #ifdef ELECTROSTATICS - max_cut_long_range = std::max(max_cut_long_range, Coulomb::cutoff()); + max_cut_long_range = std::max(max_cut_long_range, system.coulomb.cutoff()); #endif - #ifdef DIPOLES - max_cut_long_range = std::max(max_cut_long_range, Dipoles::cutoff()); + max_cut_long_range = std::max(max_cut_long_range, Dipoles::cutoff()); +#endif + } #endif return max_cut_long_range; @@ -63,9 +68,12 @@ double maximal_cutoff(bool single_node) { } bool long_range_interactions_sanity_checks() { +#if defined(ELECTROSTATICS) or defined(DIPOLES) + auto &system = System::get_system(); +#endif try { #ifdef ELECTROSTATICS - Coulomb::sanity_checks(); + system.coulomb.sanity_checks(); #endif #ifdef DIPOLES Dipoles::sanity_checks(); diff --git a/src/core/magnetostatics/dipoles_inline.hpp b/src/core/magnetostatics/dipoles_inline.hpp index d4fe040f499..65f179e42cd 100644 --- a/src/core/magnetostatics/dipoles_inline.hpp +++ b/src/core/magnetostatics/dipoles_inline.hpp @@ -33,6 +33,7 @@ #include #include +#include #include diff --git a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp index 0644de67fb8..8632d8a82f6 100644 --- a/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp +++ b/src/core/nonbonded_interactions/nonbonded_interaction_data.cpp @@ -145,7 +145,8 @@ static double recalc_maximal_cutoff(const IA_parameters &data) { #ifdef THOLE // If THOLE is active, use p3m cutoff if (data.thole.scaling_coeff != 0.) - max_cut_current = std::max(max_cut_current, Coulomb::cutoff()); + max_cut_current = + std::max(max_cut_current, Coulomb::get_coulomb().cutoff()); #endif return max_cut_current; diff --git a/src/core/nonbonded_interactions/thole.hpp b/src/core/nonbonded_interactions/thole.hpp index 2d41c57c298..dfc9998354b 100644 --- a/src/core/nonbonded_interactions/thole.hpp +++ b/src/core/nonbonded_interactions/thole.hpp @@ -73,7 +73,7 @@ thole_pair_energy(Particle const &p1, Particle const &p2, auto const thole_q1q2 = ia_params.thole.q1q2; if (thole_s != 0. and thole_q1q2 != 0. and kernel != nullptr and - dist < Coulomb::cutoff() and + dist < Coulomb::get_coulomb().cutoff() and !(pair_bond_enum_exists_between(p1, p2))) { auto const sd = thole_s * dist; auto const S_r = 1.0 - (1.0 + sd / 2.0) * exp(-sd); diff --git a/src/core/npt.cpp b/src/core/npt.cpp index 6b40edc3fc7..c56f136fc4c 100644 --- a/src/core/npt.cpp +++ b/src/core/npt.cpp @@ -27,6 +27,7 @@ #include "event.hpp" #include "integrate.hpp" #include "magnetostatics/dipoles.hpp" +#include "system/System.hpp" #include @@ -48,7 +49,7 @@ void synchronize_npt_state() { void NptIsoParameters::coulomb_dipole_sanity_checks() const { #ifdef ELECTROSTATICS - if (dimension < 3 && !cubic_box && electrostatics_actor) { + if (dimension < 3 and not cubic_box and System::get_system().coulomb.solver) { throw std::runtime_error("If electrostatics is being used you must " "use the cubic box NpT."); } diff --git a/src/core/pair_criteria/EnergyCriterion.hpp b/src/core/pair_criteria/EnergyCriterion.hpp index c84cba12d9a..aa4b00ac603 100644 --- a/src/core/pair_criteria/EnergyCriterion.hpp +++ b/src/core/pair_criteria/EnergyCriterion.hpp @@ -21,7 +21,11 @@ #include "pair_criteria/PairCriterion.hpp" +#include "Particle.hpp" #include "energy_inline.hpp" +#include "grid.hpp" +#include "nonbonded_interactions/nonbonded_interaction_data.hpp" +#include "system/System.hpp" namespace PairCriteria { /** @@ -35,7 +39,8 @@ class EnergyCriterion : public PairCriterion { // Interaction parameters for particle types auto const &ia_params = get_ia_param(p1.type(), p2.type()); - auto const coulomb_kernel = Coulomb::pair_energy_kernel(); + auto const &coulomb = System::get_system().coulomb; + auto const coulomb_kernel = coulomb.pair_energy_kernel(); auto const energy = calc_non_bonded_pair_energy( p1, p2, ia_params, d, d.norm(), coulomb_kernel.get_ptr()); diff --git a/src/core/pressure.cpp b/src/core/pressure.cpp index 5b770bf8ae1..a922ba87dc0 100644 --- a/src/core/pressure.cpp +++ b/src/core/pressure.cpp @@ -37,6 +37,7 @@ #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include "pressure_inline.hpp" #include "short_range_loop.hpp" +#include "system/System.hpp" #include "virtual_sites.hpp" #include "config/config.hpp" @@ -71,8 +72,9 @@ std::shared_ptr calculate_pressure() { add_kinetic_virials(p, obs_pressure); } - auto const coulomb_force_kernel = Coulomb::pair_force_kernel(); - auto const coulomb_pressure_kernel = Coulomb::pair_pressure_kernel(); + auto const &coulomb = System::get_system().coulomb; + auto const coulomb_force_kernel = coulomb.pair_force_kernel(); + auto const coulomb_pressure_kernel = coulomb.pair_pressure_kernel(); short_range_loop( [&obs_pressure, @@ -104,7 +106,7 @@ std::shared_ptr calculate_pressure() { #ifdef ELECTROSTATICS /* calculate k-space part of electrostatic interaction. */ - auto const coulomb_pressure = Coulomb::calc_pressure_long_range(local_parts); + auto const coulomb_pressure = coulomb.calc_pressure_long_range(local_parts); boost::copy(coulomb_pressure, obs_pressure.coulomb.begin() + 9); #endif #ifdef DIPOLES diff --git a/src/core/system/System.cpp b/src/core/system/System.cpp index d392e36ad96..11912d8fd95 100644 --- a/src/core/system/System.cpp +++ b/src/core/system/System.cpp @@ -26,7 +26,9 @@ namespace System { -static std::shared_ptr instance; +static std::shared_ptr instance = std::make_shared(); + +bool is_system_set() { return instance != nullptr; } void reset_system() { instance.reset(); } diff --git a/src/core/system/System.hpp b/src/core/system/System.hpp index f1ea2426281..2664cf16589 100644 --- a/src/core/system/System.hpp +++ b/src/core/system/System.hpp @@ -23,6 +23,7 @@ #include "GpuParticleData.hpp" #include "ResourceCleanup.hpp" +#include "electrostatics/solver.hpp" #include @@ -44,10 +45,12 @@ class System { gpu.init(); #endif } + Coulomb::Solver coulomb; }; System &get_system(); void set_system(std::shared_ptr new_instance); void reset_system(); +bool is_system_set(); } // namespace System diff --git a/src/python/espressomd/system.py b/src/python/espressomd/system.py index 0df3bd0aaeb..0d9eed1285a 100644 --- a/src/python/espressomd/system.py +++ b/src/python/espressomd/system.py @@ -227,6 +227,7 @@ def _setup_atexit(self): import atexit def session_shutdown(): + self.actors.clear() self.call_method("session_shutdown") atexit.register(session_shutdown) From e4d68bcbd151801ba365650328e49acc09aca783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Wed, 5 Jul 2023 20:43:37 +0200 Subject: [PATCH 2/5] core: Encapsulate magnetostatics globals The magnetostatics solver is now a member of the System class. --- src/core/energy.cpp | 7 +- src/core/event.cpp | 31 +++--- src/core/forces.cpp | 6 +- src/core/interactions.cpp | 4 +- src/core/magnetostatics/dipoles.cpp | 84 +++++++-------- src/core/magnetostatics/dipoles.hpp | 51 +-------- src/core/magnetostatics/dipoles_inline.hpp | 35 +++--- src/core/magnetostatics/registration.hpp | 13 ++- src/core/magnetostatics/solver.hpp | 119 +++++++++++++++++++++ src/core/npt.cpp | 2 +- src/core/pressure.cpp | 2 +- src/core/pressure_inline.hpp | 2 +- src/core/system/System.hpp | 2 + 13 files changed, 216 insertions(+), 142 deletions(-) create mode 100644 src/core/magnetostatics/solver.hpp diff --git a/src/core/energy.cpp b/src/core/energy.cpp index 9501282d385..81dcee059a6 100644 --- a/src/core/energy.cpp +++ b/src/core/energy.cpp @@ -66,7 +66,7 @@ std::shared_ptr calculate_energy() { auto const &system = System::get_system(); auto const coulomb_kernel = system.coulomb.pair_energy_kernel(); - auto const dipoles_kernel = Dipoles::pair_energy_kernel(); + auto const dipoles_kernel = system.dipoles.pair_energy_kernel(); short_range_loop( [&obs_energy, coulomb_kernel_ptr = coulomb_kernel.get_ptr()]( @@ -96,7 +96,7 @@ std::shared_ptr calculate_energy() { #ifdef DIPOLES /* calculate k-space part of magnetostatic interaction. */ - obs_energy.dipolar[1] = Dipoles::calc_energy_long_range(local_parts); + obs_energy.dipolar[1] = system.dipoles.calc_energy_long_range(local_parts); #endif Constraints::constraints.add_energy(local_parts, get_sim_time(), obs_energy); @@ -156,7 +156,8 @@ double particle_short_range_energy_contribution(int pid) { #ifdef DIPOLE_FIELD_TRACKING void calc_long_range_fields() { auto particles = cell_structure.local_particles(); - Dipoles::calc_long_range_field(particles); + auto const &dipoles = System::get_system().dipoles; + dipoles.calc_long_range_field(particles); } REGISTER_CALLBACK(calc_long_range_fields) diff --git a/src/core/event.cpp b/src/core/event.cpp index a1b248172f2..139ec4fd909 100644 --- a/src/core/event.cpp +++ b/src/core/event.cpp @@ -56,10 +56,6 @@ /** whether the thermostat has to be reinitialized before integration */ static bool reinit_thermo = true; -#ifdef DIPOLES -/** whether magnetostatics actor has to be reinitialized on observable calc */ -static bool reinit_magnetostatics = false; -#endif void on_program_start() { #ifdef CUDA @@ -125,7 +121,7 @@ void on_integration_start(double time_step) { #endif #ifdef DIPOLES { - auto const &actor = magnetostatics_actor; + auto const &actor = System::get_system().dipoles.solver; if (!Utils::Mpi::all_compare(comm_cart, static_cast(actor)) or (actor and !Utils::Mpi::all_compare(comm_cart, (*actor).which()))) runtimeErrorMsg() << "Nodes disagree about dipolar long-range method"; @@ -147,11 +143,8 @@ void on_observable_calc() { #endif #ifdef DIPOLES - if (reinit_magnetostatics) { - Dipoles::on_observable_calc(); - reinit_magnetostatics = false; - } -#endif /* DIPOLES */ + System::get_system().dipoles.on_observable_calc(); +#endif clear_particle_node(); } @@ -178,7 +171,9 @@ void on_particle_change() { } #endif #ifdef DIPOLES - reinit_magnetostatics = true; + if (System::is_system_set()) { + System::get_system().dipoles.on_particle_change(); + } #endif recalc_forces = true; @@ -194,8 +189,7 @@ void on_coulomb_and_dipoles_change() { System::get_system().coulomb.on_coulomb_change(); #endif #ifdef DIPOLES - reinit_magnetostatics = true; - Dipoles::on_dipoles_change(); + System::get_system().dipoles.on_dipoles_change(); #endif on_short_range_ia_change(); } @@ -209,8 +203,7 @@ void on_coulomb_change() { void on_dipoles_change() { #ifdef DIPOLES - reinit_magnetostatics = true; - Dipoles::on_dipoles_change(); + System::get_system().dipoles.on_dipoles_change(); #endif on_short_range_ia_change(); } @@ -242,7 +235,7 @@ void on_boxl_change(bool skip_method_adaption) { #endif #ifdef DIPOLES - Dipoles::on_boxl_change(); + System::get_system().dipoles.on_boxl_change(); #endif LB::init(); @@ -268,7 +261,7 @@ void on_cell_structure_change() { System::get_system().coulomb.on_cell_structure_change(); #endif #ifdef DIPOLES - Dipoles::on_cell_structure_change(); + System::get_system().dipoles.on_cell_structure_change(); #endif } #endif @@ -286,7 +279,7 @@ void on_periodicity_change() { #endif #ifdef DIPOLES - Dipoles::on_periodicity_change(); + System::get_system().dipoles.on_periodicity_change(); #endif #ifdef STOKESIAN_DYNAMICS @@ -321,7 +314,7 @@ void on_node_grid_change() { System::get_system().coulomb.on_node_grid_change(); #endif #ifdef DIPOLES - Dipoles::on_node_grid_change(); + System::get_system().dipoles.on_node_grid_change(); #endif cells_re_init(cell_structure.decomposition_type()); } diff --git a/src/core/forces.cpp b/src/core/forces.cpp index 4a6bca2b7f4..e8a1e66d6e2 100644 --- a/src/core/forces.cpp +++ b/src/core/forces.cpp @@ -170,7 +170,7 @@ void force_calc(CellStructure &cell_structure, double time_step, double kT) { auto const elc_kernel = espresso_system.coulomb.pair_force_elc_kernel(); auto const coulomb_kernel = espresso_system.coulomb.pair_force_kernel(); - auto const dipoles_kernel = Dipoles::pair_force_kernel(); + auto const dipoles_kernel = espresso_system.dipoles.pair_force_kernel(); #ifdef ELECTROSTATICS auto const coulomb_cutoff = espresso_system.coulomb.cutoff(); @@ -179,7 +179,7 @@ void force_calc(CellStructure &cell_structure, double time_step, double kT) { #endif #ifdef DIPOLES - auto const dipole_cutoff = Dipoles::cutoff(); + auto const dipole_cutoff = espresso_system.dipoles.cutoff(); #else auto const dipole_cutoff = INACTIVE_CUTOFF; #endif @@ -261,7 +261,7 @@ void calc_long_range_forces(const ParticleRange &particles) { #ifdef DIPOLES /* calculate k-space part of the magnetostatic interaction. */ - Dipoles::calc_long_range_force(particles); + Dipoles::get_dipoles().calc_long_range_force(particles); #endif // DIPOLES } diff --git a/src/core/interactions.cpp b/src/core/interactions.cpp index 9e4482c8f07..93237993c91 100644 --- a/src/core/interactions.cpp +++ b/src/core/interactions.cpp @@ -42,7 +42,7 @@ static double recalc_long_range_cutoff() { max_cut_long_range = std::max(max_cut_long_range, system.coulomb.cutoff()); #endif #ifdef DIPOLES - max_cut_long_range = std::max(max_cut_long_range, Dipoles::cutoff()); + max_cut_long_range = std::max(max_cut_long_range, system.dipoles.cutoff()); #endif } #endif @@ -76,7 +76,7 @@ bool long_range_interactions_sanity_checks() { system.coulomb.sanity_checks(); #endif #ifdef DIPOLES - Dipoles::sanity_checks(); + system.dipoles.sanity_checks(); #endif } catch (std::runtime_error const &err) { runtimeErrorMsg() << err.what(); diff --git a/src/core/magnetostatics/dipoles.cpp b/src/core/magnetostatics/dipoles.cpp index f38fe64da31..207b5cbda7c 100644 --- a/src/core/magnetostatics/dipoles.cpp +++ b/src/core/magnetostatics/dipoles.cpp @@ -32,6 +32,7 @@ #include "grid.hpp" #include "integrate.hpp" #include "npt.hpp" +#include "system/System.hpp" #include #include @@ -43,67 +44,61 @@ #include #include -boost::optional magnetostatics_actor; - namespace Dipoles { -void sanity_checks() { - if (magnetostatics_actor) { - boost::apply_visitor([](auto &actor) { actor->sanity_checks(); }, - *magnetostatics_actor); +Solver const &get_dipoles() { return System::get_system().dipoles; } + +void Solver::sanity_checks() const { + if (solver) { + boost::apply_visitor([](auto &actor) { actor->sanity_checks(); }, *solver); } } -void on_dipoles_change() { - visit_active_actor_try_catch([](auto &actor) { actor->init(); }, - magnetostatics_actor); +void Solver::on_dipoles_change() { + reinit_on_observable_calc = true; + visit_active_actor_try_catch([](auto &actor) { actor->init(); }, solver); } -void on_boxl_change() { +void Solver::on_boxl_change() { visit_active_actor_try_catch([](auto &actor) { actor->on_boxl_change(); }, - magnetostatics_actor); + solver); } -void on_node_grid_change() { - if (magnetostatics_actor) { +void Solver::on_node_grid_change() { + if (solver) { boost::apply_visitor([](auto &actor) { actor->on_node_grid_change(); }, - *magnetostatics_actor); + *solver); } } -void on_periodicity_change() { +void Solver::on_periodicity_change() { visit_active_actor_try_catch( - [](auto &actor) { actor->on_periodicity_change(); }, - magnetostatics_actor); + [](auto &actor) { actor->on_periodicity_change(); }, solver); } -void on_cell_structure_change() { +void Solver::on_cell_structure_change() { visit_active_actor_try_catch( - [](auto &actor) { actor->on_cell_structure_change(); }, - magnetostatics_actor); -} - -void calc_pressure_long_range() { - if (magnetostatics_actor) { - runtimeWarningMsg() << "pressure calculated, but pressure not implemented."; - } + [](auto &actor) { actor->on_cell_structure_change(); }, solver); } -double cutoff() { +double Solver::cutoff() const { #ifdef DP3M - if (auto dp3m = get_actor_by_type(magnetostatics_actor)) { + if (auto dp3m = get_actor_by_type(solver)) { return dp3m->dp3m.params.r_cut; } #endif return -1.; } -void on_observable_calc() { +void Solver::on_observable_calc() { + if (reinit_on_observable_calc) { #ifdef DP3M - if (auto dp3m = get_actor_by_type(magnetostatics_actor)) { - dp3m->count_magnetic_particles(); - } + if (auto dp3m = get_actor_by_type(solver)) { + dp3m->count_magnetic_particles(); + } #endif + reinit_on_observable_calc = false; + } } struct LongRangeForce : public boost::static_visitor { @@ -205,24 +200,29 @@ struct LongRangeField : public boost::static_visitor { }; #endif -void calc_long_range_force(ParticleRange const &particles) { - if (magnetostatics_actor) { - boost::apply_visitor(LongRangeForce(particles), *magnetostatics_actor); +void Solver::calc_pressure_long_range() const { + if (solver) { + runtimeWarningMsg() << "pressure calculated, but pressure not implemented."; + } +} + +void Solver::calc_long_range_force(ParticleRange const &particles) const { + if (solver) { + boost::apply_visitor(LongRangeForce(particles), *solver); } } -double calc_energy_long_range(ParticleRange const &particles) { - if (magnetostatics_actor) { - return boost::apply_visitor(LongRangeEnergy(particles), - *magnetostatics_actor); +double Solver::calc_energy_long_range(ParticleRange const &particles) const { + if (solver) { + return boost::apply_visitor(LongRangeEnergy(particles), *solver); } return 0.; } #ifdef DIPOLE_FIELD_TRACKING -void calc_long_range_field(ParticleRange const &particles) { - if (magnetostatics_actor) { - boost::apply_visitor(LongRangeField(particles), *magnetostatics_actor); +void Solver::calc_long_range_field(ParticleRange const &particles) const { + if (solver) { + boost::apply_visitor(LongRangeField(particles), *solver); } } #endif diff --git a/src/core/magnetostatics/dipoles.hpp b/src/core/magnetostatics/dipoles.hpp index d3077505919..cbacae5d7df 100644 --- a/src/core/magnetostatics/dipoles.hpp +++ b/src/core/magnetostatics/dipoles.hpp @@ -26,6 +26,8 @@ #include "actor/traits.hpp" +#include "magnetostatics/solver.hpp" + #include "magnetostatics/barnes_hut_gpu.hpp" #include "magnetostatics/dipolar_direct_sum.hpp" #include "magnetostatics/dipolar_direct_sum_gpu.hpp" @@ -47,24 +49,6 @@ #include #include -using MagnetostaticsActor = - boost::variant, -#ifdef DIPOLAR_DIRECT_SUM - std::shared_ptr, -#endif -#ifdef DIPOLAR_BARNES_HUT - std::shared_ptr, -#endif -#ifdef DP3M - std::shared_ptr, -#endif -#ifdef SCAFACOS_DIPOLES - std::shared_ptr, -#endif - std::shared_ptr>; - -extern boost::optional magnetostatics_actor; - /** Get the magnetostatics prefactor. */ struct GetDipolesPrefactor : public boost::static_visitor { template @@ -81,7 +65,6 @@ struct DipolesSanityChecks : public boost::static_visitor { }; namespace Dipoles { - namespace traits { /** @brief Whether an actor is a solver. */ @@ -95,35 +78,7 @@ template <> struct has_dipole_fields : std::true_type {}; #endif // DIPOLE_FIELD_TRACKING } // namespace traits - -void calc_pressure_long_range(); - -void sanity_checks(); -double cutoff(); - -void on_observable_calc(); -void on_dipoles_change(); -void on_boxl_change(); -void on_node_grid_change(); -void on_periodicity_change(); -void on_cell_structure_change(); - -void calc_long_range_force(ParticleRange const &particles); -double calc_energy_long_range(ParticleRange const &particles); -#ifdef DIPOLE_FIELD_TRACKING -void calc_long_range_field(ParticleRange const &particles); -#endif - -namespace detail { -bool flag_all_reduce(bool flag); -} // namespace detail - -} // namespace Dipoles -#else // DIPOLES -#include -namespace Dipoles { -constexpr std::size_t pressure_n() { return 0; } -constexpr std::size_t energy_n() { return 0; } } // namespace Dipoles + #endif // DIPOLES #endif diff --git a/src/core/magnetostatics/dipoles_inline.hpp b/src/core/magnetostatics/dipoles_inline.hpp index 65f179e42cd..3bfc65cbb14 100644 --- a/src/core/magnetostatics/dipoles_inline.hpp +++ b/src/core/magnetostatics/dipoles_inline.hpp @@ -22,14 +22,15 @@ #include "config/config.hpp" +#include "magnetostatics/dipoles.hpp" +#include "magnetostatics/dp3m.hpp" +#include "magnetostatics/solver.hpp" + #include "Particle.hpp" #include "actor/traits.hpp" #include "actor/visitors.hpp" -#include "magnetostatics/dipoles.hpp" -#include "magnetostatics/dp3m.hpp" - #include #include @@ -40,9 +41,8 @@ namespace Dipoles { struct ShortRangeForceKernel - : public boost::static_visitor>> { + : public boost::static_visitor< + boost::optional> { using kernel_type = result_type::value_type; @@ -71,9 +71,8 @@ struct ShortRangeForceKernel }; struct ShortRangeEnergyKernel - : public boost::static_visitor>> { + : public boost::static_visitor< + boost::optional> { using kernel_type = result_type::value_type; @@ -101,21 +100,23 @@ struct ShortRangeEnergyKernel #endif // DIPOLES }; -inline ShortRangeForceKernel::result_type pair_force_kernel() { +inline boost::optional +Solver::pair_force_kernel() const { #ifdef DIPOLES - if (magnetostatics_actor) { - auto const visitor = ShortRangeForceKernel(); - return boost::apply_visitor(visitor, *magnetostatics_actor); + if (solver) { + auto const visitor = Dipoles::ShortRangeForceKernel(); + return boost::apply_visitor(visitor, *solver); } #endif // DIPOLES return {}; } -inline ShortRangeEnergyKernel::result_type pair_energy_kernel() { +inline boost::optional +Solver::pair_energy_kernel() const { #ifdef DIPOLES - if (magnetostatics_actor) { - auto const visitor = ShortRangeEnergyKernel(); - return boost::apply_visitor(visitor, *magnetostatics_actor); + if (solver) { + auto const visitor = Dipoles::ShortRangeEnergyKernel(); + return boost::apply_visitor(visitor, *solver); } #endif // DIPOLES return {}; diff --git a/src/core/magnetostatics/registration.hpp b/src/core/magnetostatics/registration.hpp index ce9513b6237..86bb99a0547 100644 --- a/src/core/magnetostatics/registration.hpp +++ b/src/core/magnetostatics/registration.hpp @@ -29,6 +29,7 @@ #include "actor/visitors.hpp" #include "event.hpp" +#include "system/System.hpp" #include #include @@ -41,22 +42,24 @@ namespace Dipoles { template ::value> * = nullptr> void add_actor(std::shared_ptr const &actor) { - if (::magnetostatics_actor) { - auto const name = get_actor_name(*::magnetostatics_actor); + auto &system = System::get_system(); + if (system.dipoles.solver) { + auto const name = get_actor_name(*system.dipoles.solver); throw std::runtime_error("A magnetostatics solver is already active (" + name + ")"); } - add_actor(::magnetostatics_actor, actor, ::on_dipoles_change, + add_actor(system.dipoles.solver, actor, ::on_dipoles_change, detail::flag_all_reduce); } template ::value> * = nullptr> void remove_actor(std::shared_ptr const &actor) { - if (not is_already_stored(actor, ::magnetostatics_actor)) { + auto &system = System::get_system(); + if (not is_already_stored(actor, system.dipoles.solver)) { throw std::runtime_error( "The given magnetostatics solver is not currently active"); } - remove_actor(::magnetostatics_actor, actor, ::on_dipoles_change); + remove_actor(system.dipoles.solver, actor, ::on_dipoles_change); } } // namespace Dipoles diff --git a/src/core/magnetostatics/solver.hpp b/src/core/magnetostatics/solver.hpp new file mode 100644 index 00000000000..922f000c37b --- /dev/null +++ b/src/core/magnetostatics/solver.hpp @@ -0,0 +1,119 @@ +/* + * 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 "config/config.hpp" + +#include "actor/traits.hpp" + +#include "Particle.hpp" +#include "ParticleRange.hpp" + +#include + +#include +#include + +#include +#include +#include + +#ifdef DIPOLES +// forward declarations +struct DipolarLayerCorrection; +struct DipolarDirectSum; +#ifdef DIPOLAR_DIRECT_SUM +struct DipolarDirectSumGpu; +#endif +#ifdef DIPOLAR_BARNES_HUT +struct DipolarBarnesHutGpu; +#endif +#ifdef DP3M +struct DipolarP3M; +#endif +#ifdef SCAFACOS_DIPOLES +struct DipolarScafacos; +#endif + +using MagnetostaticsActor = + boost::variant, +#ifdef DIPOLAR_DIRECT_SUM + std::shared_ptr, +#endif +#ifdef DIPOLAR_BARNES_HUT + std::shared_ptr, +#endif +#ifdef DP3M + std::shared_ptr, +#endif +#ifdef SCAFACOS_DIPOLES + std::shared_ptr, +#endif + std::shared_ptr>; +#endif // DIPOLES + +namespace Dipoles { + +namespace detail { +bool flag_all_reduce(bool flag); +} // namespace detail + +struct Solver { +#ifdef DIPOLES + /// @brief Main electrostatics solver. + boost::optional solver; + /// @brief Whether to reinitialize the solver on observable calculation. + bool reinit_on_observable_calc = false; + + void sanity_checks() const; + double cutoff() const; + + void on_observable_calc(); + void on_dipoles_change(); + void on_boxl_change(); + void on_node_grid_change(); + void on_periodicity_change(); + void on_cell_structure_change(); + void on_particle_change() { reinit_on_observable_calc = true; } + + void calc_pressure_long_range() const; + void calc_long_range_force(ParticleRange const &particles) const; + double calc_energy_long_range(ParticleRange const &particles) const; +#ifdef DIPOLE_FIELD_TRACKING + void calc_long_range_field(ParticleRange const &particles) const; +#endif +#endif // DIPOLES + + using ShortRangeForceKernel = + std::function; + using ShortRangeEnergyKernel = + std::function; + + inline boost::optional pair_force_kernel() const; + inline boost::optional pair_energy_kernel() const; +}; + +#ifdef DIPOLES +Solver const &get_dipoles(); +#endif + +} // namespace Dipoles diff --git a/src/core/npt.cpp b/src/core/npt.cpp index c56f136fc4c..5e255a60174 100644 --- a/src/core/npt.cpp +++ b/src/core/npt.cpp @@ -56,7 +56,7 @@ void NptIsoParameters::coulomb_dipole_sanity_checks() const { #endif #ifdef DIPOLES - if (dimension < 3 && !cubic_box && magnetostatics_actor) { + if (dimension < 3 and not cubic_box and System::get_system().dipoles.solver) { throw std::runtime_error("If magnetostatics is being used you must " "use the cubic box NpT."); } diff --git a/src/core/pressure.cpp b/src/core/pressure.cpp index a922ba87dc0..5696d2c8be2 100644 --- a/src/core/pressure.cpp +++ b/src/core/pressure.cpp @@ -111,7 +111,7 @@ std::shared_ptr calculate_pressure() { #endif #ifdef DIPOLES /* calculate k-space part of magnetostatic interaction. */ - Dipoles::calc_pressure_long_range(); + Dipoles::get_dipoles().calc_pressure_long_range(); #endif #ifdef VIRTUAL_SITES diff --git a/src/core/pressure_inline.hpp b/src/core/pressure_inline.hpp index 7e42ff2b455..f99d036f731 100644 --- a/src/core/pressure_inline.hpp +++ b/src/core/pressure_inline.hpp @@ -87,7 +87,7 @@ inline void add_non_bonded_pair_virials( #ifdef DIPOLES /* real space magnetic dipole-dipole */ - if (magnetostatics_actor) { + if (Dipoles::get_dipoles().solver) { fprintf(stderr, "calculating pressure for magnetostatics which doesn't " "have it implemented\n"); } diff --git a/src/core/system/System.hpp b/src/core/system/System.hpp index 2664cf16589..abd24baa396 100644 --- a/src/core/system/System.hpp +++ b/src/core/system/System.hpp @@ -24,6 +24,7 @@ #include "GpuParticleData.hpp" #include "ResourceCleanup.hpp" #include "electrostatics/solver.hpp" +#include "magnetostatics/solver.hpp" #include @@ -46,6 +47,7 @@ class System { #endif } Coulomb::Solver coulomb; + Dipoles::Solver dipoles; }; System &get_system(); From 48483d6d4b733548b504825fefd970668e22c720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Fri, 7 Jul 2023 19:00:11 +0200 Subject: [PATCH 3/5] Remove electrostatics/magnetostatics from actors list --- doc/sphinx/electrostatics.rst | 35 +++--- doc/sphinx/introduction.rst | 13 +- doc/sphinx/magnetostatics.rst | 45 +++++-- .../charged_system/charged_system.ipynb | 28 +++-- doc/tutorials/constant_pH/constant_pH.ipynb | 14 +-- .../ferrofluid/ferrofluid_part1.ipynb | 21 +++- .../ferrofluid/ferrofluid_part2.ipynb | 6 +- .../ferrofluid/ferrofluid_part3.ipynb | 2 +- .../raspberry_electrophoresis.ipynb | 2 +- .../widom_insertion/widom_insertion.ipynb | 6 +- maintainer/benchmarks/p3m.py | 2 +- samples/drude_bmimpf6.py | 2 +- samples/load_checkpoint.py | 5 +- samples/minimal-charged-particles.py | 2 +- samples/p3m.py | 4 +- samples/save_checkpoint.py | 2 +- samples/visualization_charged.py | 2 +- samples/widom_insertion.py | 2 +- src/core/actor/optional.hpp | 27 ++++ src/core/actor/registration.hpp | 32 ++--- src/core/actor/visit_try_catch.hpp | 9 +- src/core/actor/visitors.hpp | 63 ++-------- src/core/constraints/ShapeBasedConstraint.cpp | 8 +- src/core/electrostatics/coulomb.cpp | 43 +++---- src/core/electrostatics/coulomb.hpp | 9 +- src/core/electrostatics/coulomb_inline.hpp | 53 ++++---- src/core/electrostatics/elc.cpp | 9 +- src/core/electrostatics/elc.hpp | 15 ++- src/core/electrostatics/icc.cpp | 21 ++-- src/core/electrostatics/p3m.cpp | 17 +-- src/core/electrostatics/registration.hpp | 93 -------------- src/core/electrostatics/solver.hpp | 40 +++--- src/core/energy.cpp | 8 +- src/core/event.cpp | 8 +- src/core/forces.cpp | 15 +-- src/core/magnetostatics/dipoles.cpp | 32 ++--- src/core/magnetostatics/dipoles.hpp | 12 +- src/core/magnetostatics/dipoles_inline.hpp | 31 +++-- src/core/magnetostatics/dlc.cpp | 5 +- src/core/magnetostatics/dlc.hpp | 8 +- src/core/magnetostatics/dp3m.cpp | 4 +- src/core/magnetostatics/registration.hpp | 68 ---------- src/core/magnetostatics/solver.hpp | 28 ++--- src/core/p3m/TuningAlgorithm.hpp | 5 +- src/core/pair_criteria/EnergyCriterion.hpp | 2 +- src/core/pressure.cpp | 7 +- .../EspressoSystemStandAlone_test.cpp | 6 +- src/python/espressomd/electrostatics.py | 12 ++ src/python/espressomd/magnetostatics.py | 14 +++ src/python/espressomd/system.py | 12 ++ src/script_interface/ObjectHandle.hpp | 3 +- .../electrostatics/Actor_impl.hpp | 35 ++++-- .../electrostatics/Container.hpp | 110 ++++++++++++++++ .../electrostatics/ICCStar.hpp | 14 +-- .../electrostatics/initialize.cpp | 3 +- .../magnetostatics/Actor_impl.hpp | 15 ++- .../magnetostatics/Container.hpp | 84 +++++++++++++ .../magnetostatics/initialize.cpp | 3 +- src/script_interface/tests/Actors_test.cpp | 15 +-- testsuite/python/analyze_energy.py | 4 +- testsuite/python/coulomb_cloud_wall.py | 21 ++-- .../python/coulomb_cloud_wall_duplicated.py | 10 +- testsuite/python/coulomb_interface.py | 46 +++---- testsuite/python/coulomb_mixed_periodicity.py | 8 +- testsuite/python/dawaanr-and-dds-gpu.py | 9 +- testsuite/python/dipolar_direct_summation.py | 20 +-- testsuite/python/dipolar_interface.py | 34 ++--- testsuite/python/drude.py | 2 +- testsuite/python/elc.py | 4 +- testsuite/python/hybrid_decomposition.py | 16 ++- testsuite/python/icc.py | 6 +- testsuite/python/icc_interface.py | 58 ++++----- testsuite/python/integrator_npt.py | 19 +-- testsuite/python/long_range_actors.py | 105 +++++++--------- .../python/p3m_electrostatic_pressure.py | 4 +- testsuite/python/p3m_fft.py | 12 +- testsuite/python/p3m_madelung.py | 22 ++-- testsuite/python/p3m_tuning_exceptions.py | 119 ++++++++++-------- testsuite/python/save_checkpoint.py | 14 +-- testsuite/python/scafacos_interface.py | 53 ++++---- testsuite/python/test_checkpoint.py | 19 +-- testsuite/python/tests_common.py | 8 +- 82 files changed, 929 insertions(+), 875 deletions(-) create mode 100644 src/core/actor/optional.hpp delete mode 100644 src/core/electrostatics/registration.hpp delete mode 100644 src/core/magnetostatics/registration.hpp create mode 100644 src/script_interface/electrostatics/Container.hpp create mode 100644 src/script_interface/magnetostatics/Container.hpp diff --git a/doc/sphinx/electrostatics.rst b/doc/sphinx/electrostatics.rst index 2594ab44b3a..05063d5542f 100644 --- a/doc/sphinx/electrostatics.rst +++ b/doc/sphinx/electrostatics.rst @@ -37,7 +37,7 @@ the particles via the particle property All solvers need a prefactor and a set of other required parameters. This example shows the general usage of the electrostatic method ``P3M``. -An instance of the solver is created and added to the actors list, at which +An instance of the solver is created and attached to the system, at which point it will be automatically activated. This activation will internally call a tuning function to achieve the requested accuracy:: @@ -48,13 +48,17 @@ call a tuning function to achieve the requested accuracy:: system.time_step = 0.01 system.part.add(pos=[[0, 0, 0], [1, 1, 1]], q=[-1, 1]) solver = espressomd.electrostatics.P3M(prefactor=2., accuracy=1e-3) - system.actors.add(solver) + system.electrostatics.solver = solver where the prefactor is defined as :math:`C` in Eqn. :eq:`coulomb_prefactor`. -The list of actors can be cleared with -:meth:`system.actors.clear() ` and -:meth:`system.actors.remove(actor) `. +The solver can be detached with either:: + + system.electrostatics.solver = None + +or:: + + system.electrostatics.clear() .. _Coulomb P3M: @@ -190,9 +194,12 @@ surface. ICC relies on a Coulomb solver that is already initialized. So far, it is implemented and well tested with the Coulomb solver P3M. ICC is an |es| actor and can be activated via:: + import espressomd.electrostatics import espressomd.electrostatic_extensions + p3m = espressomd.electrostatics.P3M(...) icc = espressomd.electrostatic_extensions.ICC(...) - system.actors.add(icc) + system.electrostatics.solver = p3m + system.electrostatics.extension = icc The ICC particles are setup as normal |es| particles. Note that they should be fixed in space and need an initial non-zero charge. The following example @@ -248,7 +255,7 @@ sets up parallel metallic plates and activates ICC:: sigmas=iccSigmas, epsilons=iccEpsilons) - system.actors.add(icc) + system.electrostatics.extension = icc With each iteration, ICC has to solve electrostatics which can severely slow @@ -290,7 +297,7 @@ Usage notes: import espressomd.electrostatics p3m = espressomd.electrostatics.P3M(prefactor=1, accuracy=1e-4) elc = espressomd.electrostatics.ELC(actor=p3m, gap_size=box_l * 0.2, maxPWerror=1e-3) - system.actors.add(elc) + system.electrostatics.solver = elc Although it is technically feasible to remove ``elc`` from the list of actors and then to add the ``p3m`` object, it is not recommended because the P3M @@ -404,7 +411,7 @@ the library, and can be queried with :meth:`espressomd.electrostatics.Scafacos.get_available_methods`. To use ScaFaCoS, create an instance of :class:`~espressomd.electrostatics.Scafacos` -and add it to the list of active actors. Three parameters have to be specified: +and attach it to the system. Three parameters have to be specified: ``prefactor`` (as defined in :eq:`coulomb_prefactor`), ``method_name``, ``method_params``. The method-specific parameters are described in the ScaFaCoS manual. In addition, methods supporting tuning have a parameter @@ -415,11 +422,11 @@ To use a specific electrostatics solver from ScaFaCoS for your system, e.g. ``ewald``, set its cutoff to :math:`1.5` and tune the other parameters for an accuracy of :math:`10^{-3}`:: - import espressomd.electrostatics - scafacos = espressomd.electrostatics.Scafacos( - prefactor=1, method_name="ewald", - method_params={"ewald_r_cut": 1.5, "tolerance_field": 1e-3}) - system.actors.add(scafacos) + import espressomd.electrostatics + scafacos = espressomd.electrostatics.Scafacos( + prefactor=1, method_name="ewald", + method_params={"ewald_r_cut": 1.5, "tolerance_field": 1e-3}) + system.electrostatics.solver = scafacos For details of the various methods and their parameters please refer to the ScaFaCoS manual. To use this feature, ScaFaCoS has to be built as a diff --git a/doc/sphinx/introduction.rst b/doc/sphinx/introduction.rst index fff222632f8..7ec21ba3f93 100644 --- a/doc/sphinx/introduction.rst +++ b/doc/sphinx/introduction.rst @@ -259,16 +259,19 @@ electrostatics solver. We start by adding the particles: :: cation = system.part.add(pos=[4.0, 1.0, 1.0], type=2, q=1.0) anion = system.part.add(pos=[6.0, 1.0, 1.0], type=2, q=-1.0) -Long-range interactions and other methods that might be mutually exclusive -are treated as so-called *actors*. They are used by first creating an instance -of the desired actor:: +Long-range interaction solvers for electrostatics, magnetostatis, hydrodynamics +and electrokinetics are treated as self-contained objects that can be attached +to the system. We start by first creating an instance of the desired solver:: p3m = espressomd.electrostatics.P3M(accuracy=1e-3, prefactor=1.0) -and then adding it to the system: :: +and then we attach it to the system:: print("Tuning p3m ...") - system.actors.add(p3m) + system.electrostatics.solver = p3m + +Solvers for electrostatics and magnetostatics automatically tune their +parameters when attached to a system. .. rubric:: Integration diff --git a/doc/sphinx/magnetostatics.rst b/doc/sphinx/magnetostatics.rst index b79cae60ef1..548a6e83032 100644 --- a/doc/sphinx/magnetostatics.rst +++ b/doc/sphinx/magnetostatics.rst @@ -36,11 +36,15 @@ Magnetostatic interactions are activated via the actor framework:: rotation=2 * [(True, True, True)]) actor = espressomd.magnetostatics.DipolarDirectSumCpu(prefactor=1.) - system.actors.add(actor) + system.magnetostatics.solver = actor -The list of actors can be cleared with -:meth:`system.actors.clear() ` and -:meth:`system.actors.remove(actor) `. +The solver can be detached with either:: + + system.magnetostatics.solver = None + +or:: + + system.magnetostatics.clear() .. _Dipolar P3M: @@ -66,7 +70,7 @@ the omitted parameters are tuned:: import espressomd.magnetostatics as magnetostatics p3m = magnetostatics.DipolarP3M(prefactor=1, mesh=32, accuracy=1E-4) - system.actors.add(p3m) + system.magnetostatics.solver = p3m It is important to note that the error estimates given in :cite:`cerda08d` used in the tuning contain assumptions about the system. In particular, a @@ -109,7 +113,7 @@ The method is used as follows:: import espressomd.magnetostatics dp3m = espressomd.magnetostatics.DipolarP3M(prefactor=1, accuracy=1E-4) mdlc = espressomd.magnetostatics.DLC(actor=dp3m, maxPWerror=1E-5, gap_size=2.) - system.actors.add(mdlc) + system.magnetostatics.solver = mdlc .. _Dipolar direct sum: @@ -144,13 +148,13 @@ Two methods are available: To use the methods, create an instance of either :class:`~espressomd.magnetostatics.DipolarDirectSumCpu` or -:class:`~espressomd.magnetostatics.DipolarDirectSumGpu` and add it to the -system's list of active actors. The only required parameter is the prefactor +:class:`~espressomd.magnetostatics.DipolarDirectSumGpu` and +attach it to the system. The only required parameter is the prefactor :eq:`dipolar_prefactor`:: import espressomd.magnetostatics dds = espressomd.magnetostatics.DipolarDirectSumGpu(prefactor=1) - system.actors.add(dds) + system.magnetostatics.solver = dds The CPU implementation has an optional argument ``n_replicas`` which adds periodic copies to the system along periodic directions. In that @@ -182,11 +186,11 @@ Barnes-Hut method application to the dipole-dipole interactions, please refer to :cite:`polyakov13a`. To use the method, create an instance of :class:`~espressomd.magnetostatics.DipolarBarnesHutGpu` -and add it to the system's list of active actors:: +and attach it to the system:: import espressomd.magnetostatics bh = espressomd.magnetostatics.DipolarBarnesHutGpu(prefactor=1., epssq=200.0, itolsq=8.0) - system.actors.add(bh) + system.magnetostatics.solver = bh .. _ScaFaCoS magnetostatics: @@ -204,12 +208,29 @@ the ScaFaCoS code. The specific methods available can be queried with :meth:`espressomd.electrostatics.Scafacos.get_available_methods`. To use ScaFaCoS, create an instance of :class:`~espressomd.magnetostatics.Scafacos` -and add it to the list of active actors. Three parameters have to be specified: +and attach it to the system. Three parameters have to be specified: ``prefactor``, ``method_name``, ``method_params``. The method-specific parameters are described in the ScaFaCoS manual. In addition, methods supporting tuning have a parameter ``tolerance_field`` which sets the desired root mean square accuracy for the magnetic field. +Here is an example with the P2NFFT method for 2D-periodic systems:: + + import espressomd.magnetostatics + scafacos = espressomd.magnetostatics.Scafacos( + prefactor=1., method_name="p2nfft", + method_params={ + "p2nfft_verbose_tuning": 0, + "pnfft_N": "80,80,160", + "pnfft_window_name": "bspline", + "pnfft_m": "4", + "p2nfft_ignore_tolerance": "1", + "pnfft_diff_ik": "0", + "p2nfft_r_cut": "6", + "p2nfft_alpha": "0.8", + "p2nfft_epsB": "0.05"}) + system.magnetostatics.solver = scafacos + For details of the various methods and their parameters please refer to the ScaFaCoS manual. To use this feature, ScaFaCoS has to be built as a shared library. diff --git a/doc/tutorials/charged_system/charged_system.ipynb b/doc/tutorials/charged_system/charged_system.ipynb index fbf8da9682f..780855e6189 100644 --- a/doc/tutorials/charged_system/charged_system.ipynb +++ b/doc/tutorials/charged_system/charged_system.ipynb @@ -232,7 +232,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now we set up the electrostatics method to calculate the forces and energies from the long-range Coulomb interaction. **ESPResSo** uses so-called actors for electrostatics, magnetostatics and hydrodynamics. This ensures that unphysical combinations of algorithms are avoided, for example simultaneous usage of two electrostatic interactions. Adding an actor to the system also activates the method and calls necessary initialization routines. Here, we define a P$^3$M object using the Bjerrum length and rms force error. This automatically starts a tuning function which tries to find optimal parameters for P$^3$M and prints them to the screen. For more details, see the user guide chapter on [Electrostatics](https://espressomd.github.io/doc/electrostatics.html)." + "Now we set up the electrostatics method to calculate the forces and energies from the long-range Coulomb interaction.\n", + "**ESPResSo** uses so-called solvers for electrostatics, magnetostatics and hydrodynamics.\n", + "They are attached to system slots in such a way that unphysical combinations of algorithms are avoided,\n", + "for example simultaneous usage of two electrostatics solvers.\n", + "Adding an actor to the system also activates the method and calls necessary initialization routines.\n", + "Here, we define a P$^3$M object using the Bjerrum length and rms force error.\n", + "This automatically starts a tuning function which tries to find optimal parameters for P$^3$M and prints them to the screen.\n", + "For more details, see the user guide chapter on [Electrostatics](https://espressomd.github.io/doc/electrostatics.html)." ] }, { @@ -249,7 +256,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For the accuracy, **ESPResSo** estimates the relative error in the force calculation introduced by the approximations of $P^3M$. We choose a relatively poor accuracy (large value) for this tutorial to make it run faster. For your own production simulations you should reduce that number." + "For the accuracy, **ESPResSo** estimates the relative error in the force calculation introduced by the approximations of $P^3M$.\n", + "We choose a relatively poor accuracy (large value) for this tutorial to make it run faster.\n", + "For your own production simulations you should reduce that number." ] }, { @@ -260,7 +269,7 @@ }, "source": [ "**Exercise:**\n", - "* Set up a ``p3m`` instance and add it to the ``actors`` of the system" + "* Set up a ``p3m`` instance and add it to the ``electrostatics`` slot of the system" ] }, { @@ -271,7 +280,7 @@ "source": [ "```python\n", "p3m = espressomd.electrostatics.P3M(**p3m_params)\n", - "system.actors.add(p3m)\n", + "system.electrostatics.solver = p3m\n", "```" ] }, @@ -286,7 +295,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Before we can start the simulation, we need to remove the overlap between particles to avoid large forces which would crash the simulation. For this, we use the steepest descent integrator with a relative convergence criterion for forces and energies." + "Before we can start the simulation, we need to remove the overlap between particles to avoid large forces which would crash the simulation.\n", + "For this, we use the steepest descent integrator with a relative convergence criterion for forces and energies." ] }, { @@ -504,7 +514,7 @@ "* Write a function ``clear_system(system)`` that\n", " * turns off the thermostat\n", " * removes all particles\n", - " * removes all actors\n", + " * removes the electrostatics solver\n", " * removes all accumulators added to the auto-update-list\n", " * resets the system clock\n", "\n", @@ -527,7 +537,7 @@ "def clear_system(system):\n", " system.thermostat.turn_off()\n", " system.part.clear()\n", - " system.actors.clear()\n", + " system.electrostatics.clear()\n", " system.auto_update_accumulators.clear()\n", " system.time = 0.\n", "```" @@ -618,7 +628,7 @@ " system, run['params']['counterion_valency'], COUNTERION_TYPE,\n", " run['params']['rod_charge_dens'], N_rod_beads, ROD_TYPE)\n", " p3m = espressomd.electrostatics.P3M(**p3m_params)\n", - " system.actors.add(p3m)\n", + " system.electrostatics.solver = p3m\n", " remove_overlap(system, STEEPEST_DESCENT_PARAMS)\n", " system.thermostat.set_langevin(**LANGEVIN_PARAMS)\n", " print('', end='', flush=True)\n", @@ -845,7 +855,7 @@ "assert abs(sum(anions.q) + sum(cations.q)) < 1e-10\n", "\n", "p3m = espressomd.electrostatics.P3M(**p3m_params)\n", - "system.actors.add(p3m)\n", + "system.electrostatics.solver = p3m\n", "remove_overlap(system, STEEPEST_DESCENT_PARAMS)\n", "system.thermostat.set_langevin(**LANGEVIN_PARAMS)\n", "print('', end='', flush=True)\n", diff --git a/doc/tutorials/constant_pH/constant_pH.ipynb b/doc/tutorials/constant_pH/constant_pH.ipynb index ca4029465a4..95a88f258b9 100644 --- a/doc/tutorials/constant_pH/constant_pH.ipynb +++ b/doc/tutorials/constant_pH/constant_pH.ipynb @@ -534,14 +534,14 @@ "if USE_ELECTROSTATICS:\n", " COULOMB_PREFACTOR=BJERRUM_LENGTH_REDUCED * KT_REDUCED\n", " if USE_P3M:\n", - " coulomb = espressomd.electrostatics.P3M(prefactor = COULOMB_PREFACTOR, \n", + " coulomb = espressomd.electrostatics.P3M(prefactor=COULOMB_PREFACTOR,\n", " accuracy=1e-3)\n", " else:\n", - " coulomb = espressomd.electrostatics.DH(prefactor = COULOMB_PREFACTOR, \n", - " kappa = KAPPA_REDUCED, \n", - " r_cut = 1. / KAPPA_REDUCED)\n", - " \n", - " system.actors.add(coulomb)\n", + " coulomb = espressomd.electrostatics.DH(prefactor=COULOMB_PREFACTOR,\n", + " kappa=KAPPA_REDUCED,\n", + " r_cut=1. / KAPPA_REDUCED)\n", + "\n", + " system.electrostatics.solver = coulomb\n", "else:\n", " # this speeds up the simulation of dilute systems with small particle numbers\n", " system.cell_system.set_n_square()" @@ -715,7 +715,7 @@ " * an integer `num_samples` that determines the number of sampling steps to be performed\n", " * a numpy array `num_As`, such that `len(num_As) == num_samples` which will be used to store the time series of the instantaneous values of $N_{\\mathrm{A^{-}}}$\n", " * a float `reaction_steps` that determines the number of reactions to be performed\n", - " \n", + "\n", "\n", "* The function should perform the following tasks:\n", " * sample the composition fluctuations using reaction moves of the constant $\\mathrm{pH}$ algorithm by calling [`RE.reaction()`](https://espressomd.github.io/doc/espressomd.html#espressomd.reaction_methods.ReactionAlgorithm.reaction)\n", diff --git a/doc/tutorials/ferrofluid/ferrofluid_part1.ipynb b/doc/tutorials/ferrofluid/ferrofluid_part1.ipynb index 42863f2a7e0..e9cfb8ebec8 100644 --- a/doc/tutorials/ferrofluid/ferrofluid_part1.ipynb +++ b/doc/tutorials/ferrofluid/ferrofluid_part1.ipynb @@ -422,7 +422,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now we add the particles with their positions and orientations to our system. Thereby we activate all degrees of freedom for the orientation of the dipole moments. As we want a two dimensional system we only allow the particles to translate in $x$- and $y$-direction and not in $z$-direction by using the fix argument." + "Now we add the particles with their positions and orientations to our system.\n", + "Thereby we activate all degrees of freedom for the orientation of the dipole moments.\n", + "As we want a two dimensional system we only allow the particles to translate\n", + "in $x$- and $y$-direction and not in $z$-direction by using the fix argument." ] }, { @@ -525,7 +528,9 @@ "After that we set up the thermostat which is, in our case, a Langevin thermostat to simulate in an NVT ensemble.\n", "\n", "**Hint:**\n", - "It should be noted that we seed the Langevin thermostat, thus the time evolution of the system is partly predefined. Partly because of the numeric accuracy and the automatic tuning algorithms of Dipolar P3M and DLC where the resulting parameters are slightly different every time. You can change the seed to get a guaranteed different time evolution." + "It should be noted that we seed the Langevin thermostat, thus the time evolution of the system is partly predefined.\n", + "Partly because of the numeric accuracy and the automatic tuning algorithms of Dipolar P3M and DLC where the resulting parameters are slightly different every time.\n", + "You can change the seed to get a guaranteed different time evolution." ] }, { @@ -543,7 +548,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To calculate the dipole-dipole interaction we use the Dipolar P3M method (see Ref. [1]) which is based on the Ewald summation. By default the boundary conditions of the system are set to conducting which means the dielectric constant is set to infinity for the surrounding medium. As we want to simulate a two dimensional system we additionally use the dipolar layer correction (DLC) (see Ref. [2]). As we add DipolarP3M to our system as an actor, a tuning function is started automatically which tries to find the optimal parameters for Dipolar P3M and prints them to the screen. The last line of the output is the value of the tuned skin." + "To calculate the dipole-dipole interaction we use the dipolar P3M method\n", + "(see Ref. [1]) which is based on the Ewald summation.\n", + "By default the boundary conditions of the system are set to conducting which\n", + "means the dielectric constant is set to infinity for the surrounding medium.\n", + "As we want to simulate a two dimensional system we additionally use the dipolar\n", + "layer correction (DLC) (see Ref. [2]).\n", + "As we attach DipolarP3M to our system, a tuning function is started automatically\n", + "which tries to find the optimal parameters for dipolar P3M and prints them to the screen.\n", + "The last line of the output is the value of the tuned skin." ] }, { @@ -558,7 +571,7 @@ "# Setup dipolar P3M and dipolar layer correction\n", "dp3m = espressomd.magnetostatics.DipolarP3M(accuracy=5E-4, prefactor=DIP_LAMBDA * LJ_SIGMA**3 * KT, **CI_DP3M_PARAMS)\n", "mdlc = espressomd.magnetostatics.DLC(actor=dp3m, maxPWerror=1E-4, gap_size=BOX_SIZE - LJ_SIGMA)\n", - "system.actors.add(mdlc)\n", + "system.magnetostatics.solver = mdlc\n", "\n", "# tune verlet list skin\n", "system.cell_system.tune_skin(min_skin=0.4, max_skin=2., tol=0.2, int_steps=100)\n", diff --git a/doc/tutorials/ferrofluid/ferrofluid_part2.ipynb b/doc/tutorials/ferrofluid/ferrofluid_part2.ipynb index 2190810c38d..fe508de7c01 100644 --- a/doc/tutorials/ferrofluid/ferrofluid_part2.ipynb +++ b/doc/tutorials/ferrofluid/ferrofluid_part2.ipynb @@ -174,7 +174,7 @@ "# Setup dipolar P3M and dipolar layer correction (DLC)\n", "dp3m = espressomd.magnetostatics.DipolarP3M(accuracy=5E-4, prefactor=DIP_LAMBDA * LJ_SIGMA**3 * KT)\n", "mdlc = espressomd.magnetostatics.DLC(actor=dp3m, maxPWerror=1E-4, gap_size=box_size - LJ_SIGMA)\n", - "system.actors.add(mdlc)\n", + "system.magnetostatics.solver = mdlc\n", "\n", "# tune verlet list skin again\n", "system.cell_system.tune_skin(min_skin=0.4, max_skin=2., tol=0.2, int_steps=100)\n", @@ -489,7 +489,7 @@ "source": [ "# remove all particles\n", "system.part.clear()\n", - "system.actors.clear()\n", + "system.magnetostatics.clear()\n", "system.thermostat.turn_off()\n", "\n", "# Random dipole moments\n", @@ -523,7 +523,7 @@ "# Setup dipolar P3M and dipolar layer correction\n", "dp3m = espressomd.magnetostatics.DipolarP3M(accuracy=5E-4, prefactor=DIP_LAMBDA * LJ_SIGMA**3 * KT)\n", "mdlc = espressomd.magnetostatics.DLC(actor=dp3m, maxPWerror=1E-4, gap_size=box_size - LJ_SIGMA)\n", - "system.actors.add(mdlc)\n", + "system.magnetostatics.solver = mdlc\n", "\n", "# tune verlet list skin again\n", "system.cell_system.tune_skin(min_skin=0.4, max_skin=2., tol=0.2, int_steps=100)" diff --git a/doc/tutorials/ferrofluid/ferrofluid_part3.ipynb b/doc/tutorials/ferrofluid/ferrofluid_part3.ipynb index a3bd86c38bc..ab23a8dcb5b 100644 --- a/doc/tutorials/ferrofluid/ferrofluid_part3.ipynb +++ b/doc/tutorials/ferrofluid/ferrofluid_part3.ipynb @@ -237,7 +237,7 @@ "\n", "# Setup dipolar P3M\n", "accuracy = 5E-4\n", - "system.actors.add(espressomd.magnetostatics.DipolarP3M(accuracy=accuracy, prefactor=dip_lambda * lj_sigma**3 * kT))" + "system.magnetostatics.solver = espressomd.magnetostatics.DipolarP3M(accuracy=accuracy, prefactor=dip_lambda * lj_sigma**3 * kT)" ] }, { diff --git a/doc/tutorials/raspberry_electrophoresis/raspberry_electrophoresis.ipynb b/doc/tutorials/raspberry_electrophoresis/raspberry_electrophoresis.ipynb index 78535da6197..03bbfd90c3f 100644 --- a/doc/tutorials/raspberry_electrophoresis/raspberry_electrophoresis.ipynb +++ b/doc/tutorials/raspberry_electrophoresis/raspberry_electrophoresis.ipynb @@ -558,7 +558,7 @@ "logging.info(\"Tuning P3M parameters...\")\n", "bjerrum = 2.\n", "p3m = espressomd.electrostatics.P3M(prefactor=bjerrum * temperature, accuracy=0.001)\n", - "system.actors.add(p3m)\n", + "system.electrostatics.solver = p3m\n", "logging.info(\"Tuning complete\")" ] }, diff --git a/doc/tutorials/widom_insertion/widom_insertion.ipynb b/doc/tutorials/widom_insertion/widom_insertion.ipynb index af5cc34dbec..1ae231e39be 100644 --- a/doc/tutorials/widom_insertion/widom_insertion.ipynb +++ b/doc/tutorials/widom_insertion/widom_insertion.ipynb @@ -266,7 +266,7 @@ " \n", " # add P3M\n", " p3m = espressomd.electrostatics.P3M(prefactor=BJERRUM_LENGTH, accuracy=1e-3, **ci_params)\n", - " system.actors.add(p3m)\n", + " system.electrostatics.solver = p3m\n", "```" ] }, @@ -373,7 +373,7 @@ " that performs ``n_mc_steps`` Widom particle insertions every ``n_md_steps`` MD steps in a loop\n", " that repeats ``sample_size`` times and returns the chemical potential and its error as a tuple via\n", " [widom.calculate_excess_chemical_potential()](https://espressomd.github.io/doc/espressomd.html#espressomd.reaction_methods.WidomInsertion.calculate_excess_chemical_potential)\n", - "* Implement a function ``system_teardown()`` that removes all actors and turns off the thermostat" + "* Implement a function ``system_teardown()`` that removes the electrostatics solver and turns off the thermostat" ] }, { @@ -401,7 +401,7 @@ " return excess_chemical_potential, error_excess_chemical_potential\n", "\n", "def system_teardown():\n", - " system.actors.clear()\n", + " system.electrostatics.clear()\n", " system.thermostat.turn_off()\n", "```" ] diff --git a/maintainer/benchmarks/p3m.py b/maintainer/benchmarks/p3m.py index c511376be99..99994b08338 100644 --- a/maintainer/benchmarks/p3m.py +++ b/maintainer/benchmarks/p3m.py @@ -143,7 +143,7 @@ system.integrator.run(min(3 * measurement_steps, 3000)) print("Tune p3m") p3m = espressomd.electrostatics.P3M(**p3m_params) -system.actors.add(p3m) +system.electrostatics.solver = p3m print("Equilibration") system.integrator.run(min(3 * measurement_steps, 3000)) print("Tune skin: {:.3f}".format(system.cell_system.tune_skin( diff --git a/samples/drude_bmimpf6.py b/samples/drude_bmimpf6.py index c689d43e267..20bb311cb7e 100644 --- a/samples/drude_bmimpf6.py +++ b/samples/drude_bmimpf6.py @@ -254,7 +254,7 @@ def combination_rule_sigma(rule, sig1, sig2): print("\n-->Tune P3M CPU") p3m = espressomd.electrostatics.P3M(**p3m_params) -system.actors.add(p3m) +system.electrostatics.solver = p3m cation_drude_parts = [] diff --git a/samples/load_checkpoint.py b/samples/load_checkpoint.py index de96e0fc530..a34ff7ff4ba 100644 --- a/samples/load_checkpoint.py +++ b/samples/load_checkpoint.py @@ -40,9 +40,8 @@ # print out actors -print("\n### current active actors ###") -for act in system.actors.active_actors: - print(act) +print("\n### current active electrostatics method ###") +print(system.electrostatics.solver) # test user variable print("\n### user variable test ###") diff --git a/samples/minimal-charged-particles.py b/samples/minimal-charged-particles.py index 8c22f58af5b..c03223fe03a 100644 --- a/samples/minimal-charged-particles.py +++ b/samples/minimal-charged-particles.py @@ -95,7 +95,7 @@ # P3M setup after charge assigned ############################################################# p3m = espressomd.electrostatics.P3M(prefactor=1.0, accuracy=1e-2) -system.actors.add(p3m) +system.electrostatics.solver = p3m ############################################################# # Integration # diff --git a/samples/p3m.py b/samples/p3m.py index 7745b69b587..63868d24459 100644 --- a/samples/p3m.py +++ b/samples/p3m.py @@ -116,15 +116,13 @@ p3m = espressomd.electrostatics.P3M(prefactor=1.0, accuracy=1e-3) print("\nSCRIPT--->Add actor (automatic tuning)\n") -system.actors.add(p3m) +system.electrostatics.solver = p3m print("\nSCRIPT--->P3M parameter:\n") p3m_params = p3m.get_params() for key, val in p3m_params.items(): print(f"{key} = {val}") -print(system.actors) - ############################################################# # Warmup Integration # ############################################################# diff --git a/samples/save_checkpoint.py b/samples/save_checkpoint.py index 052a3c0f3fb..fa8a2ec4590 100644 --- a/samples/save_checkpoint.py +++ b/samples/save_checkpoint.py @@ -74,7 +74,7 @@ q=np.resize((1, -1), n_part)) p3m = espressomd.electrostatics.P3M(prefactor=1.0, accuracy=1e-2) -system.actors.add(p3m) +system.electrostatics.solver = p3m # let's also register the p3m reference for easy access later checkpoint.register("p3m") diff --git a/samples/visualization_charged.py b/samples/visualization_charged.py index d722cec0db6..9b6d3a00e9d 100644 --- a/samples/visualization_charged.py +++ b/samples/visualization_charged.py @@ -131,7 +131,7 @@ def combination_rule_sigma(rule, sig1, sig2): print("Tune p3m") p3m = espressomd.electrostatics.P3M(prefactor=coulomb_prefactor, accuracy=1e-1) -system.actors.add(p3m) +system.electrostatics.solver = p3m system.thermostat.set_langevin(kT=temperature, gamma=2.0, seed=42) diff --git a/samples/widom_insertion.py b/samples/widom_insertion.py index d0ff627b348..ea5fa6fc101 100644 --- a/samples/widom_insertion.py +++ b/samples/widom_insertion.py @@ -74,7 +74,7 @@ epsilon=wca_eps, sigma=wca_sig) p3m = espressomd.electrostatics.P3M(prefactor=2.0, accuracy=1e-3) -system.actors.add(p3m) +system.electrostatics.solver = p3m p3m_params = p3m.get_params() for key, value in p3m_params.items(): print(f"{key} = {value}") diff --git a/src/core/actor/optional.hpp b/src/core/actor/optional.hpp new file mode 100644 index 00000000000..b4e36b9fe80 --- /dev/null +++ b/src/core/actor/optional.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +template const T *get_ptr(std::optional const &opt) { + return opt ? std::addressof(*opt) : nullptr; +} diff --git a/src/core/actor/registration.hpp b/src/core/actor/registration.hpp index bb359ab773f..2977148bed0 100644 --- a/src/core/actor/registration.hpp +++ b/src/core/actor/registration.hpp @@ -20,27 +20,26 @@ #ifndef ESPRESSO_SRC_CORE_ACTOR_REGISTRATION_HPP #define ESPRESSO_SRC_CORE_ACTOR_REGISTRATION_HPP -#include "actor/visitors.hpp" - -#include -#include +#include +#include +#include #include +#include -/** @brief Register an actor in a thread-safe manner. */ template -void add_actor(boost::optional &active_actor, - std::shared_ptr const &actor, void (&on_actor_change)(), - bool (&flag_all_reduce)(bool)) { - auto const cleanup_if_any_rank_failed = [&](bool this_failed) { - auto const any_failed = flag_all_reduce(this_failed); - if (any_failed) { - active_actor = boost::none; +void add_actor(boost::mpi::communicator const &comm, + std::optional &active_actor, + std::shared_ptr const &actor, void (&on_actor_change)()) { + std::optional other = actor; + auto const cleanup_if_any_rank_failed = [&](bool failed) { + if (boost::mpi::all_reduce(comm, failed, std::logical_or<>())) { + active_actor.swap(other); on_actor_change(); } }; try { - active_actor = actor; + active_actor.swap(other); actor->on_activation(); on_actor_change(); cleanup_if_any_rank_failed(false); @@ -50,11 +49,4 @@ void add_actor(boost::optional &active_actor, } } -template -void remove_actor(boost::optional &active_actor, - std::shared_ptr const &actor, void (&on_actor_change)()) { - active_actor = boost::none; - on_actor_change(); -} - #endif diff --git a/src/core/actor/visit_try_catch.hpp b/src/core/actor/visit_try_catch.hpp index cc4feccf6ff..0d584550b0f 100644 --- a/src/core/actor/visit_try_catch.hpp +++ b/src/core/actor/visit_try_catch.hpp @@ -21,17 +21,16 @@ #include "errorhandling.hpp" -#include -#include - +#include #include #include +#include /** @brief Run a kernel on a variant and queue errors. */ template void visit_active_actor_try_catch(Visitor &&visitor, Variant &actor) { try { - boost::apply_visitor(visitor, actor); + std::visit(visitor, actor); } catch (std::runtime_error const &err) { runtimeErrorMsg() << err.what(); } @@ -40,7 +39,7 @@ void visit_active_actor_try_catch(Visitor &&visitor, Variant &actor) { /** @brief Run a kernel on a variant and queue errors. */ template void visit_active_actor_try_catch(Visitor &&visitor, - boost::optional &actor) { + std::optional &actor) { if (actor) { visit_active_actor_try_catch(std::forward(visitor), *actor); } diff --git a/src/core/actor/visitors.hpp b/src/core/actor/visitors.hpp index 8a421f21197..0c0433b735f 100644 --- a/src/core/actor/visitors.hpp +++ b/src/core/actor/visitors.hpp @@ -22,30 +22,15 @@ #include "actor/traits.hpp" -#include - -#include -#include - #include #include +#include #include #include - -struct GetActorName : public boost::static_visitor { - template auto operator()(std::shared_ptr const &) const { - return Utils::demangle(); - } -}; - -/** @brief Get the symbol name of an actor. */ -template auto get_actor_name(Variant const &variant) { - return boost::apply_visitor(GetActorName(), variant); -} +#include /** @brief Get an actor of a specific type, recursively. */ -template -struct GetActorByType : public boost::static_visitor> { +template struct GetActorByType { private: template static constexpr bool is_exact_match_v = std::is_same_v; @@ -62,7 +47,7 @@ struct GetActorByType : public boost::static_visitor> { template and is_layer_correction_v> * = nullptr> auto operator()(std::shared_ptr const &obj) const { - return boost::apply_visitor(*this, obj->base_solver); + return std::visit(*this, obj->base_solver); } template > { /** @brief Get an active actor of a specific type, recursively. */ template std::shared_ptr get_actor_by_type(Variant const &variant) { - return boost::apply_visitor(GetActorByType(), variant); + return std::visit(GetActorByType(), variant); } template std::shared_ptr -get_actor_by_type(boost::optional const &optional) { +get_actor_by_type(std::optional const &optional) { return (optional) ? get_actor_by_type(*optional) : nullptr; } /** @brief Check if an actor of a specific type is active, recursively. */ -template -struct HasActorOfType : public boost::static_visitor { +template struct HasActorOfType { private: template static constexpr bool is_exact_match_v = std::is_same_v; @@ -104,7 +88,7 @@ struct HasActorOfType : public boost::static_visitor { template and is_layer_correction_v> * = nullptr> auto operator()(std::shared_ptr const &obj) const { - return boost::apply_visitor(*this, obj->base_solver); + return std::visit(*this, obj->base_solver); } template { /** @brief Check if an actor of a specific type is active, recursively. */ template auto has_actor_of_type(Variant const &variant) { - return boost::apply_visitor(HasActorOfType(), variant); + return std::visit(HasActorOfType(), variant); } template -auto has_actor_of_type(boost::optional const &optional) { +auto has_actor_of_type(std::optional const &optional) { return (optional) ? has_actor_of_type(*optional) : false; } -/** Check whether two actors are identical by pointer. */ -struct ActorEquality : public boost::static_visitor { - template < - typename T, typename U, - typename std::enable_if_t, std::nullptr_t> = nullptr> - bool operator()(std::shared_ptr const &lhs, - std::shared_ptr const &rhs) const { - return lhs == rhs; - } - - template , std::nullptr_t> = - nullptr> - bool operator()(std::shared_ptr const &, - std::shared_ptr const &) const { - return false; - } -}; - -/** @brief Check if an actor is already stored in an optional. */ -template -bool is_already_stored(std::shared_ptr const &actor, - boost::optional const &active_actor) { - auto const visitor = std::bind(ActorEquality(), actor, std::placeholders::_1); - return active_actor and boost::apply_visitor(visitor, *active_actor); -} - #endif diff --git a/src/core/constraints/ShapeBasedConstraint.cpp b/src/core/constraints/ShapeBasedConstraint.cpp index 3d5ad6f286c..290e813b5af 100644 --- a/src/core/constraints/ShapeBasedConstraint.cpp +++ b/src/core/constraints/ShapeBasedConstraint.cpp @@ -92,7 +92,7 @@ ParticleForce ShapeBasedConstraint::force(Particle const &p, if (dist > 0) { outer_normal_vec = -dist_vec / dist; pf = calc_non_bonded_pair_force(p, part_rep, ia_params, dist_vec, dist, - coulomb_kernel.get_ptr()); + get_ptr(coulomb_kernel)); #ifdef DPD if (thermo_switch & THERMO_DPD) { dpd_force = @@ -104,7 +104,7 @@ ParticleForce ShapeBasedConstraint::force(Particle const &p, } else if (m_penetrable && (dist <= 0)) { if ((!m_only_positive) && (dist < 0)) { pf = calc_non_bonded_pair_force(p, part_rep, ia_params, dist_vec, -dist, - coulomb_kernel.get_ptr()); + get_ptr(coulomb_kernel)); #ifdef DPD if (thermo_switch & THERMO_DPD) { dpd_force = dpd_pair_force(p, part_rep, ia_params, dist_vec, dist, @@ -146,11 +146,11 @@ void ShapeBasedConstraint::add_energy(const Particle &p, m_shape->calculate_dist(folded_pos, dist, vec); if (dist > 0) { energy = calc_non_bonded_pair_energy(p, part_rep, ia_params, vec, dist, - coulomb_kernel.get_ptr()); + get_ptr(coulomb_kernel)); } else if ((dist <= 0) && m_penetrable) { if (!m_only_positive && (dist < 0)) { energy = calc_non_bonded_pair_energy(p, part_rep, ia_params, vec, -dist, - coulomb_kernel.get_ptr()); + get_ptr(coulomb_kernel)); } } else { runtimeErrorMsg() << "Constraint violated by particle " << p.id(); diff --git a/src/core/electrostatics/coulomb.cpp b/src/core/electrostatics/coulomb.cpp index ee6271af945..238a0408828 100644 --- a/src/core/electrostatics/coulomb.cpp +++ b/src/core/electrostatics/coulomb.cpp @@ -41,11 +41,8 @@ #include #include -#include #include #include -#include -#include #include #include @@ -53,9 +50,13 @@ #include #include #include +#include +#include #include #include #include +#include +#include namespace Coulomb { @@ -63,8 +64,7 @@ Solver const &get_coulomb() { return System::get_system().coulomb; } void Solver::sanity_checks() const { if (solver) { - boost::apply_visitor([](auto const &actor) { actor->sanity_checks(); }, - *solver); + std::visit([](auto const &actor) { actor->sanity_checks(); }, *solver); } } @@ -80,8 +80,7 @@ void Solver::on_boxl_change() { void Solver::on_node_grid_change() { if (solver) { - boost::apply_visitor([](auto &actor) { actor->on_node_grid_change(); }, - *solver); + std::visit([](auto &actor) { actor->on_node_grid_change(); }, *solver); } } @@ -95,7 +94,7 @@ void Solver::on_cell_structure_change() { [](auto &actor) { actor->on_cell_structure_change(); }, solver); } -struct LongRangePressure : public boost::static_visitor { +struct LongRangePressure { explicit LongRangePressure(ParticleRange const &particles) : m_particles{particles} {} @@ -129,12 +128,12 @@ struct LongRangePressure : public boost::static_visitor { Utils::Vector9d Solver::calc_pressure_long_range(ParticleRange const &particles) const { if (solver) { - return boost::apply_visitor(LongRangePressure(particles), *solver); + return std::visit(LongRangePressure(particles), *solver); } return {}; } -struct ShortRangeCutoff : public boost::static_visitor { +struct ShortRangeCutoff { #ifdef P3M auto operator()(std::shared_ptr const &actor) const { return actor->p3m.params.r_cut; @@ -142,7 +141,7 @@ struct ShortRangeCutoff : public boost::static_visitor { auto operator()(std::shared_ptr const &actor) const { return std::max(actor->elc.space_layer, - boost::apply_visitor(*this, actor->base_solver)); + std::visit(*this, actor->base_solver)); } #endif // P3M #ifdef MMM1D_GPU @@ -168,12 +167,12 @@ struct ShortRangeCutoff : public boost::static_visitor { double Solver::cutoff() const { if (solver) { - return boost::apply_visitor(ShortRangeCutoff(), *solver); + return std::visit(ShortRangeCutoff(), *solver); } return -1.0; } -struct EventOnObservableCalc : public boost::static_visitor { +struct EventOnObservableCalc { template void operator()(std::shared_ptr const &) const {} #ifdef P3M @@ -182,7 +181,7 @@ struct EventOnObservableCalc : public boost::static_visitor { } void operator()(std::shared_ptr const &actor) const { - boost::apply_visitor(*this, actor->base_solver); + std::visit(*this, actor->base_solver); } #endif // P3M }; @@ -190,13 +189,13 @@ struct EventOnObservableCalc : public boost::static_visitor { void Solver::on_observable_calc() { if (reinit_on_observable_calc) { if (solver) { - boost::apply_visitor(EventOnObservableCalc(), *solver); + std::visit(EventOnObservableCalc(), *solver); } reinit_on_observable_calc = false; } } -struct LongRangeForce : public boost::static_visitor { +struct LongRangeForce { explicit LongRangeForce(ParticleRange const &particles) : m_particles(particles) {} @@ -247,7 +246,7 @@ struct LongRangeForce : public boost::static_visitor { ParticleRange const &m_particles; }; -struct LongRangeEnergy : public boost::static_visitor { +struct LongRangeEnergy { explicit LongRangeEnergy(ParticleRange const &particles) : m_particles(particles) {} @@ -283,13 +282,13 @@ struct LongRangeEnergy : public boost::static_visitor { void Solver::calc_long_range_force(ParticleRange const &particles) const { if (solver) { - boost::apply_visitor(LongRangeForce(particles), *solver); + std::visit(LongRangeForce(particles), *solver); } } double Solver::calc_energy_long_range(ParticleRange const &particles) const { if (solver) { - return boost::apply_visitor(LongRangeEnergy(particles), *solver); + return std::visit(LongRangeEnergy(particles), *solver); } return 0.; } @@ -352,12 +351,6 @@ void check_charge_neutrality(double relative_tolerance) { } } -namespace detail { -bool flag_all_reduce(bool flag) { - return boost::mpi::all_reduce(comm_cart, flag, std::logical_or<>()); -} -} // namespace detail - } // namespace Coulomb #endif // ELECTROSTATICS diff --git a/src/core/electrostatics/coulomb.hpp b/src/core/electrostatics/coulomb.hpp index b8370f2cc32..e5e5e6934ed 100644 --- a/src/core/electrostatics/coulomb.hpp +++ b/src/core/electrostatics/coulomb.hpp @@ -37,20 +37,13 @@ #include "electrostatics/reaction_field.hpp" #include "electrostatics/scafacos.hpp" -#include "ParticleRange.hpp" - -#include - -#include -#include - #include #include #include #include /** Get the electrostatics prefactor. */ -struct GetCoulombPrefactor : public boost::static_visitor { +struct GetCoulombPrefactor { template double operator()(std::shared_ptr const &actor) const { return actor->prefactor; diff --git a/src/core/electrostatics/coulomb_inline.hpp b/src/core/electrostatics/coulomb_inline.hpp index a032acf9771..a7d0e5359d8 100644 --- a/src/core/electrostatics/coulomb_inline.hpp +++ b/src/core/electrostatics/coulomb_inline.hpp @@ -32,21 +32,19 @@ #include #include -#include -#include - #include #include +#include #include #include +#include namespace Coulomb { -struct ShortRangeForceKernel - : public boost::static_visitor< - boost::optional> { +struct ShortRangeForceKernel { - using kernel_type = result_type::value_type; + using kernel_type = Solver::ShortRangeForceKernel; + using result_type = std::optional; #ifdef ELECTROSTATICS template @@ -61,7 +59,7 @@ struct ShortRangeForceKernel #ifdef P3M auto operator()(std::shared_ptr const &ptr) const { - return boost::apply_visitor(*this, ptr->base_solver); + return std::visit(*this, ptr->base_solver); } #endif // P3M @@ -73,11 +71,10 @@ struct ShortRangeForceKernel #endif // ELECTROSTATICS }; -struct ShortRangeForceCorrectionsKernel - : public boost::static_visitor< - boost::optional> { +struct ShortRangeForceCorrectionsKernel { - using kernel_type = result_type::value_type; + using kernel_type = Solver::ShortRangeForceCorrectionsKernel; + using result_type = std::optional; template result_type operator()(std::shared_ptr const &) const { @@ -95,11 +92,10 @@ struct ShortRangeForceCorrectionsKernel #endif // P3M }; -struct ShortRangePressureKernel - : public boost::static_visitor< - boost::optional> { +struct ShortRangePressureKernel { - using kernel_type = result_type::value_type; + using kernel_type = Solver::ShortRangePressureKernel; + using result_type = std::optional; #ifdef ELECTROSTATICS template > { +struct ShortRangeEnergyKernel { - using kernel_type = result_type::value_type; + using kernel_type = Solver::ShortRangeEnergyKernel; + using result_type = std::optional; #ifdef ELECTROSTATICS template @@ -139,7 +134,7 @@ struct ShortRangeEnergyKernel result_type operator()(std::shared_ptr const &ptr) const { auto const &actor = *ptr; - auto const energy_kernel = boost::apply_visitor(*this, actor.base_solver); + auto const energy_kernel = std::visit(*this, actor.base_solver); return kernel_type{[&actor, energy_kernel]( Particle const &p1, Particle const &p2, double q1q2, Utils::Vector3d const &d, double dist) { @@ -165,45 +160,45 @@ struct ShortRangeEnergyKernel #endif // ELECTROSTATICS }; -inline boost::optional +inline std::optional Solver::pair_force_kernel() const { #ifdef ELECTROSTATICS if (solver) { auto const visitor = Coulomb::ShortRangeForceKernel(); - return boost::apply_visitor(visitor, *solver); + return std::visit(visitor, *solver); } #endif // ELECTROSTATICS return {}; } -inline boost::optional +inline std::optional Solver::pair_force_elc_kernel() const { #ifdef ELECTROSTATICS if (solver) { auto const visitor = Coulomb::ShortRangeForceCorrectionsKernel(); - return boost::apply_visitor(visitor, *solver); + return std::visit(visitor, *solver); } #endif // ELECTROSTATICS return {}; } -inline boost::optional +inline std::optional Solver::pair_pressure_kernel() const { #ifdef ELECTROSTATICS if (solver) { auto const visitor = Coulomb::ShortRangePressureKernel(); - return boost::apply_visitor(visitor, *solver); + return std::visit(visitor, *solver); } #endif // ELECTROSTATICS return {}; } -inline boost::optional +inline std::optional Solver::pair_energy_kernel() const { #ifdef ELECTROSTATICS if (solver) { auto const visitor = Coulomb::ShortRangeEnergyKernel(); - return boost::apply_visitor(visitor, *solver); + return std::visit(visitor, *solver); } #endif // ELECTROSTATICS return {}; diff --git a/src/core/electrostatics/elc.cpp b/src/core/electrostatics/elc.cpp index 52ff2dffedf..3ce90953a84 100644 --- a/src/core/electrostatics/elc.cpp +++ b/src/core/electrostatics/elc.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include /** \name Product decomposition data organization @@ -985,7 +986,7 @@ void ElectrostaticLayerCorrection::sanity_checks_dielectric_contrasts() const { } void ElectrostaticLayerCorrection::adapt_solver() { - boost::apply_visitor( + std::visit( [this](auto &solver) { set_prefactor(solver->prefactor); solver->p3m.params.epsilon = P3M_EPSILON_METALLIC; @@ -1005,7 +1006,7 @@ void ElectrostaticLayerCorrection::recalc_box_h() { void ElectrostaticLayerCorrection::recalc_space_layer() { if (elc.dielectric_contrast_on) { - auto const p3m_r_cut = boost::apply_visitor( + auto const p3m_r_cut = std::visit( [](auto &solver) { return solver->p3m.params.r_cut; }, base_solver); // recalculate the space layer size: // 1. set the space_layer to be 1/3 of the gap size, so that box = layer @@ -1166,7 +1167,7 @@ void modify_p3m_sums(elc_data const &elc, CoulombP3M &solver, double ElectrostaticLayerCorrection::long_range_energy( ParticleRange const &particles) const { - auto const energy = boost::apply_visitor( + auto const energy = std::visit( [this, &particles](auto const &solver_ptr) { auto &solver = *solver_ptr; @@ -1202,7 +1203,7 @@ double ElectrostaticLayerCorrection::long_range_energy( void ElectrostaticLayerCorrection::add_long_range_forces( ParticleRange const &particles) const { - boost::apply_visitor( + std::visit( [this, &particles](auto const &solver_ptr) { auto &solver = *solver_ptr; if (elc.dielectric_contrast_on) { diff --git a/src/core/electrostatics/elc.hpp b/src/core/electrostatics/elc.hpp index 4b1326790e6..197aa5fe0f4 100644 --- a/src/core/electrostatics/elc.hpp +++ b/src/core/electrostatics/elc.hpp @@ -45,13 +45,12 @@ #include -#include -#include - #include +#include #include #include #include +#include struct ElectrostaticLayerCorrection; @@ -168,7 +167,7 @@ struct elc_data { struct ElectrostaticLayerCorrection : public Coulomb::Actor { - using BaseSolver = boost::variant< + using BaseSolver = std::variant< #ifdef CUDA std::shared_ptr, #endif // CUDA @@ -245,7 +244,7 @@ struct ElectrostaticLayerCorrection * When ELC is used with dielectric contrasts, the short-range cutoff needs * to be smaller than the gap size to allow placement of the image charges. */ - boost::optional veto_r_cut(double r_cut) const { + std::optional veto_r_cut(double r_cut) const { if (elc.dielectric_contrast_on and r_cut >= elc.gap_size) { return {std::string("conflict with ELC w/ dielectric contrasts")}; } @@ -257,7 +256,7 @@ struct ElectrostaticLayerCorrection Particle const &p2) const { double energy = 0.; if (elc.dielectric_contrast_on) { - energy = boost::apply_visitor( + energy = std::visit( [this, &p1, &p2, q1q2](auto &p3m_ptr) { auto const &pos1 = p1.pos(); auto const &pos2 = p2.pos(); @@ -284,7 +283,7 @@ struct ElectrostaticLayerCorrection void add_pair_force_corrections(Particle &p1, Particle &p2, double q1q2) const { if (elc.dielectric_contrast_on) { - boost::apply_visitor( + std::visit( [this, &p1, &p2, q1q2](auto &p3m_ptr) { auto const &pos1 = p1.pos(); auto const &pos2 = p2.pos(); @@ -337,7 +336,7 @@ struct ElectrostaticLayerCorrection double calc_energy(ParticleRange const &particles) const; template void visit_base_solver(Visitor &&visitor) const { - boost::apply_visitor(visitor, base_solver); + std::visit(visitor, base_solver); } }; diff --git a/src/core/electrostatics/icc.cpp b/src/core/electrostatics/icc.cpp index 55eaf2a2b57..f4e6dc4c3db 100644 --- a/src/core/electrostatics/icc.cpp +++ b/src/core/electrostatics/icc.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include /** Calculate the electrostatic forces between source charges (= real charges) @@ -77,9 +78,9 @@ static void force_calc_icc( // calc ICC forces cell_structure.non_bonded_loop( - [coulomb_kernel_ptr = coulomb_kernel.get_ptr(), - elc_kernel_ptr = elc_kernel.get_ptr()](Particle &p1, Particle &p2, - Distance const &d) { + [coulomb_kernel_ptr = get_ptr(coulomb_kernel), + elc_kernel_ptr = get_ptr(elc_kernel)](Particle &p1, Particle &p2, + Distance const &d) { auto const q1q2 = p1.q() * p2.q(); if (q1q2 != 0.) { auto force = (*coulomb_kernel_ptr)(q1q2, d.vec21, std::sqrt(d.dist2)); @@ -107,8 +108,8 @@ void ICCStar::iteration(CellStructure &cell_structure, return; } - auto const prefactor = boost::apply_visitor(GetCoulombPrefactor(), - *(Coulomb::get_coulomb().solver)); + auto const prefactor = + std::visit(GetCoulombPrefactor(), *(Coulomb::get_coulomb().solver)); auto const pref = 1. / (prefactor * 2. * Utils::pi()); auto const kernel = Coulomb::get_coulomb().pair_force_kernel(); auto const elc_kernel = Coulomb::get_coulomb().pair_force_elc_kernel(); @@ -237,7 +238,7 @@ void ICCStar::on_activation() const { on_particle_charge_change(); } -struct SanityChecksICC : public boost::static_visitor { +struct SanityChecksICC { template void operator()(std::shared_ptr const &actor) const {} #ifdef P3M @@ -252,7 +253,7 @@ struct SanityChecksICC : public boost::static_visitor { if (actor->elc.dielectric_contrast_on) { throw std::runtime_error("ICC conflicts with ELC dielectric contrast"); } - boost::apply_visitor(*this, actor->base_solver); + std::visit(*this, actor->base_solver); } #endif // P3M [[noreturn]] void operator()(std::shared_ptr const &) const { @@ -274,7 +275,7 @@ void ICCStar::sanity_check() const { void ICCStar::sanity_checks_active_solver() const { if (Coulomb::get_coulomb().solver) { - boost::apply_visitor(SanityChecksICC(), *(Coulomb::get_coulomb().solver)); + std::visit(SanityChecksICC(), *(Coulomb::get_coulomb().solver)); } else { throw std::runtime_error("An electrostatics solver is needed by ICC"); } @@ -282,8 +283,8 @@ void ICCStar::sanity_checks_active_solver() const { void update_icc_particles() { if (Coulomb::get_coulomb().extension) { - if (auto icc = boost::get>( - Coulomb::get_coulomb().extension.get_ptr())) { + if (auto icc = std::get_if>( + get_ptr(Coulomb::get_coulomb().extension))) { (**icc).iteration(cell_structure, cell_structure.local_particles(), cell_structure.ghost_particles()); } diff --git a/src/core/electrostatics/p3m.cpp b/src/core/electrostatics/p3m.cpp index 6eb12ae534b..10e6ed3cf15 100644 --- a/src/core/electrostatics/p3m.cpp +++ b/src/core/electrostatics/p3m.cpp @@ -62,7 +62,6 @@ #include #include #include -#include #include #include @@ -71,8 +70,10 @@ #include #include #include +#include #include #include +#include void CoulombP3M::count_charged_particles() { auto local_n = 0; @@ -458,10 +459,10 @@ double CoulombP3M::long_range_kernel(bool force_flag, bool energy_flag, // Note: after these calls, the grids are in the order yzx and not xyz // anymore!!! /* The dipole moment is only needed if we don't have metallic boundaries. */ - auto const box_dipole = (p3m.params.epsilon != P3M_EPSILON_METALLIC) - ? boost::make_optional(calc_dipole_moment( - comm_cart, particles, box_geo)) - : boost::none; + auto const box_dipole = + (p3m.params.epsilon != P3M_EPSILON_METALLIC) + ? calc_dipole_moment(comm_cart, particles, box_geo) + : Utils::Vector3d::broadcast(0.); auto const volume = box_geo.volume(); auto const pref = 4. * Utils::pi() / volume / (2. * p3m.params.epsilon + 1.); @@ -513,7 +514,7 @@ double CoulombP3M::long_range_kernel(bool force_flag, bool energy_flag, // add dipole forces if (p3m.params.epsilon != P3M_EPSILON_METALLIC) { - auto const dm = prefactor * pref * box_dipole.value(); + auto const dm = prefactor * pref * box_dipole; for (auto &p : particles) { p.force() -= p.q() * dm; } @@ -540,7 +541,7 @@ double CoulombP3M::long_range_kernel(bool force_flag, bool energy_flag, (2. * volume * Utils::sqr(p3m.params.alpha)); /* dipole correction */ if (p3m.params.epsilon != P3M_EPSILON_METALLIC) { - energy += pref * box_dipole.value().norm2(); + energy += pref * box_dipole.norm2(); } } return prefactor * energy; @@ -579,7 +580,7 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { m_logger->log_tuning_start(); } - boost::optional + std::optional layer_correction_veto_r_cut(double r_cut) const override { auto const &solver = Coulomb::get_coulomb().solver; if (auto actor = get_actor_by_type(solver)) { diff --git a/src/core/electrostatics/registration.hpp b/src/core/electrostatics/registration.hpp deleted file mode 100644 index 4ccb7ae23e5..00000000000 --- a/src/core/electrostatics/registration.hpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2022 The ESPResSo project - * - * This file is part of ESPResSo. - * - * ESPResSo is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ESPResSo is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef ESPRESSO_SRC_CORE_ELECTROSTATICS_COULOMB_REGISTRATION_HPP -#define ESPRESSO_SRC_CORE_ELECTROSTATICS_COULOMB_REGISTRATION_HPP - -#include "config/config.hpp" - -#ifdef ELECTROSTATICS - -#include "electrostatics/coulomb.hpp" - -#include "actor/registration.hpp" -#include "actor/visitors.hpp" - -#include "event.hpp" -#include "system/System.hpp" - -#include -#include - -#include -#include -#include - -namespace Coulomb { - -template ::value> * = nullptr> -void add_actor(std::shared_ptr const &actor) { - auto &system = System::get_system(); - if (system.coulomb.solver) { - auto const name = get_actor_name(*system.coulomb.solver); - throw std::runtime_error("An electrostatics solver is already active (" + - name + ")"); - } - add_actor(system.coulomb.solver, actor, ::on_coulomb_change, - detail::flag_all_reduce); -} - -template ::value> * = nullptr> -void remove_actor(std::shared_ptr const &actor) { - auto &system = System::get_system(); - if (not is_already_stored(actor, system.coulomb.solver)) { - throw std::runtime_error( - "The given electrostatics solver is not currently active"); - } - remove_actor(system.coulomb.solver, actor, ::on_coulomb_change); -} - -template ::value> * = nullptr> -void add_actor(std::shared_ptr const &actor) { - auto &system = System::get_system(); - if (system.coulomb.extension) { - auto const name = get_actor_name(*system.coulomb.extension); - throw std::runtime_error("An electrostatics extension is already active (" + - name + ")"); - } - add_actor(system.coulomb.extension, actor, ::on_coulomb_change, - detail::flag_all_reduce); -} - -template ::value> * = nullptr> -void remove_actor(std::shared_ptr const &actor) { - auto &system = System::get_system(); - if (not is_already_stored(actor, system.coulomb.extension)) { - throw std::runtime_error( - "The given electrostatics extension is not currently active"); - } - remove_actor(system.coulomb.extension, actor, ::on_coulomb_change); -} - -} // namespace Coulomb - -#endif // ELECTROSTATICS -#endif diff --git a/src/core/electrostatics/solver.hpp b/src/core/electrostatics/solver.hpp index aeeea6ea09b..17b0ce4d584 100644 --- a/src/core/electrostatics/solver.hpp +++ b/src/core/electrostatics/solver.hpp @@ -21,6 +21,7 @@ #include "config/config.hpp" +#include "actor/optional.hpp" #include "actor/traits.hpp" #include "Particle.hpp" @@ -28,12 +29,11 @@ #include -#include -#include - #include #include +#include #include +#include #ifdef ELECTROSTATICS // forward declarations @@ -56,38 +56,34 @@ struct CoulombScafacos; #endif using ElectrostaticsActor = - boost::variant, + std::variant, #ifdef P3M - std::shared_ptr, + std::shared_ptr, #ifdef CUDA - std::shared_ptr, + std::shared_ptr, #endif // CUDA - std::shared_ptr, + std::shared_ptr, #endif // P3M - std::shared_ptr, + std::shared_ptr, #ifdef MMM1D_GPU - std::shared_ptr, + std::shared_ptr, #endif // MMM1D_GPU #ifdef SCAFACOS - std::shared_ptr, + std::shared_ptr, #endif // SCAFACOS - std::shared_ptr>; + std::shared_ptr>; -using ElectrostaticsExtension = boost::variant>; +using ElectrostaticsExtension = std::variant>; #endif // ELECTROSTATICS namespace Coulomb { -namespace detail { -bool flag_all_reduce(bool flag); -} // namespace detail - struct Solver { #ifdef ELECTROSTATICS /// @brief Main electrostatics solver. - boost::optional solver; + std::optional solver; /// @brief Extension that modifies the solver behavior. - boost::optional extension; + std::optional extension; /// @brief Whether to reinitialize the solver on observable calculation. bool reinit_on_observable_calc = false; @@ -119,10 +115,10 @@ struct Solver { std::function; - inline boost::optional pair_force_kernel() const; - inline boost::optional pair_pressure_kernel() const; - inline boost::optional pair_energy_kernel() const; - inline boost::optional + inline std::optional pair_force_kernel() const; + inline std::optional pair_pressure_kernel() const; + inline std::optional pair_energy_kernel() const; + inline std::optional pair_force_elc_kernel() const; }; diff --git a/src/core/energy.cpp b/src/core/energy.cpp index 81dcee059a6..f45174017bd 100644 --- a/src/core/energy.cpp +++ b/src/core/energy.cpp @@ -69,7 +69,7 @@ std::shared_ptr calculate_energy() { auto const dipoles_kernel = system.dipoles.pair_energy_kernel(); short_range_loop( - [&obs_energy, coulomb_kernel_ptr = coulomb_kernel.get_ptr()]( + [&obs_energy, coulomb_kernel_ptr = get_ptr(coulomb_kernel)]( Particle const &p1, int bond_id, Utils::Span partners) { auto const &iaparams = *bonded_ia_params.at(bond_id); auto const result = @@ -80,8 +80,8 @@ std::shared_ptr calculate_energy() { } return true; }, - [&obs_energy, coulomb_kernel_ptr = coulomb_kernel.get_ptr(), - dipoles_kernel_ptr = dipoles_kernel.get_ptr()]( + [&obs_energy, coulomb_kernel_ptr = get_ptr(coulomb_kernel), + dipoles_kernel_ptr = get_ptr(dipoles_kernel)]( Particle const &p1, Particle const &p2, Distance const &d) { add_non_bonded_pair_energy(p1, p2, d.vec21, sqrt(d.dist2), d.dist2, coulomb_kernel_ptr, dipoles_kernel_ptr, @@ -136,7 +136,7 @@ double particle_short_range_energy_contribution(int pid) { if (auto const p = cell_structure.get_local_particle(pid)) { auto const &coulomb = System::get_system().coulomb; auto const coulomb_kernel = coulomb.pair_energy_kernel(); - auto kernel = [&ret, coulomb_kernel_ptr = coulomb_kernel.get_ptr()]( + auto kernel = [&ret, coulomb_kernel_ptr = get_ptr(coulomb_kernel)]( Particle const &p, Particle const &p1, Utils::Vector3d const &vec) { #ifdef EXCLUSIONS diff --git a/src/core/event.cpp b/src/core/event.cpp index 139ec4fd909..397467bac5d 100644 --- a/src/core/event.cpp +++ b/src/core/event.cpp @@ -114,16 +114,16 @@ void on_integration_start(double time_step) { #ifdef ELECTROSTATICS { auto const &actor = System::get_system().coulomb.solver; - if (!Utils::Mpi::all_compare(comm_cart, static_cast(actor)) or - (actor and !Utils::Mpi::all_compare(comm_cart, (*actor).which()))) + if (not Utils::Mpi::all_compare(comm_cart, static_cast(actor)) or + (actor and not Utils::Mpi::all_compare(comm_cart, (*actor).index()))) runtimeErrorMsg() << "Nodes disagree about Coulomb long-range method"; } #endif #ifdef DIPOLES { auto const &actor = System::get_system().dipoles.solver; - if (!Utils::Mpi::all_compare(comm_cart, static_cast(actor)) or - (actor and !Utils::Mpi::all_compare(comm_cart, (*actor).which()))) + if (not Utils::Mpi::all_compare(comm_cart, static_cast(actor)) or + (actor and not Utils::Mpi::all_compare(comm_cart, (*actor).index()))) runtimeErrorMsg() << "Nodes disagree about dipolar long-range method"; } #endif diff --git a/src/core/forces.cpp b/src/core/forces.cpp index e8a1e66d6e2..62386976271 100644 --- a/src/core/forces.cpp +++ b/src/core/forces.cpp @@ -57,6 +57,7 @@ #include #include +#include std::shared_ptr comfixed = std::make_shared(); @@ -158,8 +159,8 @@ void force_calc(CellStructure &cell_structure, double time_step, double kT) { auto ghost_particles = cell_structure.ghost_particles(); #ifdef ELECTROSTATICS if (espresso_system.coulomb.extension) { - if (auto icc = boost::get>( - espresso_system.coulomb.extension.get_ptr())) { + if (auto icc = std::get_if>( + get_ptr(espresso_system.coulomb.extension))) { (**icc).iteration(cell_structure, particles, ghost_particles); } } @@ -185,14 +186,14 @@ void force_calc(CellStructure &cell_structure, double time_step, double kT) { #endif short_range_loop( - [coulomb_kernel_ptr = coulomb_kernel.get_ptr()]( + [coulomb_kernel_ptr = get_ptr(coulomb_kernel)]( Particle &p1, int bond_id, Utils::Span partners) { return add_bonded_force(p1, bond_id, partners, coulomb_kernel_ptr); }, - [coulomb_kernel_ptr = coulomb_kernel.get_ptr(), - dipoles_kernel_ptr = dipoles_kernel.get_ptr(), - elc_kernel_ptr = elc_kernel.get_ptr()](Particle &p1, Particle &p2, - Distance const &d) { + [coulomb_kernel_ptr = get_ptr(coulomb_kernel), + dipoles_kernel_ptr = get_ptr(dipoles_kernel), + elc_kernel_ptr = get_ptr(elc_kernel)](Particle &p1, Particle &p2, + Distance const &d) { add_non_bonded_pair_force(p1, p2, d.vec21, sqrt(d.dist2), d.dist2, coulomb_kernel_ptr, dipoles_kernel_ptr, elc_kernel_ptr); diff --git a/src/core/magnetostatics/dipoles.cpp b/src/core/magnetostatics/dipoles.cpp index 207b5cbda7c..1a480726215 100644 --- a/src/core/magnetostatics/dipoles.cpp +++ b/src/core/magnetostatics/dipoles.cpp @@ -36,12 +36,11 @@ #include #include - -#include -#include +#include #include #include +#include #include namespace Dipoles { @@ -50,7 +49,7 @@ Solver const &get_dipoles() { return System::get_system().dipoles; } void Solver::sanity_checks() const { if (solver) { - boost::apply_visitor([](auto &actor) { actor->sanity_checks(); }, *solver); + std::visit([](auto &actor) { actor->sanity_checks(); }, *solver); } } @@ -66,8 +65,7 @@ void Solver::on_boxl_change() { void Solver::on_node_grid_change() { if (solver) { - boost::apply_visitor([](auto &actor) { actor->on_node_grid_change(); }, - *solver); + std::visit([](auto &actor) { actor->on_node_grid_change(); }, *solver); } } @@ -101,7 +99,7 @@ void Solver::on_observable_calc() { } } -struct LongRangeForce : public boost::static_visitor { +struct LongRangeForce { ParticleRange const &m_particles; explicit LongRangeForce(ParticleRange const &particles) : m_particles(particles) {} @@ -121,7 +119,7 @@ struct LongRangeForce : public boost::static_visitor { #endif // DP3M void operator()(std::shared_ptr const &actor) const { actor->add_force_corrections(m_particles); - boost::apply_visitor(*this, actor->base_solver); + std::visit(*this, actor->base_solver); } void operator()(std::shared_ptr const &actor) const { actor->add_long_range_forces(m_particles); @@ -143,7 +141,7 @@ struct LongRangeForce : public boost::static_visitor { #endif }; -struct LongRangeEnergy : public boost::static_visitor { +struct LongRangeEnergy { ParticleRange const &m_particles; explicit LongRangeEnergy(ParticleRange const &particles) : m_particles(particles) {} @@ -156,7 +154,7 @@ struct LongRangeEnergy : public boost::static_visitor { #endif // DP3M double operator()(std::shared_ptr const &actor) const { - auto energy = boost::apply_visitor(*this, actor->base_solver); + auto energy = std::visit(*this, actor->base_solver); return energy + actor->energy_correction(m_particles); } double operator()(std::shared_ptr const &actor) const { @@ -182,7 +180,7 @@ struct LongRangeEnergy : public boost::static_visitor { }; #ifdef DIPOLE_FIELD_TRACKING -struct LongRangeField : public boost::static_visitor { +struct LongRangeField { ParticleRange const &m_particles; explicit LongRangeField(ParticleRange const &particles) : m_particles(particles) {} @@ -208,13 +206,13 @@ void Solver::calc_pressure_long_range() const { void Solver::calc_long_range_force(ParticleRange const &particles) const { if (solver) { - boost::apply_visitor(LongRangeForce(particles), *solver); + std::visit(LongRangeForce(particles), *solver); } } double Solver::calc_energy_long_range(ParticleRange const &particles) const { if (solver) { - return boost::apply_visitor(LongRangeEnergy(particles), *solver); + return std::visit(LongRangeEnergy(particles), *solver); } return 0.; } @@ -222,16 +220,10 @@ double Solver::calc_energy_long_range(ParticleRange const &particles) const { #ifdef DIPOLE_FIELD_TRACKING void Solver::calc_long_range_field(ParticleRange const &particles) const { if (solver) { - boost::apply_visitor(LongRangeField(particles), *solver); + std::visit(LongRangeField(particles), *solver); } } #endif -namespace detail { -bool flag_all_reduce(bool flag) { - return boost::mpi::all_reduce(comm_cart, flag, std::logical_or<>()); -} -} // namespace detail - } // namespace Dipoles #endif // DIPOLES diff --git a/src/core/magnetostatics/dipoles.hpp b/src/core/magnetostatics/dipoles.hpp index cbacae5d7df..5b8d76c87e7 100644 --- a/src/core/magnetostatics/dipoles.hpp +++ b/src/core/magnetostatics/dipoles.hpp @@ -35,14 +35,6 @@ #include "magnetostatics/dp3m.hpp" #include "magnetostatics/scafacos.hpp" -#include "ParticleRange.hpp" - -#include - -#include -#include -#include - #include #include #include @@ -50,7 +42,7 @@ #include /** Get the magnetostatics prefactor. */ -struct GetDipolesPrefactor : public boost::static_visitor { +struct GetDipolesPrefactor { template double operator()(std::shared_ptr const &actor) const { return actor->prefactor; @@ -58,7 +50,7 @@ struct GetDipolesPrefactor : public boost::static_visitor { }; /** Run actor sanity checks. */ -struct DipolesSanityChecks : public boost::static_visitor { +struct DipolesSanityChecks { template void operator()(std::shared_ptr const &actor) const { actor->sanity_checks(); } diff --git a/src/core/magnetostatics/dipoles_inline.hpp b/src/core/magnetostatics/dipoles_inline.hpp index 3bfc65cbb14..0c8c3f131ef 100644 --- a/src/core/magnetostatics/dipoles_inline.hpp +++ b/src/core/magnetostatics/dipoles_inline.hpp @@ -33,18 +33,16 @@ #include -#include -#include - #include +#include +#include namespace Dipoles { -struct ShortRangeForceKernel - : public boost::static_visitor< - boost::optional> { +struct ShortRangeForceKernel { - using kernel_type = result_type::value_type; + using kernel_type = Solver::ShortRangeForceKernel; + using result_type = std::optional; #ifdef DIPOLES template @@ -65,16 +63,15 @@ struct ShortRangeForceKernel result_type operator()(std::shared_ptr const &ptr) const { - return boost::apply_visitor(*this, ptr->base_solver); + return std::visit(*this, ptr->base_solver); } #endif // DIPOLES }; -struct ShortRangeEnergyKernel - : public boost::static_visitor< - boost::optional> { +struct ShortRangeEnergyKernel { - using kernel_type = result_type::value_type; + using kernel_type = Solver::ShortRangeEnergyKernel; + using result_type = std::optional; #ifdef DIPOLES template @@ -95,28 +92,28 @@ struct ShortRangeEnergyKernel result_type operator()(std::shared_ptr const &ptr) const { - return boost::apply_visitor(*this, ptr->base_solver); + return std::visit(*this, ptr->base_solver); } #endif // DIPOLES }; -inline boost::optional +inline std::optional Solver::pair_force_kernel() const { #ifdef DIPOLES if (solver) { auto const visitor = Dipoles::ShortRangeForceKernel(); - return boost::apply_visitor(visitor, *solver); + return std::visit(visitor, *solver); } #endif // DIPOLES return {}; } -inline boost::optional +inline std::optional Solver::pair_energy_kernel() const { #ifdef DIPOLES if (solver) { auto const visitor = Dipoles::ShortRangeEnergyKernel(); - return boost::apply_visitor(visitor, *solver); + return std::visit(visitor, *solver); } #endif // DIPOLES return {}; diff --git a/src/core/magnetostatics/dlc.cpp b/src/core/magnetostatics/dlc.cpp index fb6811b936a..ecd9a238724 100644 --- a/src/core/magnetostatics/dlc.cpp +++ b/src/core/magnetostatics/dlc.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include void DipolarLayerCorrection::check_gap(Particle const &p) const { @@ -445,7 +446,7 @@ double DipolarLayerCorrection::tune_far_cut() const { } /** @brief Lock an actor and modify its parameters. */ -struct AdaptSolver : public boost::static_visitor { +struct AdaptSolver { explicit AdaptSolver(DipolarLayerCorrection *this_ptr) : m_actor{this_ptr} {} void operator()(std::shared_ptr const &solver) { @@ -466,7 +467,7 @@ struct AdaptSolver : public boost::static_visitor { void DipolarLayerCorrection::adapt_solver() { auto visitor = AdaptSolver{this}; - boost::apply_visitor(visitor, base_solver); + std::visit(visitor, base_solver); epsilon_correction = (epsilon == P3M_EPSILON_METALLIC) ? 0. : 1. / (2. * epsilon + 1.); } diff --git a/src/core/magnetostatics/dlc.hpp b/src/core/magnetostatics/dlc.hpp index 0efd1f85ae4..b6693fb7928 100644 --- a/src/core/magnetostatics/dlc.hpp +++ b/src/core/magnetostatics/dlc.hpp @@ -33,10 +33,8 @@ #include -#include -#include - #include +#include struct DipolarLayerCorrection; @@ -78,7 +76,7 @@ struct dlc_data { * z-direction. For details see @cite brodka04a. */ struct DipolarLayerCorrection { - using BaseSolver = boost::variant< + using BaseSolver = std::variant< #ifdef DP3M std::shared_ptr, #endif @@ -161,7 +159,7 @@ struct DipolarLayerCorrection { void sanity_checks_node_grid() const; template void visit_base_solver(Visitor &&visitor) const { - boost::apply_visitor(visitor, base_solver); + std::visit(visitor, base_solver); } }; diff --git a/src/core/magnetostatics/dp3m.cpp b/src/core/magnetostatics/dp3m.cpp index 2b714c12461..90538004629 100644 --- a/src/core/magnetostatics/dp3m.cpp +++ b/src/core/magnetostatics/dp3m.cpp @@ -60,11 +60,11 @@ #include #include -#include #include #include #include +#include #include #include #include @@ -596,7 +596,7 @@ class DipolarTuningAlgorithm : public TuningAlgorithm { void on_solver_change() const override { on_dipoles_change(); } - boost::optional + std::optional layer_correction_veto_r_cut(double) const override { return {}; } diff --git a/src/core/magnetostatics/registration.hpp b/src/core/magnetostatics/registration.hpp deleted file mode 100644 index 86bb99a0547..00000000000 --- a/src/core/magnetostatics/registration.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2022 The ESPResSo project - * - * This file is part of ESPResSo. - * - * ESPResSo is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ESPResSo is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef ESPRESSO_SRC_CORE_MAGNETOSTATICS_DIPOLE_REGISTRATION_HPP -#define ESPRESSO_SRC_CORE_MAGNETOSTATICS_DIPOLE_REGISTRATION_HPP - -#include "config/config.hpp" - -#ifdef DIPOLES - -#include "magnetostatics/dipoles.hpp" - -#include "actor/registration.hpp" -#include "actor/visitors.hpp" - -#include "event.hpp" -#include "system/System.hpp" - -#include -#include - -#include -#include -#include - -namespace Dipoles { - -template ::value> * = nullptr> -void add_actor(std::shared_ptr const &actor) { - auto &system = System::get_system(); - if (system.dipoles.solver) { - auto const name = get_actor_name(*system.dipoles.solver); - throw std::runtime_error("A magnetostatics solver is already active (" + - name + ")"); - } - add_actor(system.dipoles.solver, actor, ::on_dipoles_change, - detail::flag_all_reduce); -} - -template ::value> * = nullptr> -void remove_actor(std::shared_ptr const &actor) { - auto &system = System::get_system(); - if (not is_already_stored(actor, system.dipoles.solver)) { - throw std::runtime_error( - "The given magnetostatics solver is not currently active"); - } - remove_actor(system.dipoles.solver, actor, ::on_dipoles_change); -} - -} // namespace Dipoles - -#endif // DIPOLES -#endif diff --git a/src/core/magnetostatics/solver.hpp b/src/core/magnetostatics/solver.hpp index 922f000c37b..facc05b0c95 100644 --- a/src/core/magnetostatics/solver.hpp +++ b/src/core/magnetostatics/solver.hpp @@ -21,6 +21,7 @@ #include "config/config.hpp" +#include "actor/optional.hpp" #include "actor/traits.hpp" #include "Particle.hpp" @@ -28,12 +29,11 @@ #include -#include -#include - #include #include +#include #include +#include #ifdef DIPOLES // forward declarations @@ -53,32 +53,28 @@ struct DipolarScafacos; #endif using MagnetostaticsActor = - boost::variant, + std::variant, #ifdef DIPOLAR_DIRECT_SUM - std::shared_ptr, + std::shared_ptr, #endif #ifdef DIPOLAR_BARNES_HUT - std::shared_ptr, + std::shared_ptr, #endif #ifdef DP3M - std::shared_ptr, + std::shared_ptr, #endif #ifdef SCAFACOS_DIPOLES - std::shared_ptr, + std::shared_ptr, #endif - std::shared_ptr>; + std::shared_ptr>; #endif // DIPOLES namespace Dipoles { -namespace detail { -bool flag_all_reduce(bool flag); -} // namespace detail - struct Solver { #ifdef DIPOLES /// @brief Main electrostatics solver. - boost::optional solver; + std::optional solver; /// @brief Whether to reinitialize the solver on observable calculation. bool reinit_on_observable_calc = false; @@ -108,8 +104,8 @@ struct Solver { std::function; - inline boost::optional pair_force_kernel() const; - inline boost::optional pair_energy_kernel() const; + inline std::optional pair_force_kernel() const; + inline std::optional pair_energy_kernel() const; }; #ifdef DIPOLES diff --git a/src/core/p3m/TuningAlgorithm.hpp b/src/core/p3m/TuningAlgorithm.hpp index 5ea7bae5034..15c2844ad20 100644 --- a/src/core/p3m/TuningAlgorithm.hpp +++ b/src/core/p3m/TuningAlgorithm.hpp @@ -31,11 +31,10 @@ #include -#include - #include #include #include +#include #include #include @@ -136,7 +135,7 @@ class TuningAlgorithm { double r_cut_iL) const = 0; /** @brief Veto real-space cutoffs larger than the layer correction gap. */ - virtual boost::optional + virtual std::optional layer_correction_veto_r_cut(double r_cut) const = 0; /** @brief Write tuned parameters to the P3M parameter struct. */ diff --git a/src/core/pair_criteria/EnergyCriterion.hpp b/src/core/pair_criteria/EnergyCriterion.hpp index aa4b00ac603..af331e4e8ed 100644 --- a/src/core/pair_criteria/EnergyCriterion.hpp +++ b/src/core/pair_criteria/EnergyCriterion.hpp @@ -43,7 +43,7 @@ class EnergyCriterion : public PairCriterion { auto const coulomb_kernel = coulomb.pair_energy_kernel(); auto const energy = calc_non_bonded_pair_energy( - p1, p2, ia_params, d, d.norm(), coulomb_kernel.get_ptr()); + p1, p2, ia_params, d, d.norm(), get_ptr(coulomb_kernel)); return energy >= m_cut_off; } diff --git a/src/core/pressure.cpp b/src/core/pressure.cpp index 5696d2c8be2..da26f84200e 100644 --- a/src/core/pressure.cpp +++ b/src/core/pressure.cpp @@ -77,8 +77,7 @@ std::shared_ptr calculate_pressure() { auto const coulomb_pressure_kernel = coulomb.pair_pressure_kernel(); short_range_loop( - [&obs_pressure, - coulomb_force_kernel_ptr = coulomb_force_kernel.get_ptr()]( + [&obs_pressure, coulomb_force_kernel_ptr = get_ptr(coulomb_force_kernel)]( Particle const &p1, int bond_id, Utils::Span partners) { auto const &iaparams = *bonded_ia_params.at(bond_id); auto const result = calc_bonded_pressure_tensor( @@ -95,8 +94,8 @@ std::shared_ptr calculate_pressure() { } return true; }, - [&obs_pressure, coulomb_force_kernel_ptr = coulomb_force_kernel.get_ptr(), - coulomb_pressure_kernel_ptr = coulomb_pressure_kernel.get_ptr()]( + [&obs_pressure, coulomb_force_kernel_ptr = get_ptr(coulomb_force_kernel), + coulomb_pressure_kernel_ptr = get_ptr(coulomb_pressure_kernel)]( Particle const &p1, Particle const &p2, Distance const &d) { add_non_bonded_pair_virials(p1, p2, d.vec21, sqrt(d.dist2), obs_pressure, coulomb_force_kernel_ptr, diff --git a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp index f0ef4040df5..0164da8f059 100644 --- a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp +++ b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp @@ -30,13 +30,13 @@ namespace utf = boost::unit_test; #include "EspressoSystemStandAlone.hpp" #include "Particle.hpp" #include "accumulators/TimeSeries.hpp" +#include "actor/registration.hpp" #include "bonded_interactions/bonded_interaction_utils.hpp" #include "bonded_interactions/fene.hpp" #include "bonded_interactions/harmonic.hpp" #include "cells.hpp" #include "communication.hpp" #include "electrostatics/p3m.hpp" -#include "electrostatics/registration.hpp" #include "energy.hpp" #include "event.hpp" #include "galilei/Galilei.hpp" @@ -44,6 +44,7 @@ namespace utf = boost::unit_test; #include "nonbonded_interactions/lj.hpp" #include "observables/ParticleVelocities.hpp" #include "particle_node.hpp" +#include "system/System.hpp" #include #include @@ -260,7 +261,8 @@ BOOST_FIXTURE_TEST_CASE(espresso_system_stand_alone, ParticleFactory) { 1e-3}; auto solver = std::make_shared(std::move(p3m), prefactor, 1, false, true); - ::Coulomb::add_actor(solver); + add_actor(comm, System::get_system().coulomb.solver, solver, + ::on_coulomb_change); // measure energies auto const step = 0.02; diff --git a/src/python/espressomd/electrostatics.py b/src/python/espressomd/electrostatics.py index a9b6560dc09..2ed531fc354 100644 --- a/src/python/espressomd/electrostatics.py +++ b/src/python/espressomd/electrostatics.py @@ -22,6 +22,18 @@ from .code_features import has_features +@script_interface_register +class Container(ScriptInterfaceHelper): + _so_name = "Coulomb::Container" + _so_bind_methods = ("clear",) + + def __init__(self, *args, **kwargs): + if not has_features("ELECTROSTATICS"): + raise NotImplementedError("Feature ELECTROSTATICS not compiled in") + + super().__init__(*args, **kwargs) + + class ElectrostaticInteraction(ScriptInterfaceHelper): """ Common interface for electrostatics solvers. diff --git a/src/python/espressomd/magnetostatics.py b/src/python/espressomd/magnetostatics.py index a37e9334e2b..a3ddba7c829 100644 --- a/src/python/espressomd/magnetostatics.py +++ b/src/python/espressomd/magnetostatics.py @@ -22,6 +22,18 @@ from .code_features import has_features +@script_interface_register +class Container(ScriptInterfaceHelper): + _so_name = "Dipoles::Container" + _so_bind_methods = ("clear",) + + def __init__(self, *args, **kwargs): + if not has_features("DIPOLES"): + raise NotImplementedError("Feature DIPOLES not compiled in") + + super().__init__(*args, **kwargs) + + class MagnetostaticInteraction(ScriptInterfaceHelper): """ Common interface for magnetostatics solvers. @@ -325,6 +337,8 @@ class DLC(MagnetostaticInteraction): Parameters ---------- + actor : object derived of :obj:`MagnetostaticInteraction`, required + Base solver. gap_size : :obj:`float` The gap size gives the height :math:`h` of the empty region between the system box and the neighboring artificial images. |es| checks diff --git a/src/python/espressomd/system.py b/src/python/espressomd/system.py index 0d9eed1285a..a8bc698a247 100644 --- a/src/python/espressomd/system.py +++ b/src/python/espressomd/system.py @@ -29,6 +29,8 @@ from . import collision_detection from . import comfixed from . import constraints +from . import electrostatics +from . import magnetostatics from . import galilei from . import interactions from . import integrate @@ -207,6 +209,10 @@ def __init__(self, **kwargs): self.constraints = constraints.Constraints() 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.ekcontainer = electrokinetics.EKContainer() self.ekreactions = electrokinetics.EKReactions() @@ -253,6 +259,12 @@ def __getstate__(self): ] if has_features("COLLISION_DETECTION"): checkpointable_properties.append("collision_detection") + if has_features("ELECTROSTATICS"): + checkpointable_properties.append("electrostatics") + if has_features("DIPOLES"): + checkpointable_properties.append("magnetostatics") + if has_features("ELECTROSTATICS"): + checkpointable_properties.append("electrostatics") checkpointable_properties += ["actors", "thermostat"] if has_features("WALBERLA"): checkpointable_properties += ["ekcontainer", "ekreactions"] diff --git a/src/script_interface/ObjectHandle.hpp b/src/script_interface/ObjectHandle.hpp index 66c585a36e9..4f220e56192 100644 --- a/src/script_interface/ObjectHandle.hpp +++ b/src/script_interface/ObjectHandle.hpp @@ -122,6 +122,7 @@ class ObjectHandle { /** * @brief Set single parameter. + * Can only be called on the head node. */ void set_parameter(const std::string &name, const Variant &value); @@ -134,10 +135,10 @@ class ObjectHandle { public: /** * @brief Call a method on the object. + * Can only be called on the head node. */ Variant call_method(const std::string &name, const VariantMap ¶ms); -protected: /** * @brief Local implementation of @c call_method. * diff --git a/src/script_interface/electrostatics/Actor_impl.hpp b/src/script_interface/electrostatics/Actor_impl.hpp index 3310f633677..87ff49a89fd 100644 --- a/src/script_interface/electrostatics/Actor_impl.hpp +++ b/src/script_interface/electrostatics/Actor_impl.hpp @@ -17,14 +17,18 @@ * along with this program. If not, see . */ +#pragma once + #include "config/config.hpp" #ifdef ELECTROSTATICS #include "Actor.hpp" +#include "core/actor/registration.hpp" #include "core/electrostatics/coulomb.hpp" -#include "core/electrostatics/registration.hpp" +#include "core/event.hpp" +#include "core/system/System.hpp" #include "script_interface/auto_parameters/AutoParameter.hpp" @@ -34,26 +38,26 @@ namespace Coulomb { template Actor::Actor() { add_parameters({ {"prefactor", AutoParameter::read_only, - [this]() { return actor()->prefactor; }}, + [this]() { return m_actor->prefactor; }}, {"check_neutrality", [this](Variant const &value) { auto const flag = get_value(value); - auto &tolerance = actor()->charge_neutrality_tolerance; + auto &tolerance = m_actor->charge_neutrality_tolerance; if (flag) { if (tolerance == -1.) { - tolerance = actor()->charge_neutrality_tolerance_default; + tolerance = m_actor->charge_neutrality_tolerance_default; } } else { tolerance = -1.; } }, [this]() { - auto const tolerance = actor()->charge_neutrality_tolerance; + auto const tolerance = m_actor->charge_neutrality_tolerance; return Variant{tolerance != -1.}; }}, {"charge_neutrality_tolerance", [this](Variant const &value) { - auto &tolerance = actor()->charge_neutrality_tolerance; + auto &tolerance = m_actor->charge_neutrality_tolerance; if (is_none(value)) { tolerance = -1.; } else { @@ -69,7 +73,7 @@ template Actor::Actor() { } }, [this]() { - auto const tolerance = actor()->charge_neutrality_tolerance; + auto const tolerance = m_actor->charge_neutrality_tolerance; if (tolerance == -1.) { return make_variant(none); } @@ -82,11 +86,18 @@ template Variant Actor::do_call_method(std::string const &name, VariantMap const ¶ms) { if (name == "activate") { - context()->parallel_try_catch([&]() { ::Coulomb::add_actor(actor()); }); - return {}; - } - if (name == "deactivate") { - context()->parallel_try_catch([&]() { ::Coulomb::remove_actor(actor()); }); + auto &coulomb = System::get_system().coulomb; + decltype(coulomb.extension) old_extension = std::nullopt; + coulomb.extension.swap(old_extension); + try { + context()->parallel_try_catch([&]() { + add_actor(context()->get_comm(), coulomb.solver, m_actor, + ::on_coulomb_change); + }); + } catch (...) { + coulomb.extension.swap(old_extension); + throw; + } return {}; } return {}; diff --git a/src/script_interface/electrostatics/Container.hpp b/src/script_interface/electrostatics/Container.hpp new file mode 100644 index 00000000000..0505a72c3f3 --- /dev/null +++ b/src/script_interface/electrostatics/Container.hpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2023 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "config/config.hpp" + +#ifdef ELECTROSTATICS + +#include "core/event.hpp" +#include "core/system/System.hpp" + +#include +#include + +#include +#include +#include + +namespace ScriptInterface::Coulomb { + +class Container : public AutoParameters { + ObjectRef m_solver; + ObjectRef m_extension; + + void reset_solver() { + auto &coulomb = System::get_system().coulomb; + m_solver.reset(); + m_extension.reset(); + coulomb.extension = std::nullopt; + coulomb.solver = std::nullopt; + ::on_coulomb_change(); + } + + void reset_extension() { + auto &coulomb = System::get_system().coulomb; + m_extension.reset(); + coulomb.extension = std::nullopt; + ::on_coulomb_change(); + } + +public: + Container() { + add_parameters({ + {"solver", + [this](Variant const &v) { + if (is_none(v)) { + reset_solver(); + } else { + auto actor = get_value(v); + actor->do_call_method("activate", {}); + m_solver = actor; + } + }, + [this]() { return m_solver ? Variant{m_solver} : Variant{None{}}; }}, + {"extension", + [this](Variant const &v) { + if (is_none(v)) { + reset_extension(); + } else { + auto actor = get_value(v); + actor->do_call_method("activate", {}); + m_extension = actor; + } + }, + [this]() { + return m_extension ? Variant{m_extension} : Variant{None{}}; + }}, + }); + } + + void do_construct(VariantMap const ¶ms) override { + if (params.count("solver")) { + do_set_parameter("solver", params.at("solver")); + } + if (params.count("extension")) { + do_set_parameter("extension", params.at("extension")); + } + } + +protected: + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "clear") { + reset_solver(); + return {}; + } + return {}; + } +}; + +} // namespace ScriptInterface::Coulomb + +#endif // ELECTROSTATICS diff --git a/src/script_interface/electrostatics/ICCStar.hpp b/src/script_interface/electrostatics/ICCStar.hpp index 998d9e43238..c09026bb67d 100644 --- a/src/script_interface/electrostatics/ICCStar.hpp +++ b/src/script_interface/electrostatics/ICCStar.hpp @@ -24,9 +24,9 @@ #ifdef ELECTROSTATICS +#include "core/actor/registration.hpp" #include "core/electrostatics/icc.hpp" - -#include "core/electrostatics/registration.hpp" +#include "core/event.hpp" #include @@ -100,12 +100,10 @@ class ICCStar : public AutoParameters { Variant do_call_method(std::string const &name, VariantMap const ¶ms) override { if (name == "activate") { - context()->parallel_try_catch([&]() { ::Coulomb::add_actor(actor()); }); - return {}; - } - if (name == "deactivate") { - context()->parallel_try_catch( - [&]() { ::Coulomb::remove_actor(actor()); }); + context()->parallel_try_catch([&]() { + add_actor(context()->get_comm(), System::get_system().coulomb.extension, + m_actor, ::on_coulomb_change); + }); return {}; } return {}; diff --git a/src/script_interface/electrostatics/initialize.cpp b/src/script_interface/electrostatics/initialize.cpp index be164f64034..de8e21cc710 100644 --- a/src/script_interface/electrostatics/initialize.cpp +++ b/src/script_interface/electrostatics/initialize.cpp @@ -25,6 +25,7 @@ #include "Actor_impl.hpp" +#include "Container.hpp" #include "CoulombMMM1D.hpp" #include "CoulombMMM1DGpu.hpp" #include "CoulombP3M.hpp" @@ -36,7 +37,6 @@ #include "ReactionField.hpp" #include "core/electrostatics/coulomb.hpp" -#include "core/electrostatics/registration.hpp" #include "script_interface/auto_parameters/AutoParameter.hpp" @@ -67,6 +67,7 @@ void initialize(Utils::Factory *om) { om->register_new("Coulomb::CoulombScafacos"); #endif om->register_new("Coulomb::ReactionField"); + om->register_new("Coulomb::Container"); #endif // ELECTROSTATICS } diff --git a/src/script_interface/magnetostatics/Actor_impl.hpp b/src/script_interface/magnetostatics/Actor_impl.hpp index 61028a517ab..925ca00d908 100644 --- a/src/script_interface/magnetostatics/Actor_impl.hpp +++ b/src/script_interface/magnetostatics/Actor_impl.hpp @@ -17,14 +17,18 @@ * along with this program. If not, see . */ +#pragma once + #include "config/config.hpp" #ifdef DIPOLES #include "Actor.hpp" +#include "core/actor/registration.hpp" +#include "core/event.hpp" #include "core/magnetostatics/dipoles.hpp" -#include "core/magnetostatics/registration.hpp" +#include "core/system/System.hpp" #include "script_interface/auto_parameters/AutoParameter.hpp" @@ -35,11 +39,10 @@ template Variant Actor::do_call_method(std::string const &name, VariantMap const ¶ms) { if (name == "activate") { - context()->parallel_try_catch([&]() { ::Dipoles::add_actor(actor()); }); - return {}; - } - if (name == "deactivate") { - context()->parallel_try_catch([&]() { ::Dipoles::remove_actor(actor()); }); + context()->parallel_try_catch([&]() { + add_actor(context()->get_comm(), System::get_system().dipoles.solver, + m_actor, ::on_dipoles_change); + }); return {}; } return {}; diff --git a/src/script_interface/magnetostatics/Container.hpp b/src/script_interface/magnetostatics/Container.hpp new file mode 100644 index 00000000000..3fd5ef55d57 --- /dev/null +++ b/src/script_interface/magnetostatics/Container.hpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "config/config.hpp" + +#ifdef DIPOLES + +#include "core/event.hpp" +#include "core/system/System.hpp" + +#include +#include + +#include +#include +#include + +namespace ScriptInterface::Dipoles { + +class Container : public AutoParameters { + ObjectRef m_solver; + + void reset_solver() { + auto &dipoles = System::get_system().dipoles; + m_solver.reset(); + dipoles.solver = std::nullopt; + ::on_dipoles_change(); + } + +public: + Container() { + add_parameters({ + {"solver", + [this](Variant const &v) { + if (is_none(v)) { + reset_solver(); + } else { + auto actor = get_value(v); + actor->do_call_method("activate", {}); + m_solver = actor; + } + }, + [this]() { return m_solver ? Variant{m_solver} : Variant{None{}}; }}, + }); + } + + void do_construct(VariantMap const ¶ms) override { + if (params.count("solver")) { + do_set_parameter("solver", params.at("solver")); + } + } + +protected: + Variant do_call_method(std::string const &name, + VariantMap const ¶ms) override { + if (name == "clear") { + reset_solver(); + return {}; + } + return {}; + } +}; + +} // namespace ScriptInterface::Dipoles + +#endif // DIPOLES diff --git a/src/script_interface/magnetostatics/initialize.cpp b/src/script_interface/magnetostatics/initialize.cpp index cb3007333d6..972d37879ee 100644 --- a/src/script_interface/magnetostatics/initialize.cpp +++ b/src/script_interface/magnetostatics/initialize.cpp @@ -24,6 +24,7 @@ #include "Actor_impl.hpp" +#include "Container.hpp" #include "DipolarBarnesHutGpu.hpp" #include "DipolarDirectSum.hpp" #include "DipolarDirectSumGpu.hpp" @@ -32,7 +33,6 @@ #include "DipolarScafacos.hpp" #include "core/magnetostatics/dipoles.hpp" -#include "core/magnetostatics/registration.hpp" #include "script_interface/auto_parameters/AutoParameter.hpp" @@ -59,6 +59,7 @@ void initialize(Utils::Factory *om) { om->register_new("Dipoles::DipolarScafacos"); #endif om->register_new("Dipoles::DipolarLayerCorrection"); + om->register_new("Dipoles::Container"); #endif // DIPOLES } diff --git a/src/script_interface/tests/Actors_test.cpp b/src/script_interface/tests/Actors_test.cpp index 78c74fa0f58..33c22e2a693 100644 --- a/src/script_interface/tests/Actors_test.cpp +++ b/src/script_interface/tests/Actors_test.cpp @@ -36,10 +36,8 @@ #include "core/actor/visitors.hpp" #include "core/electrostatics/coulomb.hpp" #include "core/electrostatics/debye_hueckel.hpp" -#include "core/electrostatics/registration.hpp" #include "core/magnetostatics/dipolar_direct_sum.hpp" #include "core/magnetostatics/dipoles.hpp" -#include "core/magnetostatics/registration.hpp" #include "core/communication.hpp" @@ -50,6 +48,7 @@ #include #include +#include #include #include #include @@ -100,17 +99,9 @@ BOOST_AUTO_TEST_CASE(coulomb_actor) { BOOST_CHECK_CLOSE(std::as_const(actor).actor()->prefactor, 2., tol); // check visitors BOOST_CHECK(has_actor_of_type<::DebyeHueckel>( - boost::optional(actor.actor()))); + std::optional(actor.actor()))); BOOST_CHECK(not has_actor_of_type( - boost::optional(actor.actor()))); - BOOST_CHECK(is_already_stored( - actor.actor(), boost::optional(actor.actor()))); - BOOST_CHECK(not is_already_stored( - std::shared_ptr<::DebyeHueckel>{}, - boost::optional(actor.actor()))); - BOOST_CHECK(not is_already_stored( - std::shared_ptr{}, - boost::optional(actor.actor()))); + std::optional(actor.actor()))); } #endif // ELECTROSTATICS diff --git a/testsuite/python/analyze_energy.py b/testsuite/python/analyze_energy.py index be47ace862c..6e82c69c9be 100644 --- a/testsuite/python/analyze_energy.py +++ b/testsuite/python/analyze_energy.py @@ -54,7 +54,7 @@ def setUp(self): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.electrostatics.clear() def test_kinetic(self): p0, p1 = self.system.part.all() @@ -181,7 +181,7 @@ def check_electrostatics(self, p3m_class): r_cut=8.90625, alpha=0.38761105, tune=False) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m # did not verify if this is correct, but looks pretty good (close to # 1/2) diff --git a/testsuite/python/coulomb_cloud_wall.py b/testsuite/python/coulomb_cloud_wall.py index c4713c7a01b..c3d6b809d6f 100644 --- a/testsuite/python/coulomb_cloud_wall.py +++ b/testsuite/python/coulomb_cloud_wall.py @@ -56,7 +56,7 @@ def setUp(self): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.electrostatics.clear() def compare(self, method_name, prefactor, force_tol, energy_tol): # Compare forces and energy now in the system to reference data @@ -75,29 +75,26 @@ def compare(self, method_name, prefactor, force_tol, energy_tol): @utx.skipIfMissingFeatures(["P3M"]) def test_p3m_cpu(self): - self.system.actors.add( - espressomd.electrostatics.P3M( - **self.p3m_params, prefactor=3., tune=False)) + self.system.electrostatics.solver = espressomd.electrostatics.P3M( + **self.p3m_params, prefactor=3., tune=False) self.system.integrator.run(0) self.compare("p3m", prefactor=3., force_tol=2e-3, energy_tol=1e-3) @utx.skipIfMissingGPU() @utx.skipIfMissingFeatures(["P3M"]) def test_p3m_gpu(self): - self.system.actors.add( - espressomd.electrostatics.P3MGPU( - **self.p3m_params, prefactor=2.2, tune=False)) + self.system.electrostatics.solver = espressomd.electrostatics.P3MGPU( + **self.p3m_params, prefactor=2.2, tune=False) self.system.integrator.run(0) self.compare("p3m_gpu", prefactor=2.2, force_tol=2e-3, energy_tol=1e-3) @utx.skipIfMissingFeatures(["SCAFACOS"]) @utx.skipIfMissingScafacosMethod("p2nfft") def test_scafacos_p2nfft(self): - self.system.actors.add( - espressomd.electrostatics.Scafacos( - prefactor=2.8, - method_name="p2nfft", - method_params={"p2nfft_r_cut": 1.001, "tolerance_field": 1E-4})) + self.system.electrostatics.solver = espressomd.electrostatics.Scafacos( + prefactor=2.8, + method_name="p2nfft", + method_params={"p2nfft_r_cut": 1.001, "tolerance_field": 1E-4}) self.system.integrator.run(0) self.compare( "scafacos_p2nfft", diff --git a/testsuite/python/coulomb_cloud_wall_duplicated.py b/testsuite/python/coulomb_cloud_wall_duplicated.py index 9390098cd88..19443e6c37e 100644 --- a/testsuite/python/coulomb_cloud_wall_duplicated.py +++ b/testsuite/python/coulomb_cloud_wall_duplicated.py @@ -53,7 +53,7 @@ def setUp(self): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.electrostatics.clear() def compare(self, method_name, energy=True): # Compare forces and energy now in the system to stored ones @@ -76,15 +76,15 @@ def compare(self, method_name, energy=True): @utx.skipIfMissingFeatures("P3M") def test_p3m(self): - self.system.actors.add( - espressomd.electrostatics.P3M(**self.p3m_params, tune=False)) + p3m = espressomd.electrostatics.P3M(**self.p3m_params, tune=False) + self.system.electrostatics.solver = p3m self.system.integrator.run(0) self.compare("p3m", energy=True) @utx.skipIfMissingGPU() def test_p3m_gpu(self): - self.system.actors.add( - espressomd.electrostatics.P3MGPU(**self.p3m_params, tune=False)) + p3m = espressomd.electrostatics.P3MGPU(**self.p3m_params, tune=False) + self.system.electrostatics.solver = p3m self.system.integrator.run(0) self.compare("p3m_gpu", energy=False) diff --git a/testsuite/python/coulomb_interface.py b/testsuite/python/coulomb_interface.py index fbfda219323..30d11fb8f08 100644 --- a/testsuite/python/coulomb_interface.py +++ b/testsuite/python/coulomb_interface.py @@ -40,34 +40,34 @@ def setUp(self): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.electrostatics.clear() if espressomd.has_features(["ELECTROSTATICS"]): test_dh = tests_common.generate_test_for_actor_class( - system, espressomd.electrostatics.DH, + system.electrostatics, espressomd.electrostatics.DH, dict(prefactor=2., kappa=3., r_cut=1.5, check_neutrality=True, charge_neutrality_tolerance=7e-12)) if espressomd.has_features(["ELECTROSTATICS"]): test_rf = tests_common.generate_test_for_actor_class( - system, espressomd.electrostatics.ReactionField, + system.electrostatics, espressomd.electrostatics.ReactionField, dict(prefactor=2., kappa=3., epsilon1=4., epsilon2=5., r_cut=1.5, check_neutrality=True, charge_neutrality_tolerance=7e-12)) if espressomd.has_features(["P3M"]): test_p3m_cpu_metallic = tests_common.generate_test_for_actor_class( - system, espressomd.electrostatics.P3M, + system.electrostatics, espressomd.electrostatics.P3M, dict(prefactor=2., epsilon=0., mesh_off=[0.6, 0.7, 0.8], r_cut=1.5, cao=2, mesh=[8, 10, 8], alpha=12., accuracy=0.01, tune=False, check_neutrality=True, charge_neutrality_tolerance=7e-12, check_complex_residuals=False)) test_p3m_cpu_non_metallic = tests_common.generate_test_for_actor_class( - system, espressomd.electrostatics.P3M, + system.electrostatics, espressomd.electrostatics.P3M, dict(prefactor=2., epsilon=3., mesh_off=[0.6, 0.7, 0.8], r_cut=1.5, cao=2, mesh=[8, 8, 8], alpha=12., accuracy=0.01, tune=False, check_neutrality=True, charge_neutrality_tolerance=7e-12)) test_p3m_cpu_elc = tests_common.generate_test_for_actor_class( - system, espressomd.electrostatics.ELC, + system.electrostatics, espressomd.electrostatics.ELC, dict(gap_size=2., maxPWerror=1e-3, const_pot=True, pot_diff=-3., delta_mid_top=0.5, delta_mid_bot=0.5, check_neutrality=False, actor=espressomd.electrostatics.P3M( @@ -76,18 +76,18 @@ def tearDown(self): if espressomd.has_features(["P3M", "CUDA"]) and espressomd.gpu_available(): test_p3m_gpu_metallic = tests_common.generate_test_for_actor_class( - system, espressomd.electrostatics.P3MGPU, + system.electrostatics, espressomd.electrostatics.P3MGPU, dict(prefactor=2., epsilon=0., mesh_off=[0.6, 0.7, 0.8], r_cut=1.5, cao=2, mesh=[8, 10, 8], alpha=12., accuracy=0.01, tune=False, check_neutrality=True, charge_neutrality_tolerance=7e-12, check_complex_residuals=False)) test_p3m_gpu_non_metallic = tests_common.generate_test_for_actor_class( - system, espressomd.electrostatics.P3MGPU, + system.electrostatics, espressomd.electrostatics.P3MGPU, dict(prefactor=2., epsilon=3., mesh_off=[0.6, 0.7, 0.8], r_cut=1.5, cao=2, mesh=[8, 8, 8], alpha=12., accuracy=0.01, tune=False, check_neutrality=True, charge_neutrality_tolerance=7e-12)) test_p3m_gpu_elc = tests_common.generate_test_for_actor_class( - system, espressomd.electrostatics.ELC, + system.electrostatics, espressomd.electrostatics.ELC, dict(gap_size=2., maxPWerror=1e-3, const_pot=True, pot_diff=-3., delta_mid_top=0.5, delta_mid_bot=0.5, check_neutrality=False, actor=espressomd.electrostatics.P3MGPU( @@ -102,7 +102,7 @@ def test_mmm1d_cpu(self): check_neutrality=True, charge_neutrality_tolerance=7e-12, timings=5, verbose=False) tests_common.generate_test_for_actor_class( - self.system, espressomd.electrostatics.MMM1D, valid_params)(self) + self.system.electrostatics, espressomd.electrostatics.MMM1D, valid_params)(self) for key in ["prefactor", "maxPWerror", "far_switch_radius", "timings"]: invalid_params = valid_params.copy() @@ -120,7 +120,7 @@ def test_mmm1d_gpu(self): check_neutrality=True, charge_neutrality_tolerance=7e-12, bessel_cutoff=1) tests_common.generate_test_for_actor_class( - self.system, espressomd.electrostatics.MMM1DGPU, valid_params)(self) + self.system.electrostatics, espressomd.electrostatics.MMM1DGPU, valid_params)(self) for key in ["prefactor", "maxPWerror", "far_switch_radius", "bessel_cutoff"]: @@ -135,8 +135,8 @@ def test_charge_neutrality_check(self): self.system.cell_system.set_n_square() actor = espressomd.electrostatics.MMM1D(prefactor=1.0, maxPWerror=1e-3) with self.assertRaisesRegex(RuntimeError, "The system is not charge neutral"): - self.system.actors.add(actor) - self.assertEqual(len(self.system.actors), 0) + self.system.electrostatics.solver = actor + self.assertIsNone(self.system.electrostatics.solver) self.assertFalse(actor.is_tuned) self.assertTrue(actor.check_neutrality) self.assertAlmostEqual(actor.charge_neutrality_tolerance, 2e-12) @@ -158,8 +158,8 @@ def test_mmm1d_cpu_tuning_exceptions(self): actor = espressomd.electrostatics.MMM1D( prefactor=1., maxPWerror=1e-3, far_switch_radius=0.1) with self.assertRaisesRegex(RuntimeError, "MMM1D could not find a reasonable Bessel cutoff"): - self.system.actors.add(actor) - self.assertEqual(len(self.system.actors), 0) + self.system.electrostatics.solver = actor + self.assertIsNone(self.system.electrostatics.solver) self.assertFalse(actor.is_tuned) @utx.skipIfMissingGPU() @@ -170,8 +170,8 @@ def test_mmm1d_gpu_tuning_exceptions(self): actor = espressomd.electrostatics.MMM1DGPU( prefactor=1., maxPWerror=1e-3, far_switch_radius=0.1) with self.assertRaisesRegex(RuntimeError, "No reasonable Bessel cutoff could be determined"): - self.system.actors.add(actor) - self.assertEqual(len(self.system.actors), 0) + self.system.electrostatics.solver = actor + self.assertIsNone(self.system.electrostatics.solver) self.assertFalse(actor.is_tuned) @utx.skipIfMissingFeatures(["P3M"]) @@ -223,7 +223,7 @@ def test_elc_p3m_exceptions(self): # run sanity checks elc = espressomd.electrostatics.ELC( gap_size=4., maxPWerror=1., actor=p3m) - self.system.actors.add(elc) + self.system.electrostatics.solver = elc periodicity_err_msg = r"requires periodicity \(True, True, True\)" with self.assertRaisesRegex(Exception, periodicity_err_msg): self.system.periodicity = [False, False, False] @@ -244,7 +244,7 @@ def test_elc_p3m_exceptions(self): with self.assertRaisesRegex(Exception, "while assigning system parameter 'box_l': ERROR: ELC gap size .+ larger than box length in z-direction"): self.system.box_l = [10., 10., 2.5] self.system.box_l = [10., 10., 10.] - self.system.actors.clear() + self.system.electrostatics.solver = None with self.assertRaisesRegex(RuntimeError, "P3M real-space cutoff too large for ELC w/ dielectric contrast"): self.system.box_l = [10., 10., 5.] elc = espressomd.electrostatics.ELC( @@ -257,15 +257,15 @@ def test_elc_p3m_exceptions(self): pot_diff=-3, check_neutrality=False, ) - self.system.actors.add(elc) - self.assertEqual(len(self.system.actors), 0) + self.system.electrostatics.solver = elc + self.assertIsNone(self.system.electrostatics.solver) self.system.box_l = [10., 10., 10.] self.system.periodicity = [True, True, False] with self.assertRaisesRegex(RuntimeError, periodicity_err_msg): elc = espressomd.electrostatics.ELC( gap_size=2., maxPWerror=1., actor=p3m) - self.system.actors.add(elc) - self.assertEqual(len(self.system.actors), 0) + self.system.electrostatics.solver = elc + self.assertIsNone(self.system.electrostatics.solver) self.system.periodicity = [True, True, True] diff --git a/testsuite/python/coulomb_mixed_periodicity.py b/testsuite/python/coulomb_mixed_periodicity.py index 71834f841fe..b7584c62458 100644 --- a/testsuite/python/coulomb_mixed_periodicity.py +++ b/testsuite/python/coulomb_mixed_periodicity.py @@ -48,7 +48,7 @@ def setUp(self): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.electrostatics.clear() def compare(self, method_name, force_tol, energy_tol): self.system.integrator.run(0) @@ -96,7 +96,7 @@ def test_elc_cpu(self): elc = espressomd.electrostatics.ELC( actor=p3m, maxPWerror=1E-6, gap_size=3) - self.system.actors.add(elc) + self.system.electrostatics.solver = elc self.compare("elc", force_tol=1e-5, energy_tol=1e-4) @utx.skipIfMissingGPU() @@ -110,7 +110,7 @@ def test_elc_gpu(self): elc = espressomd.electrostatics.ELC( actor=p3m, maxPWerror=1E-6, gap_size=3.) - self.system.actors.add(elc) + self.system.electrostatics.solver = elc self.compare("elc", force_tol=1e-5, energy_tol=1e-4) @utx.skipIfMissingFeatures(["SCAFACOS"]) @@ -129,7 +129,7 @@ def test_scafacos_p2nfft(self): "pnfft_N": "96,96,128", "r_cut": 2.4, "pnfft_m": 3}) - self.system.actors.add(scafacos) + self.system.electrostatics.solver = scafacos self.assertTrue(scafacos.call_method("get_near_field_delegation")) self.compare("scafacos_p2nfft", force_tol=1e-4, energy_tol=1.8e-3) diff --git a/testsuite/python/dawaanr-and-dds-gpu.py b/testsuite/python/dawaanr-and-dds-gpu.py index f52d50f09b2..2a344d54146 100644 --- a/testsuite/python/dawaanr-and-dds-gpu.py +++ b/testsuite/python/dawaanr-and-dds-gpu.py @@ -74,7 +74,7 @@ def test(self): dds_cpu = espressomd.magnetostatics.DipolarDirectSumCpu( prefactor=pf_dawaanr) - self.system.actors.add(dds_cpu) + self.system.magnetostatics.solver = dds_cpu self.system.integrator.run(steps=0, recalc_forces=True) dawaanr_f = np.copy(self.system.part.all().f) @@ -82,13 +82,12 @@ def test(self): dawaanr_e = self.system.analysis.energy()["total"] del dds_cpu - for i in range(len(self.system.actors.active_actors)): - self.system.actors.remove(self.system.actors.active_actors[i]) + self.system.magnetostatics.clear() self.system.integrator.run(steps=0, recalc_forces=True) dds_gpu = espressomd.magnetostatics.DipolarDirectSumGpu( prefactor=pf_dds_gpu) - self.system.actors.add(dds_gpu) + self.system.magnetostatics.solver = dds_gpu self.system.integrator.run(steps=0, recalc_forces=True) ddsgpu_f = np.copy(self.system.part.all().f) @@ -117,7 +116,7 @@ def test(self): self.system.integrator.run(steps=0, recalc_forces=True) del dds_gpu - self.system.actors.clear() + self.system.magnetostatics.clear() self.system.part.clear() diff --git a/testsuite/python/dipolar_direct_summation.py b/testsuite/python/dipolar_direct_summation.py index c43bf871cbc..6ae5a397a64 100644 --- a/testsuite/python/dipolar_direct_summation.py +++ b/testsuite/python/dipolar_direct_summation.py @@ -40,14 +40,14 @@ class Test(ut.TestCase): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.magnetostatics.clear() self.system.periodicity = [False, False, False] def dds_gpu_data(self): system = self.system dds_cpu = espressomd.magnetostatics.DipolarDirectSumGpu(prefactor=1.2) - system.actors.add(dds_cpu) + system.magnetostatics.solver = dds_cpu # check MD cell reset has no impact self.system.box_l = self.system.box_l self.system.periodicity = self.system.periodicity @@ -58,7 +58,7 @@ def dds_gpu_data(self): ref_f = np.copy(self.particles.f) ref_t = np.copy(self.particles.torque_lab) - system.actors.clear() + system.magnetostatics.clear() return (ref_e, ref_f, ref_t) @@ -66,7 +66,7 @@ def dds_data(self): system = self.system dds_cpu = espressomd.magnetostatics.DipolarDirectSumCpu(prefactor=1.2) - system.actors.add(dds_cpu) + system.magnetostatics.solver = dds_cpu # check MD cell reset has no impact self.system.box_l = self.system.box_l self.system.periodicity = self.system.periodicity @@ -77,7 +77,7 @@ def dds_data(self): ref_f = np.copy(self.particles.f) ref_t = np.copy(self.particles.torque_lab) - system.actors.clear() + system.magnetostatics.clear() return (ref_e, ref_f, ref_t) @@ -89,14 +89,14 @@ def fcs_data(self): method_name="direct", method_params={"direct_periodic_images": "0,0,0", "direct_cutoff": 0}) - system.actors.add(scafacos_direct) + system.magnetostatics.solver = scafacos_direct system.integrator.run(0, recalc_forces=True) ref_e = system.analysis.energy()["dipolar"] ref_f = np.copy(self.particles.f) ref_t = np.copy(self.particles.torque_lab) - system.actors.clear() + system.magnetostatics.clear() return (ref_e, ref_f, ref_t) @@ -203,7 +203,7 @@ def check_min_image_convention(self, solver, rtol): ref_min_img_energy = 62.5 ref_min_img_forces = 937.5 * np.array([[-1., 0., 0.], [+1., 0., 0.]]) ref_min_img_torques = 62.5 * np.array([[+1., 0., 0.], [-1., 0., 0.]]) - system.actors.add(solver) + system.magnetostatics.solver = solver # check min image against reference data system.periodicity = [False, False, False] @@ -268,10 +268,10 @@ def test_inner_loop_consistency_cpu(self): p2 = system.part.add(pos=[1., 0., 0.], dip=[0., 0., 1.], rotation=[True, True, True]) for n_replicas in [0, 1]: - system.actors.clear() + system.magnetostatics.clear() solver = espressomd.magnetostatics.DipolarDirectSumCpu( prefactor=1., n_replicas=n_replicas) - system.actors.add(solver) + system.magnetostatics.solver = solver # intra-node calculation p1.pos = [system.box_l[0] / 2. - 0.1, 0., 2.] diff --git a/testsuite/python/dipolar_interface.py b/testsuite/python/dipolar_interface.py index 123d91a908f..9334c764f7f 100644 --- a/testsuite/python/dipolar_interface.py +++ b/testsuite/python/dipolar_interface.py @@ -36,41 +36,41 @@ def setUp(self): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.magnetostatics.clear() if espressomd.has_features("DIPOLES"): test_dds_cpu = tests_common.generate_test_for_actor_class( - system, espressomd.magnetostatics.DipolarDirectSumCpu, + system.magnetostatics, espressomd.magnetostatics.DipolarDirectSumCpu, dict(prefactor=3.4)) if espressomd.has_features("DIPOLES"): test_dds_replica_cpu = tests_common.generate_test_for_actor_class( - system, espressomd.magnetostatics.DipolarDirectSumCpu, + system.magnetostatics, espressomd.magnetostatics.DipolarDirectSumCpu, dict(prefactor=3.4, n_replicas=3)) if espressomd.has_features( "DIPOLAR_DIRECT_SUM") and espressomd.gpu_available(): test_dds_gpu = tests_common.generate_test_for_actor_class( - system, espressomd.magnetostatics.DipolarDirectSumGpu, + system.magnetostatics, espressomd.magnetostatics.DipolarDirectSumGpu, dict(prefactor=3.4)) if espressomd.has_features( "DIPOLAR_BARNES_HUT") and espressomd.gpu_available(): test_dds_gpu = tests_common.generate_test_for_actor_class( - system, espressomd.magnetostatics.DipolarBarnesHutGpu, + system.magnetostatics, espressomd.magnetostatics.DipolarBarnesHutGpu, dict(prefactor=3.4, epssq=200.0, itolsq=8.0)) if espressomd.has_features("DP3M"): test_dp3m_metallic = tests_common.generate_test_for_actor_class( - system, espressomd.magnetostatics.DipolarP3M, + system.magnetostatics, espressomd.magnetostatics.DipolarP3M, dict(prefactor=2., epsilon=0., mesh_off=[0.6, 0.7, 0.8], r_cut=1.4, cao=2, mesh=[8, 8, 8], alpha=12., accuracy=0.01, tune=False)) test_dp3m_non_metallic = tests_common.generate_test_for_actor_class( - system, espressomd.magnetostatics.DipolarP3M, + system.magnetostatics, espressomd.magnetostatics.DipolarP3M, dict(prefactor=3., epsilon=3., mesh_off=[0.6, 0.7, 0.8], r_cut=1.6, cao=3, mesh=[8, 8, 8], alpha=14., accuracy=0.01, tune=False)) test_dp3m_dlc = tests_common.generate_test_for_actor_class( - system, espressomd.magnetostatics.DLC, + system.magnetostatics, espressomd.magnetostatics.DLC, dict(gap_size=2., maxPWerror=0.1, far_cut=1., actor=espressomd.magnetostatics.DipolarP3M( cao=2, tune=False, mesh=8, prefactor=2., @@ -80,7 +80,7 @@ def test_dds_mixed_particles(self): # check that non-magnetic particles don't influence the DDS kernels actor = espressomd.magnetostatics.DipolarDirectSumCpu( prefactor=1., n_replicas=2) - self.system.actors.add(actor) + self.system.magnetostatics.solver = actor energy1 = self.system.analysis.energy()["dipolar"] self.system.part.add(pos=(0.4, 0.2, 0.2), dip=(0.0, 0.0, 0.0)) energy2 = self.system.analysis.energy()["dipolar"] @@ -109,32 +109,32 @@ def test_exceptions_non_p3m(self): ddsr = DDSR(prefactor=1., n_replicas=1) with self.assertRaisesRegex(Exception, r"DLC: requires periodicity \(True, True, True\)"): mdlc = MDLC(gap_size=1., maxPWerror=1e-5, actor=ddsr) - self.system.actors.add(mdlc) - self.assertEqual(len(self.system.actors), 0) + self.system.magnetostatics.solver = mdlc + self.assertIsNone(self.system.magnetostatics.solver) self.system.periodicity = [True, True, True] self.system.box_l = [10., 10. + 2e-3, 10.] with self.assertRaisesRegex(Exception, "box size in x direction is different from y direction"): mdlc = MDLC(gap_size=1., maxPWerror=1e-5, actor=ddsr) - self.system.actors.add(mdlc) - self.assertEqual(len(self.system.actors), 0) + self.system.magnetostatics.solver = mdlc + self.assertIsNone(self.system.magnetostatics.solver) if espressomd.has_features( ["DIPOLAR_DIRECT_SUM", "DIPOLE_FIELD_TRACKING"]) and has_gpu: ddsg = DDSG(prefactor=1.) - self.system.actors.add(ddsg) + self.system.magnetostatics.solver = ddsg with self.assertRaisesRegex(Exception, "Dipoles field calculation not implemented by dipolar method DipolarDirectSumGpu"): self.system.part.add(pos=(0.2, 0.2, 0.2), dip=(0.0, 0.0, 1.0)) self.system.analysis.dipole_fields() self.system.part.clear() - self.system.actors.clear() + self.system.magnetostatics.clear() # check it's safe to resize the box, i.e. there are no currently # active sanity check in the core self.system.box_l = [10., 10., 10.] with self.assertRaisesRegex(Exception, "box size in x direction is different from y direction"): ddsr = DDSR(prefactor=1., n_replicas=1) mdlc = MDLC(gap_size=1., maxPWerror=1e-5, actor=ddsr) - self.system.actors.add(mdlc) + self.system.magnetostatics.solver = mdlc self.system.box_l = [9., 10., 10.] - self.system.actors.clear() + self.system.magnetostatics.clear() self.system.box_l = [10., 10., 10.] @utx.skipIfMissingFeatures(["DP3M"]) diff --git a/testsuite/python/drude.py b/testsuite/python/drude.py index 99fef04bc1f..d432cd87ca4 100644 --- a/testsuite/python/drude.py +++ b/testsuite/python/drude.py @@ -159,7 +159,7 @@ def test(self): p3m = espressomd.electrostatics.P3M(prefactor=coulomb_prefactor, accuracy=1e-4, mesh=3 * [18], cao=5) - system.actors.add(p3m) + system.electrostatics.solver = p3m # Drude related Bonds diff --git a/testsuite/python/elc.py b/testsuite/python/elc.py index adfcee58c59..ec35086b962 100644 --- a/testsuite/python/elc.py +++ b/testsuite/python/elc.py @@ -35,7 +35,7 @@ class ElcTest: def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.electrostatics.clear() def test_finite_potential_drop(self): system = self.system @@ -60,7 +60,7 @@ def test_finite_potential_drop(self): pot_diff=POTENTIAL_DIFFERENCE, ) - system.actors.add(elc) + system.electrostatics.solver = elc # Calculated energy U_elc = system.analysis.energy()['coulomb'] diff --git a/testsuite/python/hybrid_decomposition.py b/testsuite/python/hybrid_decomposition.py index f11317662da..fa6828ebd97 100644 --- a/testsuite/python/hybrid_decomposition.py +++ b/testsuite/python/hybrid_decomposition.py @@ -36,8 +36,12 @@ def setUp(self): self.system.time_step = 1e-3 def tearDown(self): + self.system.cell_system.set_regular_decomposition() self.system.part.clear() - self.system.actors.clear() + if espressomd.has_features(["ELECTROSTATICS"]): + self.system.electrostatics.clear() + if espressomd.has_features(["DIPOLES"]): + self.system.magnetostatics.clear() def check_resort(self): n_part = 2352 @@ -254,7 +258,7 @@ def test_against_regular_p3m(self): coulomb_interaction = espressomd.electrostatics.P3M( **self.valid_p3m_parameters() ) - self.system.actors.add(coulomb_interaction) + self.system.electrostatics.solver = coulomb_interaction self.run_comparison(self.system.cell_system.set_regular_decomposition) @@ -266,10 +270,10 @@ def test_against_regular_dp3m(self): self.prepare_hybrid_setup(n_part_small=0, n_part_large=0) self.add_particles(dip=True) - dipolar_interaction = espressomd.magnetostatics.DipolarP3M( + actor = espressomd.magnetostatics.DipolarP3M( **self.valid_dp3m_parameters() ) - self.system.actors.add(dipolar_interaction) + self.system.magnetostatics.solver = actor self.run_comparison(self.system.cell_system.set_regular_decomposition) @@ -284,7 +288,7 @@ def test_mpi_exception_p3m(self): actor = espressomd.electrostatics.P3M( **self.valid_p3m_parameters() ) - self.system.actors.add(actor) + self.system.electrostatics.solver = actor with self.assertRaises(Exception): self.system.cell_system.set_hybrid_decomposition( @@ -301,7 +305,7 @@ def test_mpi_exception_dp3m(self): actor = espressomd.magnetostatics.DipolarP3M( **self.valid_dp3m_parameters() ) - self.system.actors.add(actor) + self.system.magnetostatics.solver = actor with self.assertRaises(Exception): self.system.cell_system.set_hybrid_decomposition( diff --git a/testsuite/python/icc.py b/testsuite/python/icc.py index 526cd8be37f..7dda37b2d89 100644 --- a/testsuite/python/icc.py +++ b/testsuite/python/icc.py @@ -35,7 +35,7 @@ class TestICC(ut.TestCase): system.time_step = 0.01 def tearDown(self): - self.system.actors.clear() + self.system.electrostatics.clear() self.system.part.clear() def add_icc_particles(self, side_num_particles, @@ -111,8 +111,8 @@ def test_dipole_system(self): prefactor=1., mesh=32, cao=7, accuracy=1e-5) p3m.charge_neutrality_tolerance = 1e-11 - self.system.actors.add(p3m) - self.system.actors.add(icc) + self.system.electrostatics.solver = p3m + self.system.electrostatics.extension = icc self.system.integrator.run(0) charge_lower = sum(part_slice_lower.q) diff --git a/testsuite/python/icc_interface.py b/testsuite/python/icc_interface.py index ef1c44ae72d..e447861077c 100644 --- a/testsuite/python/icc_interface.py +++ b/testsuite/python/icc_interface.py @@ -33,7 +33,7 @@ class Test(ut.TestCase): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.electrostatics.clear() self.system.thermostat.turn_off() self.system.integrator.set_vv() @@ -113,8 +113,8 @@ def test_exceptions_small_r_cut(self): p3m = espressomd.electrostatics.P3M( prefactor=1., mesh=32, cao=7, accuracy=1e-5, r_cut=0.01875, alpha=0.005, tune=False, check_neutrality=False) - self.system.actors.add(p3m) - self.system.actors.add(icc) + self.system.electrostatics.solver = p3m + self.system.electrostatics.extension = icc with self.assertRaisesRegex(Exception, "ICC found zero electric field on a charge"): self.system.integrator.run(0) @@ -126,16 +126,16 @@ def test_exceptions_large_r_cut(self): p3m = espressomd.electrostatics.P3M( check_complex_residuals=False, **self.valid_p3m_parameters()) - self.system.actors.add(p3m) - self.system.actors.add(icc) + self.system.electrostatics.solver = p3m + self.system.electrostatics.extension = icc with self.assertRaisesRegex(Exception, f"Particle with id {p.id} has a charge .+ that is too large for the ICC algorithm"): p.q = 1e8 self.system.integrator.run(0) - self.system.actors.remove(icc) + self.system.electrostatics.extension = None self.system.part.clear() icc, (_, p) = self.setup_icc_particles_and_solver(max_iterations=1) - self.system.actors.add(icc) + self.system.electrostatics.extension = icc with self.assertRaisesRegex(Exception, "ICC failed to converge in the given number of maximal steps"): p.q = 1. self.system.integrator.run(0) @@ -149,19 +149,19 @@ def test_exceptions_gpu(self): icc, _ = self.setup_icc_particles_and_solver() p3m = espressomd.electrostatics.P3MGPU(**self.valid_p3m_parameters()) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m with self.assertRaisesRegex(RuntimeError, "ICC does not work with P3MGPU"): - self.system.actors.add(icc) - self.assertEqual(len(self.system.actors), 1) + self.system.electrostatics.extension = icc + self.assertIsNone(self.system.electrostatics.extension) self.system.integrator.run(0) - self.system.actors.clear() + self.system.electrostatics.clear() elc = espressomd.electrostatics.ELC( actor=p3m, gap_size=5., maxPWerror=1e-3) - self.system.actors.add(elc) + self.system.electrostatics.solver = elc with self.assertRaisesRegex(RuntimeError, "ICC does not work with P3MGPU"): - self.system.actors.add(icc) - self.assertEqual(len(self.system.actors), 1) + self.system.electrostatics.extension = icc + self.assertIsNone(self.system.electrostatics.extension) self.system.integrator.run(0) @utx.skipIfMissingFeatures(["P3M"]) @@ -172,18 +172,18 @@ def test_exceptions_elc(self): actor=p3m, gap_size=5., maxPWerror=1e-3, pot_diff=-3., delta_mid_top=-1., delta_mid_bot=-1., const_pot=True) - self.system.actors.add(elc) + self.system.electrostatics.solver = elc with self.assertRaisesRegex(RuntimeError, "ICC conflicts with ELC dielectric contrast"): - self.system.actors.add(icc) - self.assertEqual(len(self.system.actors), 1) + self.system.electrostatics.extension = icc + self.assertIsNone(self.system.electrostatics.extension) self.system.integrator.run(0) - self.system.actors.clear() + self.system.electrostatics.clear() # valid ELC actor should pass sanity checks elc = espressomd.electrostatics.ELC( actor=p3m, gap_size=5., maxPWerror=1e-3) - self.system.actors.add(elc) - self.system.actors.add(icc) + self.system.electrostatics.solver = elc + self.system.electrostatics.extension = icc self.system.part.clear() self.system.integrator.run(0) @@ -192,10 +192,10 @@ def test_exceptions_dh(self): solver = espressomd.electrostatics.DH( prefactor=2., kappa=0.8, r_cut=1.2) - self.system.actors.add(solver) + self.system.electrostatics.solver = solver with self.assertRaisesRegex(RuntimeError, "ICC does not work with DebyeHueckel"): - self.system.actors.add(icc) - self.assertEqual(len(self.system.actors), 1) + self.system.electrostatics.extension = icc + self.assertIsNone(self.system.electrostatics.extension) self.system.integrator.run(0) def test_exceptions_rf(self): @@ -203,10 +203,10 @@ def test_exceptions_rf(self): solver = espressomd.electrostatics.ReactionField( prefactor=1., kappa=2., epsilon1=1., epsilon2=2., r_cut=2.) - self.system.actors.add(solver) + self.system.electrostatics.solver = solver with self.assertRaisesRegex(RuntimeError, "ICC does not work with ReactionField"): - self.system.actors.add(icc) - self.assertEqual(len(self.system.actors), 1) + self.system.electrostatics.extension = icc + self.assertIsNone(self.system.electrostatics.extension) self.system.integrator.run(0) @utx.skipIfMissingFeatures(["NPT", "P3M"]) @@ -214,12 +214,12 @@ def test_exceptions_npt(self): icc, _ = self.setup_icc_particles_and_solver() p3m = espressomd.electrostatics.P3M(**self.valid_p3m_parameters()) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m self.system.thermostat.set_npt(kT=1., gamma0=2., gammav=0.004, seed=42) self.system.integrator.set_isotropic_npt(ext_pressure=2., piston=0.001) with self.assertRaisesRegex(RuntimeError, "ICC does not work in the NPT ensemble"): - self.system.actors.add(icc) - self.assertEqual(len(self.system.actors), 1) + self.system.electrostatics.extension = icc + self.assertIsNone(self.system.electrostatics.extension) self.system.integrator.run(0) diff --git a/testsuite/python/integrator_npt.py b/testsuite/python/integrator_npt.py index 8684db9966f..5c86768694f 100644 --- a/testsuite/python/integrator_npt.py +++ b/testsuite/python/integrator_npt.py @@ -37,7 +37,10 @@ def setUp(self): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + if espressomd.has_features(["ELECTROSTATICS"]): + self.system.electrostatics.clear() + if espressomd.has_features(["DIPOLES"]): + self.system.magnetostatics.clear() self.system.thermostat.turn_off() self.system.integrator.set_vv() @@ -120,7 +123,7 @@ def test_00_integrator_recovery(self): np.copy(system.part.all().pos), positions_start + np.array([[-1.2e-3, 0, 0], [1.2e-3, 0, 0]])) - def run_with_p3m(self, p3m, method): + def run_with_p3m(self, container, p3m, method): system = self.system npt_kwargs = {"ext_pressure": 0.001, "piston": 0.001} npt_kwargs_rectangular = { @@ -141,7 +144,7 @@ def run_with_p3m(self, p3m, method): system.integrator.run(100) system.integrator.set_vv() # combine NpT with a P3M algorithm - system.actors.add(p3m) + container.solver = p3m system.integrator.run(10) system.integrator.set_isotropic_npt(**npt_kwargs) system.thermostat.set_npt(kT=1.0, gamma0=2, gammav=0.04, seed=42) @@ -155,9 +158,9 @@ def run_with_p3m(self, p3m, method): self.assertIsInstance( system.integrator.integrator, espressomd.integrate.VelocityVerlet) - system.actors.remove(p3m) + container.solver = None system.integrator.set_isotropic_npt(**npt_kwargs_rectangular) - system.actors.add(p3m) + container.solver = p3m with self.assertRaisesRegex(Exception, err_msg): system.integrator.run(0, recalc_forces=True) @@ -167,7 +170,7 @@ def test_npt_dp3m_cpu(self): dp3m = espressomd.magnetostatics.DipolarP3M( prefactor=1.0, accuracy=1e-2, mesh=3 * [36], cao=7, r_cut=1.0, alpha=2.995, tune=False) - self.run_with_p3m(dp3m, "magnetostatics") + self.run_with_p3m(self.system.magnetostatics, dp3m, "magnetostatics") @utx.skipIfMissingFeatures(["P3M"]) def test_npt_p3m_cpu(self): @@ -175,7 +178,7 @@ def test_npt_p3m_cpu(self): p3m = espressomd.electrostatics.P3M( prefactor=1.0, accuracy=1e-2, mesh=3 * [8], cao=3, r_cut=0.36, alpha=5.35, tune=False) - self.run_with_p3m(p3m, "electrostatics") + self.run_with_p3m(self.system.electrostatics, p3m, "electrostatics") @utx.skipIfMissingGPU() @utx.skipIfMissingFeatures(["P3M"]) @@ -184,7 +187,7 @@ def test_npt_p3m_gpu(self): p3m = espressomd.electrostatics.P3MGPU( prefactor=1.0, accuracy=1e-2, mesh=3 * [8], cao=3, r_cut=0.36, alpha=5.35, tune=False) - self.run_with_p3m(p3m, "electrostatics") + self.run_with_p3m(self.system.electrostatics, p3m, "electrostatics") if __name__ == "__main__": diff --git a/testsuite/python/long_range_actors.py b/testsuite/python/long_range_actors.py index fa195ff5e66..e06b4b74a5a 100644 --- a/testsuite/python/long_range_actors.py +++ b/testsuite/python/long_range_actors.py @@ -41,7 +41,10 @@ def setUp(self): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + if espressomd.has_features(["ELECTROSTATICS"]): + self.system.electrostatics.clear() + if espressomd.has_features(["DIPOLES"]): + self.system.magnetostatics.clear() self.system.thermostat.turn_off() self.system.integrator.set_vv() self.system.periodicity = [True, True, True] @@ -90,36 +93,21 @@ def test_electrostatics_registration(self): import espressomd.highlander icc, _ = self.setup_icc_particles_and_solver() p3m = espressomd.electrostatics.P3M(**self.valid_p3m_parameters()) - dh = espressomd.electrostatics.DH( - prefactor=2., kappa=0.8, r_cut=1.2) + p3m_new = espressomd.electrostatics.P3M(**self.valid_p3m_parameters()) self.assertIsNone(p3m.call_method("unknown")) - - with self.assertRaisesRegex(RuntimeError, "The given electrostatics solver is not currently active"): - p3m._deactivate() - with self.assertRaisesRegex(RuntimeError, "The given electrostatics extension is not currently active"): - icc._deactivate() + self.assertIsNone(icc.call_method("unknown")) with self.assertRaisesRegex(RuntimeError, "An electrostatics solver is needed by ICC"): - self.system.actors.add(icc) - - self.system.actors.add(p3m) - self.system.actors.add(icc) - - with self.assertRaises(espressomd.highlander.ThereCanOnlyBeOne): - self.system.actors.add(p3m) - with self.assertRaisesRegex(RuntimeError, r"An electrostatics solver is already active \(CoulombP3M\)"): - self.system.actors.add(dh) - - with self.assertRaises(espressomd.highlander.ThereCanOnlyBeOne): - self.system.actors.add(icc) - with self.assertRaisesRegex(RuntimeError, r"An electrostatics extension is already active \(ICCStar\)"): - icc_new, _ = self.setup_icc_particles_and_solver() - self.system.actors.add(icc_new) + self.system.electrostatics.extension = icc - with self.assertRaisesRegex(Exception, r"An electrostatics solver is needed by ICC"): - self.system.actors.remove(p3m) - self.system.integrator.run(0) + self.system.electrostatics.solver = p3m + self.system.electrostatics.extension = icc + self.system.electrostatics.solver = p3m_new + self.assertIsNotNone(self.system.electrostatics.extension) + self.system.electrostatics.solver = None + self.assertIsNone(self.system.electrostatics.extension) + self.system.integrator.run(0) @utx.skipIfMissingFeatures(["DP3M"]) def test_magnetostatics_registration(self): @@ -128,18 +116,7 @@ def test_magnetostatics_registration(self): **self.valid_dp3m_parameters()) self.assertIsNone(dp3m.call_method("unknown")) - - with self.assertRaisesRegex(RuntimeError, "The given magnetostatics solver is not currently active"): - dp3m._deactivate() - - self.system.actors.add(dp3m) - - with self.assertRaises(espressomd.highlander.ThereCanOnlyBeOne): - self.system.actors.add(dp3m) - with self.assertRaisesRegex(RuntimeError, r"A magnetostatics solver is already active \(DipolarP3M\)"): - dp3m_new = espressomd.magnetostatics.DipolarP3M( - **self.valid_dp3m_parameters()) - self.system.actors.add(dp3m_new) + self.system.magnetostatics.solver = dp3m def check_obs_stats(self, key): # check observable statistics have the correct shape @@ -159,7 +136,7 @@ def check_obs_stats(self, key): def test_dp3m_cpu_pressure(self): dp3m = espressomd.magnetostatics.DipolarP3M( **self.valid_dp3m_parameters()) - self.system.actors.add(dp3m) + self.system.magnetostatics.solver = dp3m pressure_tensor, pressure_scalar = self.check_obs_stats("dipolar") # DP3M doesn't contribute to the pressure np.testing.assert_allclose(pressure_tensor["dipolar"], 0., atol=1e-12) @@ -169,7 +146,7 @@ def test_dp3m_cpu_pressure(self): def test_p3m_cpu_pressure(self): self.add_charged_particles() p3m = espressomd.electrostatics.P3M(**self.valid_p3m_parameters()) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m self.check_obs_stats("coulomb") @utx.skipIfMissingGPU() @@ -177,7 +154,7 @@ def test_p3m_cpu_pressure(self): def test_p3m_gpu_pressure(self): self.add_charged_particles() p3m = espressomd.electrostatics.P3MGPU(**self.valid_p3m_parameters()) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m self.check_obs_stats("coulomb") @utx.skipIfMissingFeatures(["P3M"]) @@ -186,7 +163,7 @@ def test_elc_cpu_pressure(self): p3m = espressomd.electrostatics.P3M(**self.valid_p3m_parameters()) elc = espressomd.electrostatics.ELC( actor=p3m, gap_size=2., maxPWerror=1e-3, check_neutrality=False) - self.system.actors.add(elc) + self.system.electrostatics.solver = elc pressure_tensor, pressure_scalar = self.check_obs_stats("coulomb") # ELC doesn't contribute to the pressure pressure_tensor_far_field = pressure_tensor[("coulomb", 1)] @@ -203,7 +180,7 @@ def test_rf_pressure(self): self.add_charged_particles() actor = espressomd.electrostatics.ReactionField( prefactor=1., kappa=2., epsilon1=1., epsilon2=2., r_cut=2.) - self.system.actors.add(actor) + self.system.electrostatics.solver = actor pressure_tensor, pressure_scalar = self.check_obs_stats("coulomb") # actor doesn't contribute to the pressure in the far field pressure_tensor_far_field = pressure_tensor[("coulomb", 1)] @@ -215,7 +192,7 @@ def test_rf_pressure(self): def test_dh_pressure(self): self.add_charged_particles() actor = espressomd.electrostatics.DH(prefactor=1., kappa=1., r_cut=1.) - self.system.actors.add(actor) + self.system.electrostatics.solver = actor pressure_tensor, pressure_scalar = self.check_obs_stats("coulomb") # actor doesn't contribute to the pressure in the far field pressure_tensor_far_field = pressure_tensor[("coulomb", 1)] @@ -228,42 +205,46 @@ def test_dh_pressure(self): def test_mdds_cpu_no_magnetic_particles(self): self.system.part.add(pos=2 * [[1., 1., 1.]], dip=2 * [[0., 0., 0.]]) mdds = espressomd.magnetostatics.DipolarDirectSumCpu(prefactor=2.) - self.system.actors.add(mdds) + self.system.magnetostatics.solver = mdds energy = self.system.analysis.energy() self.assertAlmostEqual(energy["dipolar"], 0., delta=1e-12) - def check_p3m_pre_conditions(self, class_p3m): + def check_p3m_pre_conditions(self, container, class_p3m): params = {"prefactor": 1., "accuracy": 1., "r_cut": 1., "alpha": 1.} # P3M pre-condition: cao / mesh[i] < 1 with self.assertRaisesRegex(RuntimeError, "k-space cutoff .+ is larger than half of box dimension"): - self.system.actors.add( - class_p3m(cao=6, mesh=6, tune=False, **params)) + container.solver = class_p3m(cao=6, mesh=6, tune=False, **params) # P3M pre-condition: cao / mesh[i] < 2 / n_nodes[i] with self.assertRaisesRegex(RuntimeError, "k-space cutoff .+ is larger than local box dimension"): self.system.cell_system.node_grid = [self.n_nodes, 1, 1] - self.system.actors.add( - class_p3m(cao=7, mesh=8, tune=False, **params)) + container.solver = class_p3m(cao=7, mesh=8, tune=False, **params) @utx.skipIfMissingFeatures(["P3M"]) @ut.skipIf(n_nodes < 3, "only runs for 3+ MPI ranks") def test_p3m_cpu_pre_condition_exceptions(self): self.add_charged_particles() - self.check_p3m_pre_conditions(espressomd.electrostatics.P3M) + self.check_p3m_pre_conditions( + self.system.electrostatics, + espressomd.electrostatics.P3M) @utx.skipIfMissingGPU() @utx.skipIfMissingFeatures(["P3M"]) @ut.skipIf(n_nodes < 3, "only runs for 3+ MPI ranks") def test_p3m_gpu_pre_condition_exceptions(self): self.add_charged_particles() - self.check_p3m_pre_conditions(espressomd.electrostatics.P3MGPU) + self.check_p3m_pre_conditions( + self.system.electrostatics, + espressomd.electrostatics.P3MGPU) @utx.skipIfMissingFeatures(["DP3M"]) @ut.skipIf(n_nodes < 3, "only runs for 3+ MPI ranks") def test_dp3m_cpu_pre_condition_exceptions(self): self.add_magnetic_particles() - self.check_p3m_pre_conditions(espressomd.magnetostatics.DipolarP3M) + self.check_p3m_pre_conditions( + self.system.magnetostatics, + espressomd.magnetostatics.DipolarP3M) @utx.skipIfMissingFeatures(["P3M"]) def test_p3m_cpu_tuning_accuracy_exception(self): @@ -271,18 +252,18 @@ def test_p3m_cpu_tuning_accuracy_exception(self): p3m = espressomd.electrostatics.P3M(prefactor=1., mesh=8, alpha=60., r_cut=1.25625, accuracy=1e-5) with self.assertRaisesRegex(RuntimeError, "failed to reach requested accuracy"): - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m self.assertFalse(p3m.is_tuned) - self.assertEqual(len(self.system.actors), 0) + self.assertIsNone(self.system.electrostatics.solver) def check_p3m_tuning_errors(self, p3m): # set an incompatible combination of thermostat and integrators self.system.integrator.set_isotropic_npt(ext_pressure=2., piston=0.01) self.system.thermostat.set_brownian(kT=1.0, gamma=1.0, seed=42) with self.assertRaisesRegex(RuntimeError, r"tuning failed: an exception was thrown while benchmarking the integration loop"): - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m self.assertFalse(p3m.is_tuned) - self.assertEqual(len(self.system.actors), 0) + self.assertIsNone(self.system.electrostatics.solver) @utx.skipIfMissingFeatures(["P3M", "NPT"]) def test_p3m_cpu_tuning_errors(self): @@ -303,8 +284,8 @@ def check_mmm1d_exceptions(self, mmm1d_class): # check cell system exceptions with self.assertRaisesRegex(Exception, "MMM1D requires the N-square cellsystem"): self.system.cell_system.set_regular_decomposition() - self.system.actors.add(mmm1d) - self.assertEqual(len(self.system.actors), 0) + self.system.electrostatics.solver = mmm1d + self.assertIsNone(self.system.electrostatics.solver) self.assertFalse(mmm1d.is_tuned) self.system.cell_system.set_n_square() @@ -315,8 +296,8 @@ def check_mmm1d_exceptions(self, mmm1d_class): self.system.periodicity = periodicity with self.assertRaisesRegex(Exception, r"MMM1D requires periodicity \(False, False, True\)"): mmm1d = mmm1d_class(prefactor=1., maxPWerror=1e-2) - self.system.actors.add(mmm1d) - self.assertEqual(len(self.system.actors), 0) + self.system.electrostatics.solver = mmm1d + self.assertIsNone(self.system.electrostatics.solver) self.assertFalse(mmm1d.is_tuned) self.system.periodicity = (False, False, True) @@ -353,7 +334,7 @@ def test_elc_tuning_exceptions(self): ) self.system.part.add(pos=[0., 0., 0.], q=1.) with self.assertRaisesRegex(RuntimeError, "ELC does not currently support non-neutral systems"): - self.system.actors.add(elc) + self.system.electrostatics.solver = elc if __name__ == "__main__": diff --git a/testsuite/python/p3m_electrostatic_pressure.py b/testsuite/python/p3m_electrostatic_pressure.py index d42ce30c6bf..465da0af8e4 100644 --- a/testsuite/python/p3m_electrostatic_pressure.py +++ b/testsuite/python/p3m_electrostatic_pressure.py @@ -117,13 +117,13 @@ def setUp(self): def tearDown(self): self.system.part.clear() - self.system.actors.clear() + self.system.electrostatics.clear() self.system.thermostat.turn_off() self.system.integrator.set_vv() def check_p3m_pressure(self, class_p3m): p3m = class_p3m(prefactor=2., accuracy=1e-3, mesh=16, cao=6, r_cut=7.5) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m skin = self.system.cell_system.tune_skin( min_skin=0.0, max_skin=2.5, tol=0.05, int_steps=100) print(f"Tuned skin: {skin}") diff --git a/testsuite/python/p3m_fft.py b/testsuite/python/p3m_fft.py index 21f63440e16..88e8af795ce 100644 --- a/testsuite/python/p3m_fft.py +++ b/testsuite/python/p3m_fft.py @@ -56,6 +56,10 @@ def setUp(self): def tearDown(self): self.system.actors.clear() self.system.part.clear() + if espressomd.has_features(["ELECTROSTATICS"]): + self.system.electrostatics.clear() + if espressomd.has_features(["DIPOLES"]): + self.system.magnetostatics.clear() def minimize(self): self.system.non_bonded_inter[0, 0].lennard_jones.set_params( @@ -93,10 +97,10 @@ def test_fft_plans(self): self.system.cell_system.node_grid = node_grid solver = espressomd.electrostatics.P3M( prefactor=2, accuracy=1e-6, tune=False, **p3m_params) - self.system.actors.add(solver) + self.system.electrostatics.solver = solver ref_energy = -75.871906 p3m_energy = self.system.analysis.energy()['coulomb'] - self.system.actors.clear() + self.system.electrostatics.clear() np.testing.assert_allclose(p3m_energy, ref_energy, rtol=1e-4) @utx.skipIfMissingFeatures("P3M") @@ -109,7 +113,7 @@ def test_unsorted_node_grid_exception_p3m(self): self.system.cell_system.node_grid = unsorted_node_grid solver = espressomd.electrostatics.P3M(prefactor=2, accuracy=1e-2) with self.assertRaisesRegex(Exception, 'node grid must be sorted, largest first'): - self.system.actors.add(solver) + self.system.electrostatics.solver = solver @utx.skipIfMissingFeatures("DP3M") @ut.skipIf(n_nodes < 2 or n_nodes >= 8, "only runs for 2 <= n_nodes <= 7") @@ -122,7 +126,7 @@ def test_unsorted_node_grid_exception_dp3m(self): solver = espressomd.magnetostatics.DipolarP3M( prefactor=2, accuracy=1e-2) with self.assertRaisesRegex(Exception, 'node grid must be sorted, largest first'): - self.system.actors.add(solver) + self.system.magnetostatics.solver = solver if __name__ == "__main__": diff --git a/testsuite/python/p3m_madelung.py b/testsuite/python/p3m_madelung.py index de5bf298088..01cd6ec89c0 100644 --- a/testsuite/python/p3m_madelung.py +++ b/testsuite/python/p3m_madelung.py @@ -40,6 +40,7 @@ class Test(ut.TestCase): def tearDown(self): self.system.part.clear() self.system.actors.clear() + self.system.electrostatics.clear() def get_normalized_obs_per_ion(self, pressure=True): energy = self.system.analysis.energy()["coulomb"] @@ -210,12 +211,11 @@ def check(): rtol=1e-6) p3m = espressomd.electrostatics.P3M(**p3m_params) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m check() if espressomd.has_features("CUDA") and espressomd.gpu_available(): - self.system.actors.clear() p3m = espressomd.electrostatics.P3MGPU(**p3m_params) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m check() @utx.skipIfMissingFeatures(["P3M"]) @@ -244,20 +244,17 @@ def check(pressure=True): rtol=5e-7) p3m = espressomd.electrostatics.P3M(**p3m_params) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m check(pressure=True) - self.system.actors.remove(p3m) elc = espressomd.electrostatics.ELC(actor=p3m, **elc_params) - self.system.actors.add(elc) + self.system.electrostatics.solver = elc check(pressure=False) if espressomd.has_features("CUDA") and espressomd.gpu_available(): - self.system.actors.clear() p3m = espressomd.electrostatics.P3MGPU(**p3m_params) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m check(pressure=True) - self.system.actors.remove(p3m) elc = espressomd.electrostatics.ELC(actor=p3m, **elc_params) - self.system.actors.add(elc) + self.system.electrostatics.solver = elc check(pressure=False) @utx.skipIfMissingFeatures(["P3M"]) @@ -282,14 +279,13 @@ def check(): rtol=5e-6) p3m = espressomd.electrostatics.P3M(**p3m_params) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m check() if espressomd.has_features("CUDA") and espressomd.gpu_available(): - self.system.actors.clear() p3m_params = dict(prefactor=1., accuracy=3e-7, mesh=42, r_cut=5.5, cao=7, alpha=0.709017, tune=False) p3m = espressomd.electrostatics.P3MGPU(**p3m_params) - self.system.actors.add(p3m) + self.system.electrostatics.solver = p3m check() diff --git a/testsuite/python/p3m_tuning_exceptions.py b/testsuite/python/p3m_tuning_exceptions.py index a4d9b62f471..677ccfb59b1 100644 --- a/testsuite/python/p3m_tuning_exceptions.py +++ b/testsuite/python/p3m_tuning_exceptions.py @@ -53,7 +53,10 @@ def setUp(self): self.system.periodicity = [True, True, True] def tearDown(self): - self.system.actors.clear() + if espressomd.has_features(["ELECTROSTATICS"]): + self.system.electrostatics.clear() + if espressomd.has_features(["DIPOLES"]): + self.system.magnetostatics.clear() self.system.part.clear() # assertion: there should be no pending runtime error espressomd.utils.handle_errors("tearDown") @@ -77,7 +80,7 @@ def test_01_time_not_set_p3m_gpu(self): solver = espressomd.electrostatics.P3MGPU(prefactor=2, accuracy=1e-2) with self.assertRaisesRegex(Exception, 'time_step not set'): - self.system.actors.add(solver) + self.system.electrostatics.solver = solver @utx.skipIfMissingFeatures("P3M") def test_01_time_not_set_p3m_cpu(self): @@ -85,7 +88,7 @@ def test_01_time_not_set_p3m_cpu(self): solver = espressomd.electrostatics.P3M(prefactor=2, accuracy=1e-2) with self.assertRaisesRegex(Exception, 'time_step not set'): - self.system.actors.add(solver) + self.system.electrostatics.solver = solver @utx.skipIfMissingFeatures("DP3M") def test_01_time_not_set_dp3m_cpu(self): @@ -94,7 +97,7 @@ def test_01_time_not_set_dp3m_cpu(self): solver = espressomd.magnetostatics.DipolarP3M( prefactor=2, accuracy=1e-2) with self.assertRaisesRegex(Exception, 'time_step not set'): - self.system.actors.add(solver) + self.system.magnetostatics.solver = solver ############################################## # block of tests where particles are missing # @@ -107,7 +110,7 @@ def test_02_no_particles_p3m_gpu(self): solver = espressomd.electrostatics.P3MGPU(prefactor=2, accuracy=1e-2) with self.assertRaisesRegex(RuntimeError, 'no charged particles in the system'): - self.system.actors.add(solver) + self.system.electrostatics.solver = solver @utx.skipIfMissingFeatures("P3M") def test_02_no_particles_p3m_cpu(self): @@ -115,7 +118,7 @@ def test_02_no_particles_p3m_cpu(self): solver = espressomd.electrostatics.P3M(prefactor=2, accuracy=1e-2) with self.assertRaisesRegex(RuntimeError, 'no charged particles in the system'): - self.system.actors.add(solver) + self.system.electrostatics.solver = solver @utx.skipIfMissingFeatures("DP3M") def test_02_no_particles_dp3m_cpu(self): @@ -124,7 +127,7 @@ def test_02_no_particles_dp3m_cpu(self): solver = espressomd.magnetostatics.DipolarP3M( **self.get_valid_params('DP3M')) with self.assertRaisesRegex(RuntimeError, 'DipolarP3M: no dipolar particles in the system'): - self.system.actors.add(solver) + self.system.magnetostatics.solver = solver @utx.skipIfMissingFeatures("DP3M") def test_02_accuracy_dp3m_cpu(self): @@ -134,7 +137,7 @@ def test_02_accuracy_dp3m_cpu(self): solver = espressomd.magnetostatics.DipolarP3M( **self.get_valid_params('DP3M', accuracy=1e-20)) with self.assertRaisesRegex(Exception, 'DipolarP3M: failed to reach requested accuracy'): - self.system.actors.add(solver) + self.system.magnetostatics.solver = solver ####################################### # block of tests with non-cubic boxes # @@ -150,7 +153,7 @@ def test_03_non_cubic_box_p3m_gpu(self): solver = espressomd.electrostatics.P3MGPU( prefactor=2, accuracy=1e-2, epsilon=1) with self.assertRaisesRegex(RuntimeError, 'P3M: non-metallic epsilon requires cubic box'): - self.system.actors.add(solver) + self.system.electrostatics.solver = solver @utx.skipIfMissingFeatures("P3M") def test_03_non_cubic_box_p3m_cpu(self): @@ -161,13 +164,13 @@ def test_03_non_cubic_box_p3m_cpu(self): solver = espressomd.electrostatics.P3M( prefactor=2, accuracy=1e-2, epsilon=1, mesh=[8, 8, 8]) with self.assertRaisesRegex(RuntimeError, 'P3M: non-metallic epsilon requires cubic box'): - self.system.actors.add(solver) + self.system.electrostatics.solver = solver self.system.box_l = [10., 10., 10.] solver = espressomd.electrostatics.P3M( prefactor=2, accuracy=1e-2, epsilon=1, mesh=[4, 8, 8]) with self.assertRaisesRegex(RuntimeError, 'P3M: non-metallic epsilon requires cubic box'): - self.system.actors.add(solver) + self.system.electrostatics.solver = solver @utx.skipIfMissingFeatures("DP3M") def test_03_non_cubic_box_dp3m_cpu(self): @@ -176,14 +179,15 @@ def test_03_non_cubic_box_dp3m_cpu(self): self.add_magnetic_particles() with self.assertRaisesRegex(RuntimeError, 'DipolarP3M: requires a cubic box'): - self.system.actors.add(espressomd.magnetostatics.DipolarP3M( - **self.get_valid_params('DP3M'), tune=False)) + solver = espressomd.magnetostatics.DipolarP3M( + **self.get_valid_params('DP3M'), tune=False) + self.system.magnetostatics.solver = solver ########################################## # block of tests with invalid parameters # ########################################## - def check_invalid_params(self, class_solver, **custom_params): + def check_invalid_params(self, container, class_solver, **custom_params): valid_params = { 'prefactor': 2.0, 'accuracy': .01, 'tune': False, 'cao': 3, 'r_cut': 0.373, 'alpha': 3.81, 'mesh': (8, 8, 8), 'epsilon': 0., @@ -217,8 +221,8 @@ def check_invalid_params(self, class_solver, **custom_params): with self.assertRaisesRegex(RuntimeError, "P3M: requires periodicity"): self.system.periodicity = (True, True, False) solver = class_solver(**valid_params) - self.system.actors.add(solver) - self.assertEqual(len(self.system.actors), 0) + container.solver = solver + self.assertIsNone(container.solver) self.system.periodicity = (True, True, True) def check_invalid_cell_systems(self): @@ -241,12 +245,14 @@ def test_04_invalid_params_p3m_cpu(self): self.system.time_step = 0.01 self.add_charged_particles() - self.check_invalid_params(espressomd.electrostatics.P3M) + self.check_invalid_params( + self.system.electrostatics, + espressomd.electrostatics.P3M) # set up a valid actor solver = espressomd.electrostatics.P3M( prefactor=2, accuracy=0.1, cao=2, r_cut=3.18, mesh=8) - self.system.actors.add(solver) + self.system.electrostatics.solver = solver self.check_invalid_cell_systems() @utx.skipIfMissingGPU() @@ -255,7 +261,7 @@ def test_04_invalid_params_p3m_gpu(self): self.system.time_step = 0.01 self.add_charged_particles() - self.check_invalid_params(espressomd.electrostatics.P3MGPU, + self.check_invalid_params(self.system.electrostatics, espressomd.electrostatics.P3MGPU, mesh=3 * [28], alpha=0.3548, r_cut=4.4434) @utx.skipIfMissingFeatures("DP3M") @@ -263,23 +269,26 @@ def test_04_invalid_params_dp3m_cpu(self): self.system.time_step = 0.01 self.add_magnetic_particles() - self.check_invalid_params(espressomd.magnetostatics.DipolarP3M) + self.check_invalid_params( + self.system.magnetostatics, + espressomd.magnetostatics.DipolarP3M) # check bisection exception with self.assertRaisesRegex(RuntimeError, r"Root must be bracketed for bisection in dp3m_rtbisection"): solver = espressomd.magnetostatics.DipolarP3M( prefactor=2, accuracy=0.01, cao=1, r_cut=0.373, alpha=3.81, mesh=(8, 8, 8)) - self.system.actors.add(solver) - self.system.actors.clear() + self.system.magnetostatics.solver = solver + self.system.magnetostatics.clear() # set up a valid actor solver = espressomd.magnetostatics.DipolarP3M( **self.get_valid_params('DP3M'), tune=False) - self.system.actors.add(solver) + self.system.magnetostatics.solver = solver self.check_invalid_cell_systems() - def check_invalid_params_layer_corrections(self, solver_p3m, class_lc): + def check_invalid_params_layer_corrections( + self, container, solver_p3m, class_lc): with self.assertRaisesRegex(ValueError, "Parameter 'gap_size' must be > 0"): class_lc(actor=solver_p3m, gap_size=-1., maxPWerror=0.01) with self.assertRaisesRegex(ValueError, "Parameter 'maxPWerror' must be > 0"): @@ -290,12 +299,12 @@ def check_invalid_params_layer_corrections(self, solver_p3m, class_lc): class_lc(actor=solver_p3m, gap_size=1., maxPWerror=1., far_cut=-2.) with self.assertRaisesRegex(RuntimeError, "LC gap size .+ larger than box length in z-direction"): lc = class_lc(actor=solver_p3m, gap_size=100., maxPWerror=0.01) - self.system.actors.add(lc) - self.assertEqual(len(self.system.actors), 0) + container.solver = lc + self.assertIsNone(container.solver) - def check_invalid_params_elc_p3m(self, solver_p3m): + def check_invalid_params_elc_p3m(self, container, solver_p3m): ELC = espressomd.electrostatics.ELC - self.check_invalid_params_layer_corrections(solver_p3m, ELC) + self.check_invalid_params_layer_corrections(container, solver_p3m, ELC) self.system.part.by_id(0).q = -1.00001 @@ -303,15 +312,15 @@ def check_invalid_params_elc_p3m(self, solver_p3m): actor = ELC(actor=solver_p3m, gap_size=1., maxPWerror=1., pot_diff=3., delta_mid_top=0.5, delta_mid_bot=0.5, const_pot=True, check_neutrality=False) - self.system.actors.add(actor) - self.system.actors.clear() + self.system.electrostatics.solver = actor + self.system.electrostatics.clear() with self.assertRaisesRegex(RuntimeError, "ELC does not work for non-neutral systems and non-metallic dielectric contrast"): actor = ELC(actor=solver_p3m, gap_size=1., maxPWerror=1., pot_diff=0., delta_mid_top=0.5, delta_mid_bot=0.5, const_pot=False, check_neutrality=False) - self.system.actors.add(actor) - self.system.actors.clear() + self.system.electrostatics.solver = actor + self.system.electrostatics.clear() self.system.part.by_id(0).q = -1 @@ -319,8 +328,8 @@ def check_invalid_params_elc_p3m(self, solver_p3m): # reduce box size to make tuning converge in at most 50 steps self.system.box_l = [1., 1., 1.] elc = ELC(actor=solver_p3m, gap_size=0.5, maxPWerror=1e-90) - self.system.actors.add(elc) - self.assertEqual(len(self.system.actors), 0) + self.system.electrostatics.solver = elc + self.assertIsNone(self.system.electrostatics.solver) self.system.box_l = [10., 10., 10.] # r_cut > gap isn't allowed with dielectric contrasts @@ -330,13 +339,12 @@ def check_invalid_params_elc_p3m(self, solver_p3m): elc = ELC(actor=p3m, gap_size=p3m.r_cut / 2., maxPWerror=0.01, delta_mid_top=0.5, delta_mid_bot=0.5, pot_diff=-3., const_pot=True) - self.system.actors.add(elc) - self.assertEqual(len(self.system.actors), 0) + self.system.electrostatics.solver = elc + self.assertIsNone(self.system.electrostatics.solver) # r_cut > gap is allowed without dielectric contrasts elc = ELC(actor=p3m, gap_size=p3m.r_cut / 2., maxPWerror=0.01) - self.system.actors.add(elc) - self.assertEqual(len(self.system.actors), 1) + self.system.electrostatics.solver = elc self.assertAlmostEqual(elc.prefactor, 1.5, delta=1e-12) @utx.skipIfMissingFeatures("P3M") @@ -345,7 +353,8 @@ def test_04_invalid_params_elc_p3m_cpu(self): self.add_charged_particles() params = self.get_valid_params('P3M', tune=False) solver = espressomd.electrostatics.P3M(**params) - self.check_invalid_params_elc_p3m(solver) + self.check_invalid_params_elc_p3m( + self.system.electrostatics, solver) @utx.skipIfMissingGPU() @utx.skipIfMissingFeatures("P3M") @@ -354,7 +363,7 @@ def test_04_invalid_params_elc_p3m_gpu(self): self.add_charged_particles() params = self.get_valid_params('P3MGPU', tune=False) solver = espressomd.electrostatics.P3MGPU(**params) - self.check_invalid_params_elc_p3m(solver) + self.check_invalid_params_elc_p3m(self.system.electrostatics, solver) @utx.skipIfMissingFeatures("DP3M") def test_04_invalid_params_dlc_dp3m_cpu(self): @@ -367,12 +376,12 @@ def test_04_invalid_params_dlc_dp3m_cpu(self): solver_dp3m = espressomd.magnetostatics.DipolarP3M( epsilon='metallic', tune=False, **dp3m_params) self.check_invalid_params_layer_corrections( - solver_dp3m, espressomd.magnetostatics.DLC) + self.system.magnetostatics, solver_dp3m, espressomd.magnetostatics.DLC) solver_mdlc = espressomd.magnetostatics.DLC( gap_size=1, maxPWerror=1e-30, actor=solver_dp3m) with self.assertRaisesRegex(RuntimeError, "DLC tuning failed: maxPWerror too small"): - self.system.actors.add(solver_mdlc) + self.system.magnetostatics.solver = solver_mdlc dp3m_params = {'accuracy': 1e-30, 'mesh': [6, 6, 6], 'prefactor': 1.1, 'r_cut': 4.50} @@ -382,7 +391,7 @@ def test_04_invalid_params_dlc_dp3m_cpu(self): solver_mdlc = espressomd.magnetostatics.DLC( gap_size=1., maxPWerror=1e-2, actor=solver_dp3m) with self.assertRaisesRegex(RuntimeError, "P3M: failed to reach requested accuracy"): - self.system.actors.add(solver_mdlc) + self.system.magnetostatics.solver = solver_mdlc ########################################################### # block of tests where tuning should not throw exceptions # @@ -397,7 +406,7 @@ def test_09_no_errors_p3m_gpu(self): # mesh is fixed to significantly speed up tuning solver = espressomd.electrostatics.P3MGPU( prefactor=2, accuracy=1e-2, epsilon='metallic', mesh=[20, 20, 20]) - self.system.actors.add(solver) + self.system.electrostatics.solver = solver @utx.skipIfMissingFeatures("P3M") def test_09_no_errors_p3m_cpu(self): @@ -412,8 +421,8 @@ def test_09_no_errors_p3m_cpu(self): for key, value in valid_params.items(): solver = espressomd.electrostatics.P3M( prefactor=2, accuracy=1e-2, epsilon=0.0, **{key: value}) - self.system.actors.add(solver) - self.system.actors.clear() + self.system.electrostatics.solver = solver + self.system.electrostatics.solver = None @utx.skipIfMissingFeatures("DP3M") def test_09_no_errors_dp3m_cpu(self): @@ -428,8 +437,8 @@ def test_09_no_errors_dp3m_cpu(self): for key, value in valid_params.items(): solver = espressomd.magnetostatics.DipolarP3M( prefactor=2, accuracy=1e-2, **{key: value}) - self.system.actors.add(solver) - self.system.actors.clear() + self.system.magnetostatics.solver = solver + self.system.magnetostatics.clear() @utx.skipIfMissingFeatures("P3M") def test_09_no_errors_p3m_cpu_rescale_mesh(self): @@ -440,7 +449,7 @@ def test_09_no_errors_p3m_cpu_rescale_mesh(self): solver = espressomd.electrostatics.P3M(prefactor=2, accuracy=1e-2, epsilon='metallic', mesh=[8, -1, -1]) - self.system.actors.add(solver) + self.system.electrostatics.solver = solver np.testing.assert_equal(np.copy(solver.mesh), [8, 12, 16]) # check MD cell reset event @@ -458,7 +467,7 @@ def test_09_no_errors_p3m_gpu_rescale_mesh(self): solver = espressomd.electrostatics.P3MGPU(prefactor=2, accuracy=1e-1, epsilon='metallic', mesh=[20, -1, -1]) - self.system.actors.add(solver) + self.system.electrostatics.solver = solver np.testing.assert_equal(np.copy(solver.mesh), [20, 20, 40]) # check MD cell reset event @@ -475,14 +484,15 @@ def test_09_no_errors_dp3m_cpu_rescale_mesh(self): 'prefactor': 1.1, 'r_cut': 4.50, 'alpha': 0.8216263} solver = espressomd.magnetostatics.DipolarP3M( epsilon='metallic', tune=False, **dp3m_params) - self.system.actors.add(solver) + self.system.magnetostatics.solver = solver # check MD cell reset event self.system.box_l = self.system.box_l self.system.periodicity = self.system.periodicity self.system.cell_system.node_grid = self.system.cell_system.node_grid - def check_tuning_layer_corrections(self, class_p3m, class_lc, params): + def check_tuning_layer_corrections( + self, container, class_p3m, class_lc, params): if class_p3m is espressomd.magnetostatics.DipolarP3M: mesh_a = np.array([2., 2., 2.]) else: @@ -498,7 +508,7 @@ def check_tuning_layer_corrections(self, class_p3m, class_lc, params): # epsilon values either, it sets metallic epsilon before tuning. if class_lc is espressomd.electrostatics.ELC: self.assertEqual(p3m.epsilon, 0.) - self.system.actors.add(lc) + container.solver = lc # check parameter rescaling alpha = p3m.alpha @@ -521,6 +531,7 @@ def check_tuning_layer_corrections(self, class_p3m, class_lc, params): def test_09_no_errors_elc_p3m_cpu_rescale_mesh(self): self.add_charged_particles() self.check_tuning_layer_corrections( + self.system.electrostatics, espressomd.electrostatics.P3M, espressomd.electrostatics.ELC, self.get_valid_params("P3M", accuracy=0.1)) @@ -530,6 +541,7 @@ def test_09_no_errors_elc_p3m_cpu_rescale_mesh(self): def test_09_no_errors_elc_p3m_gpu_rescale_mesh(self): self.add_charged_particles() self.check_tuning_layer_corrections( + self.system.electrostatics, espressomd.electrostatics.P3MGPU, espressomd.electrostatics.ELC, self.get_valid_params("P3MGPU", accuracy=0.1)) @@ -538,6 +550,7 @@ def test_09_no_errors_elc_p3m_gpu_rescale_mesh(self): def test_09_no_errors_dlc_dp3m_cpu_rescale_mesh(self): self.add_magnetic_particles() self.check_tuning_layer_corrections( + self.system.magnetostatics, espressomd.magnetostatics.DipolarP3M, espressomd.magnetostatics.DLC, self.get_valid_params("DP3M", accuracy=0.1)) diff --git a/testsuite/python/save_checkpoint.py b/testsuite/python/save_checkpoint.py index a47bdc2aa08..9981fdc8f92 100644 --- a/testsuite/python/save_checkpoint.py +++ b/testsuite/python/save_checkpoint.py @@ -152,10 +152,10 @@ maxPWerror=0.1, delta_mid_top=0.9, delta_mid_bot=0.1) - system.actors.add(elc) + system.electrostatics.solver = elc elc.charge_neutrality_tolerance = 7e-12 else: - system.actors.add(p3m) + system.electrostatics.solver = p3m p3m.charge_neutrality_tolerance = 5e-12 # accumulators @@ -328,22 +328,22 @@ accuracy=0.01, timings=15, tune=False) - system.actors.add(dp3m) + system.magnetostatics.solver = dp3m if espressomd.has_features('SCAFACOS') and 'SCAFACOS' in modes \ and 'p3m' in espressomd.code_info.scafacos_methods(): - system.actors.add(espressomd.electrostatics.Scafacos( + system.electrostatics.solver = espressomd.electrostatics.Scafacos( prefactor=0.5, method_name="p3m", method_params={ "p3m_r_cut": 1.0, "p3m_grid": 64, "p3m_cao": 7, - "p3m_alpha": 2.084652})) + "p3m_alpha": 2.084652}) if espressomd.has_features('SCAFACOS_DIPOLES') and 'SCAFACOS' in modes \ and 'p2nfft' in espressomd.code_info.scafacos_methods(): - system.actors.add(espressomd.magnetostatics.Scafacos( + system.magnetostatics.solver = espressomd.magnetostatics.Scafacos( prefactor=1.2, method_name='p2nfft', method_params={ @@ -355,7 +355,7 @@ "p2nfft_ignore_tolerance": "1", "pnfft_diff_ik": "0", "p2nfft_r_cut": "11", - "p2nfft_alpha": "0.37"})) + "p2nfft_alpha": "0.37"}) if lbf_class: system.actors.add(lbf) diff --git a/testsuite/python/scafacos_interface.py b/testsuite/python/scafacos_interface.py index 8e26a66a773..7e9063400be 100644 --- a/testsuite/python/scafacos_interface.py +++ b/testsuite/python/scafacos_interface.py @@ -35,8 +35,11 @@ class ScafacosInterface(ut.TestCase): system.periodicity = 3 * [True] def tearDown(self): + if espressomd.has_features(["ELECTROSTATICS"]): + self.system.electrostatics.clear() + if espressomd.has_features(["DIPOLES"]): + self.system.magnetostatics.clear() self.system.part.clear() - self.system.actors.clear() self.system.integrator.set_vv() def test_decorator(self): @@ -58,7 +61,6 @@ def check_available_methods(self, methods): @utx.skipIfMissingScafacosMethod("p3m") @utx.skipIfMissingScafacosMethod("p2nfft") def test_magnetostatics_actor_exceptions(self): - system = self.system with self.assertRaisesRegex(ValueError, "Parameter 'prefactor' must be > 0"): espressomd.magnetostatics.Scafacos( prefactor=-0.8, method_name="p2nfft", @@ -66,14 +68,13 @@ def test_magnetostatics_actor_exceptions(self): with self.assertRaisesRegex(RuntimeError, "Dipole particles not implemented for solver method 'p3m'"): actor = espressomd.magnetostatics.Scafacos( prefactor=1., method_name="p3m", method_params={"p3m_cao": 7}) - system.actors.add(actor) - self.assertEqual(len(system.actors), 0) + self.system.magnetostatics.solver = actor + self.assertIsNone(self.system.magnetostatics.solver) @utx.skipIfMissingFeatures(["SCAFACOS"]) @utx.skipIfMissingScafacosMethod("p3m") @utx.skipIfMissingScafacosMethod("ewald") def test_electrostatics_actor_exceptions(self): - system = self.system with self.assertRaisesRegex(ValueError, "Parameter 'prefactor' must be > 0"): espressomd.electrostatics.Scafacos( prefactor=-0.8, method_name="p3m", method_params={"p3m_cao": 7}) @@ -89,12 +90,12 @@ def test_electrostatics_actor_exceptions(self): prefactor=1., method_name="ewald", method_params={"tolerance_field": 0.1}) - self.system.actors.add(scafacos) + self.system.electrostatics.solver = scafacos self.assertFalse(scafacos.call_method("get_near_field_delegation")) scafacos.call_method("set_near_field_delegation", delegate=False) with self.assertRaisesRegex(RuntimeError, "Method 'ewald' cannot delegate short-range calculation"): scafacos.call_method("set_near_field_delegation", delegate=True) - system.actors.clear() + self.system.electrostatics.clear() @utx.skipIfMissingFeatures(["SCAFACOS"]) @utx.skipIfMissingScafacosMethod("p3m") @@ -109,7 +110,7 @@ def test_actor_coulomb(self): "p3m_alpha": 2.799269, "p3m_grid": 32, "p3m_cao": 7}) - system.actors.add(actor) + system.electrostatics.solver = actor params = actor.get_params() self.assertEqual(params["prefactor"], 0.5) self.assertEqual(params["method_name"], "p3m") @@ -133,8 +134,6 @@ def test_actor_coulomb(self): @utx.skipIfMissingFeatures(["SCAFACOS"]) @utx.skipIfMissingScafacosMethod("p3m") def test_tuning_r_cut_near_field(self): - system = self.system - actor = espressomd.electrostatics.Scafacos( prefactor=0.5, method_name="p3m", @@ -143,7 +142,7 @@ def test_tuning_r_cut_near_field(self): "p3m_alpha": 2.8, "p3m_grid": 32, "p3m_cao": 7}) - system.actors.add(actor) + self.system.electrostatics.solver = actor tuned_r_cut = actor.method_params['p3m_r_cut'] self.assertGreaterEqual(tuned_r_cut, 0.1) self.assertLessEqual(tuned_r_cut, min(self.system.box_l) / 2.) @@ -179,8 +178,8 @@ def test_tuning_exceptions(self): # attempt to tune r_cut with ScaFaCoS with self.assertRaisesRegex(RuntimeError, "r_cut is negative"): - system.actors.add(actor) - system.actors.clear() + self.system.electrostatics.solver = actor + self.system.electrostatics.clear() tuned_r_cut = actor.method_params['ewald_r_cut'] self.assertAlmostEqual(tuned_r_cut, -1., delta=1e-12) @@ -188,8 +187,6 @@ def test_tuning_exceptions(self): @utx.skipIfMissingFeatures(["SCAFACOS"]) @utx.skipIfMissingScafacosMethod("ewald") def test_tuning_r_cut_ewald(self): - system = self.system - actor = espressomd.electrostatics.Scafacos( prefactor=0.5, method_name="ewald", @@ -198,7 +195,7 @@ def test_tuning_r_cut_ewald(self): "ewald_maxkmax": 200}) # let ScaFaCoS tune r_cut - system.actors.add(actor) + self.system.electrostatics.solver = actor # cutoff is hidden since we don't delegate near-field self.assertNotIn("ewald_r_cut", actor.method_params) @@ -206,8 +203,6 @@ def test_tuning_r_cut_ewald(self): @utx.skipIfMissingFeatures(["SCAFACOS"]) @utx.skipIfMissingScafacosMethod("ewald") def test_tuning_alpha_ewald(self): - system = self.system - actor = espressomd.electrostatics.Scafacos( prefactor=0.5, method_name="ewald", @@ -216,7 +211,7 @@ def test_tuning_alpha_ewald(self): "ewald_maxkmax": 200}) # let ScaFaCoS tune alpha - system.actors.add(actor) + self.system.electrostatics.solver = actor tuned_r_cut = actor.method_params['ewald_r_cut'] self.assertAlmostEqual(tuned_r_cut, 0.2, delta=1e-12) @@ -248,11 +243,11 @@ def test_actor_dipoles(self): "p2nfft_r_cut": 11, # should come back as an integer "p2nfft_alpha": "0.37" + 30 * "0" + "1", # discard extra digits } - system.actors.add(espressomd.magnetostatics.Scafacos( + self.system.magnetostatics.solver = espressomd.magnetostatics.Scafacos( prefactor=1.2, method_name="p2nfft", - method_params=method_params)) - actor = system.actors[0] + method_params=method_params) + actor = self.system.magnetostatics.solver params = actor.get_params() self.assertEqual(params["prefactor"], 1.2) self.assertEqual(params["method_name"], "p2nfft") @@ -282,7 +277,7 @@ def p3m_data(self): mesh=32, cao=7, r_cut=1.0) - system.actors.add(p3m) + self.system.electrostatics.solver = p3m dp3m = espressomd.magnetostatics.DipolarP3M( prefactor=1.0, @@ -291,7 +286,7 @@ def p3m_data(self): mesh=48, r_cut=1.88672, epsilon="metallic") - system.actors.add(dp3m) + self.system.magnetostatics.solver = dp3m system.integrator.run(0, recalc_forces=True) ref_E_coulomb = system.analysis.energy()["coulomb"] @@ -299,7 +294,8 @@ def p3m_data(self): ref_forces = np.copy(system.part.all().f) ref_torques = np.copy(system.part.all().torque_lab) - system.actors.clear() + self.system.electrostatics.clear() + self.system.magnetostatics.clear() return (ref_E_coulomb, ref_E_dipoles, ref_forces, ref_torques) @@ -320,7 +316,7 @@ def fcs_data(self): "pnfft_diff_ik": "0", "p2nfft_r_cut": "1.0", "p2nfft_alpha": "2.92"}) - system.actors.add(scafacos_coulomb) + self.system.electrostatics.solver = scafacos_coulomb scafacos_dipoles = espressomd.magnetostatics.Scafacos( prefactor=1.0, @@ -335,7 +331,7 @@ def fcs_data(self): "pnfft_diff_ik": "0", "p2nfft_r_cut": "11", "p2nfft_alpha": "0.37"}) - system.actors.add(scafacos_dipoles) + self.system.magnetostatics.solver = scafacos_dipoles system.integrator.run(0, recalc_forces=True) ref_E_coulomb = system.analysis.energy()["coulomb"] @@ -357,7 +353,8 @@ def fcs_data(self): np.testing.assert_allclose(new_forces, ref_forces, atol=0, rtol=0.) np.testing.assert_allclose(new_torques, ref_torques, atol=0, rtol=0.) - system.actors.clear() + self.system.electrostatics.clear() + self.system.magnetostatics.clear() return (ref_E_coulomb, ref_E_dipoles, ref_forces, ref_torques) diff --git a/testsuite/python/test_checkpoint.py b/testsuite/python/test_checkpoint.py index 32b09bea323..21094ae46d4 100644 --- a/testsuite/python/test_checkpoint.py +++ b/testsuite/python/test_checkpoint.py @@ -810,8 +810,8 @@ def predicate(key, attribute): @ut.skipIf('DP3M.CPU' not in modes, "Skipping test due to missing combination.") def test_dp3m(self): - actor = self.get_active_actor_of_type( - espressomd.magnetostatics.DipolarP3M) + actor = system.magnetostatics.solver + self.assertIsInstance(actor, espressomd.magnetostatics.DipolarP3M) state = actor.get_params() reference = {'prefactor': 1.0, 'accuracy': 0.01, 'mesh': 3 * [8], 'cao': 1, 'alpha': 12.0, 'r_cut': 2.4, 'tune': False, @@ -824,8 +824,8 @@ def test_dp3m(self): @utx.skipIfMissingFeatures('P3M') @ut.skipIf(not has_p3m_mode, "Skipping test due to missing combination.") def test_p3m(self): - actor = self.get_active_actor_of_type( - espressomd.electrostatics._P3MBase) + actor = system.electrostatics.solver + self.assertIsInstance(actor, espressomd.electrostatics._P3MBase) state = actor.get_params() reference = {'prefactor': 1.0, 'accuracy': 0.1, 'mesh': 3 * [10], 'cao': 1, 'alpha': 1.0, 'r_cut': 1.0, 'tune': False, @@ -840,7 +840,8 @@ def test_p3m(self): @utx.skipIfMissingFeatures('P3M') @ut.skipIf('ELC' not in modes, "Skipping test due to missing combination.") def test_elc(self): - actor = self.get_active_actor_of_type(espressomd.electrostatics.ELC) + actor = system.electrostatics.solver + self.assertIsInstance(actor, espressomd.electrostatics.ELC) elc_state = actor.get_params() p3m_state = elc_state['actor'].get_params() p3m_reference = {'prefactor': 1.0, 'accuracy': 0.1, 'mesh': 3 * [10], @@ -865,8 +866,8 @@ def test_elc(self): @utx.skipIfMissingScafacosMethod("p3m") @ut.skipIf('SCAFACOS' not in modes, "Missing combination.") def test_scafacos_coulomb(self): - actor = self.get_active_actor_of_type( - espressomd.electrostatics.Scafacos) + actor = system.electrostatics.solver + self.assertIsInstance(actor, espressomd.electrostatics.Scafacos) state = actor.get_params() reference = {'prefactor': 0.5, 'method_name': 'p3m', 'method_params': { @@ -881,8 +882,8 @@ def test_scafacos_coulomb(self): @utx.skipIfMissingScafacosMethod("p2nfft") @ut.skipIf('SCAFACOS' not in modes, "Missing combination.") def test_scafacos_dipoles(self): - actor = self.get_active_actor_of_type( - espressomd.magnetostatics.Scafacos) + actor = system.magnetostatics.solver + self.assertIsInstance(actor, espressomd.magnetostatics.Scafacos) state = actor.get_params() reference = {'prefactor': 1.2, 'method_name': 'p2nfft', 'method_params': { diff --git a/testsuite/python/tests_common.py b/testsuite/python/tests_common.py index 86f1dff7ec6..5b3b4be339d 100644 --- a/testsuite/python/tests_common.py +++ b/testsuite/python/tests_common.py @@ -58,7 +58,7 @@ def assert_params_match(ut_obj, inParams, outParams, msg_long=""): ut_obj.assertEqual(value_out, value_in, msg=msg) -def generate_test_for_actor_class(_system, _class_actor, _params): +def generate_test_for_actor_class(_container, _class_actor, _params): """ Generate a test case for an actor to verify parameters in the interface and the core match. @@ -66,7 +66,7 @@ def generate_test_for_actor_class(_system, _class_actor, _params): """ params_in = _params class_actor = _class_actor - system = _system + container = _container def func(self): # This code is run at the execution of the generated function. @@ -75,10 +75,10 @@ def func(self): # set parameters actor = class_actor(**params_in) - system.actors.add(actor) + container.solver = actor # read them out again params_out = {key: getattr(actor, key) for key in params_in} - system.actors.remove(actor) + container.solver = None assert_params_match(self, params_in, params_out, msg_long=f"Parameters set {params_in} vs. {params_out}") From 6c293f7e71d0a8307a32780d52b6b147d0faab32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 10 Jul 2023 18:52:01 +0200 Subject: [PATCH 4/5] core: Use pointer-to-implementation Separation of concerns: hide implementation details of long-range methods from the global System class. --- src/core/EspressoSystemStandAlone.cpp | 2 + src/core/actor/optional.hpp | 2 +- src/core/actor/visit_try_catch.hpp | 19 +----- src/core/electrostatics/coulomb.cpp | 57 ++++++++++------- src/core/electrostatics/coulomb.hpp | 37 ++++++++--- src/core/electrostatics/coulomb_inline.hpp | 16 ++--- src/core/electrostatics/icc.cpp | 22 ++++--- src/core/electrostatics/p3m.cpp | 11 ++-- src/core/electrostatics/p3m_gpu.cpp | 2 +- src/core/electrostatics/solver.hpp | 54 +++------------- src/core/event.cpp | 4 +- src/core/forces.cpp | 4 +- src/core/magnetostatics/dipoles.cpp | 62 ++++++++++++------- src/core/magnetostatics/dipoles.hpp | 36 +++++++---- src/core/magnetostatics/dipoles_inline.hpp | 8 +-- src/core/magnetostatics/solver.hpp | 45 +++----------- src/core/npt.cpp | 7 ++- src/core/pressure_inline.hpp | 3 +- src/core/system/System.cpp | 2 + .../EspressoSystemStandAlone_test.cpp | 4 +- src/core/unit_tests/EspressoSystem_test.cpp | 2 + src/core/unit_tests/ResourceCleanup_test.cpp | 2 + .../electrostatics/Actor_impl.hpp | 17 ++--- .../electrostatics/Container.hpp | 13 +++- .../electrostatics/ICCStar.hpp | 5 +- .../magnetostatics/Actor_impl.hpp | 5 +- .../magnetostatics/Container.hpp | 2 +- src/script_interface/system/System.cpp | 2 + src/script_interface/tests/Actors_test.cpp | 4 +- testsuite/python/dawaanr-and-dds-gpu.py | 4 +- testsuite/python/long_range_actors.py | 16 ++++- 31 files changed, 242 insertions(+), 227 deletions(-) diff --git a/src/core/EspressoSystemStandAlone.cpp b/src/core/EspressoSystemStandAlone.cpp index 3e502b9a630..5291191b49e 100644 --- a/src/core/EspressoSystemStandAlone.cpp +++ b/src/core/EspressoSystemStandAlone.cpp @@ -21,9 +21,11 @@ #include "EspressoSystemStandAlone.hpp" #include "communication.hpp" +#include "electrostatics/coulomb.hpp" #include "event.hpp" #include "grid.hpp" #include "integrate.hpp" +#include "magnetostatics/dipoles.hpp" #include "system/System.hpp" #include "virtual_sites.hpp" #include "virtual_sites/VirtualSitesOff.hpp" diff --git a/src/core/actor/optional.hpp b/src/core/actor/optional.hpp index b4e36b9fe80..4cede96d023 100644 --- a/src/core/actor/optional.hpp +++ b/src/core/actor/optional.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The ESPResSo project + * Copyright (C) 2023 The ESPResSo project * * This file is part of ESPResSo. * diff --git a/src/core/actor/visit_try_catch.hpp b/src/core/actor/visit_try_catch.hpp index 0d584550b0f..83ce9acc179 100644 --- a/src/core/actor/visit_try_catch.hpp +++ b/src/core/actor/visit_try_catch.hpp @@ -16,33 +16,20 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef ESPRESSO_SRC_CORE_ACTOR_VISIT_TRY_CATCH_HPP -#define ESPRESSO_SRC_CORE_ACTOR_VISIT_TRY_CATCH_HPP + +#pragma once #include "errorhandling.hpp" -#include #include -#include #include /** @brief Run a kernel on a variant and queue errors. */ template -void visit_active_actor_try_catch(Visitor &&visitor, Variant &actor) { +void visit_try_catch(Visitor &&visitor, Variant &actor) { try { std::visit(visitor, actor); } catch (std::runtime_error const &err) { runtimeErrorMsg() << err.what(); } } - -/** @brief Run a kernel on a variant and queue errors. */ -template -void visit_active_actor_try_catch(Visitor &&visitor, - std::optional &actor) { - if (actor) { - visit_active_actor_try_catch(std::forward(visitor), *actor); - } -} - -#endif diff --git a/src/core/electrostatics/coulomb.cpp b/src/core/electrostatics/coulomb.cpp index 238a0408828..afdaaf7e27d 100644 --- a/src/core/electrostatics/coulomb.cpp +++ b/src/core/electrostatics/coulomb.cpp @@ -19,6 +19,8 @@ #include "config/config.hpp" +#include "electrostatics/solver.hpp" + #ifdef ELECTROSTATICS #include "electrostatics/coulomb.hpp" @@ -60,38 +62,50 @@ namespace Coulomb { +Solver::Solver() { + impl = std::make_unique(); + reinit_on_observable_calc = false; +} + Solver const &get_coulomb() { return System::get_system().coulomb; } void Solver::sanity_checks() const { - if (solver) { - std::visit([](auto const &actor) { actor->sanity_checks(); }, *solver); + if (impl->solver) { + std::visit([](auto const &ptr) { ptr->sanity_checks(); }, *impl->solver); } } void Solver::on_coulomb_change() { reinit_on_observable_calc = true; - visit_active_actor_try_catch([](auto &actor) { actor->init(); }, solver); + if (impl->solver) { + visit_try_catch([](auto &ptr) { ptr->init(); }, *impl->solver); + } } void Solver::on_boxl_change() { - visit_active_actor_try_catch([](auto &actor) { actor->on_boxl_change(); }, - solver); + if (impl->solver) { + visit_try_catch([](auto &ptr) { ptr->on_boxl_change(); }, *impl->solver); + } } void Solver::on_node_grid_change() { - if (solver) { - std::visit([](auto &actor) { actor->on_node_grid_change(); }, *solver); + if (impl->solver) { + std::visit([](auto &ptr) { ptr->on_node_grid_change(); }, *impl->solver); } } void Solver::on_periodicity_change() { - visit_active_actor_try_catch( - [](auto &actor) { actor->on_periodicity_change(); }, solver); + if (impl->solver) { + visit_try_catch([](auto &ptr) { ptr->on_periodicity_change(); }, + *impl->solver); + } } void Solver::on_cell_structure_change() { - visit_active_actor_try_catch( - [](auto &actor) { actor->on_cell_structure_change(); }, solver); + if (impl->solver) { + visit_try_catch([](auto &ptr) { ptr->on_cell_structure_change(); }, + *impl->solver); + } } struct LongRangePressure { @@ -127,8 +141,8 @@ struct LongRangePressure { Utils::Vector9d Solver::calc_pressure_long_range(ParticleRange const &particles) const { - if (solver) { - return std::visit(LongRangePressure(particles), *solver); + if (impl->solver) { + return std::visit(LongRangePressure(particles), *impl->solver); } return {}; } @@ -166,8 +180,8 @@ struct ShortRangeCutoff { }; double Solver::cutoff() const { - if (solver) { - return std::visit(ShortRangeCutoff(), *solver); + if (impl->solver) { + return std::visit(ShortRangeCutoff(), *impl->solver); } return -1.0; } @@ -188,8 +202,8 @@ struct EventOnObservableCalc { void Solver::on_observable_calc() { if (reinit_on_observable_calc) { - if (solver) { - std::visit(EventOnObservableCalc(), *solver); + if (impl->solver) { + std::visit(EventOnObservableCalc(), *impl->solver); } reinit_on_observable_calc = false; } @@ -281,14 +295,14 @@ struct LongRangeEnergy { }; void Solver::calc_long_range_force(ParticleRange const &particles) const { - if (solver) { - std::visit(LongRangeForce(particles), *solver); + if (impl->solver) { + std::visit(LongRangeForce(particles), *impl->solver); } } double Solver::calc_energy_long_range(ParticleRange const &particles) const { - if (solver) { - return std::visit(LongRangeEnergy(particles), *solver); + if (impl->solver) { + return std::visit(LongRangeEnergy(particles), *impl->solver); } return 0.; } @@ -352,5 +366,4 @@ void check_charge_neutrality(double relative_tolerance) { } } // namespace Coulomb - #endif // ELECTROSTATICS diff --git a/src/core/electrostatics/coulomb.hpp b/src/core/electrostatics/coulomb.hpp index e5e5e6934ed..193779104e6 100644 --- a/src/core/electrostatics/coulomb.hpp +++ b/src/core/electrostatics/coulomb.hpp @@ -40,17 +40,40 @@ #include #include #include +#include #include +#include -/** Get the electrostatics prefactor. */ -struct GetCoulombPrefactor { - template - double operator()(std::shared_ptr const &actor) const { - return actor->prefactor; - } +namespace Coulomb { + +using ElectrostaticsActor = + std::variant, +#ifdef P3M + std::shared_ptr, +#ifdef CUDA + std::shared_ptr, +#endif // CUDA + std::shared_ptr, +#endif // P3M + std::shared_ptr, +#ifdef MMM1D_GPU + std::shared_ptr, +#endif // MMM1D_GPU +#ifdef SCAFACOS + std::shared_ptr, +#endif // SCAFACOS + std::shared_ptr>; + +using ElectrostaticsExtension = std::variant>; + +struct Solver::Implementation { + /// @brief Main electrostatics solver. + std::optional solver; + /// @brief Extension that modifies the solver behavior. + std::optional extension; + Implementation() : solver{}, extension{} {} }; -namespace Coulomb { namespace traits { #ifdef P3M diff --git a/src/core/electrostatics/coulomb_inline.hpp b/src/core/electrostatics/coulomb_inline.hpp index a7d0e5359d8..81b6cecec67 100644 --- a/src/core/electrostatics/coulomb_inline.hpp +++ b/src/core/electrostatics/coulomb_inline.hpp @@ -163,9 +163,9 @@ struct ShortRangeEnergyKernel { inline std::optional Solver::pair_force_kernel() const { #ifdef ELECTROSTATICS - if (solver) { + if (impl->solver) { auto const visitor = Coulomb::ShortRangeForceKernel(); - return std::visit(visitor, *solver); + return std::visit(visitor, *impl->solver); } #endif // ELECTROSTATICS return {}; @@ -174,9 +174,9 @@ Solver::pair_force_kernel() const { inline std::optional Solver::pair_force_elc_kernel() const { #ifdef ELECTROSTATICS - if (solver) { + if (impl->solver) { auto const visitor = Coulomb::ShortRangeForceCorrectionsKernel(); - return std::visit(visitor, *solver); + return std::visit(visitor, *impl->solver); } #endif // ELECTROSTATICS return {}; @@ -185,9 +185,9 @@ Solver::pair_force_elc_kernel() const { inline std::optional Solver::pair_pressure_kernel() const { #ifdef ELECTROSTATICS - if (solver) { + if (impl->solver) { auto const visitor = Coulomb::ShortRangePressureKernel(); - return std::visit(visitor, *solver); + return std::visit(visitor, *impl->solver); } #endif // ELECTROSTATICS return {}; @@ -196,9 +196,9 @@ Solver::pair_pressure_kernel() const { inline std::optional Solver::pair_energy_kernel() const { #ifdef ELECTROSTATICS - if (solver) { + if (impl->solver) { auto const visitor = Coulomb::ShortRangeEnergyKernel(); - return std::visit(visitor, *solver); + return std::visit(visitor, *impl->solver); } #endif // ELECTROSTATICS return {}; diff --git a/src/core/electrostatics/icc.cpp b/src/core/electrostatics/icc.cpp index f4e6dc4c3db..1a2590ba570 100644 --- a/src/core/electrostatics/icc.cpp +++ b/src/core/electrostatics/icc.cpp @@ -43,6 +43,7 @@ #include "errorhandling.hpp" #include "event.hpp" #include "integrate.hpp" +#include "system/System.hpp" #include @@ -94,7 +95,7 @@ static void force_calc_icc( } }); - Coulomb::get_coulomb().calc_long_range_force(particles); + System::get_system().coulomb.calc_long_range_force(particles); } void ICCStar::iteration(CellStructure &cell_structure, @@ -108,11 +109,12 @@ void ICCStar::iteration(CellStructure &cell_structure, return; } - auto const prefactor = - std::visit(GetCoulombPrefactor(), *(Coulomb::get_coulomb().solver)); + auto const &coulomb = System::get_system().coulomb; + auto const prefactor = std::visit( + [](auto const &ptr) { return ptr->prefactor; }, *coulomb.impl->solver); auto const pref = 1. / (prefactor * 2. * Utils::pi()); - auto const kernel = Coulomb::get_coulomb().pair_force_kernel(); - auto const elc_kernel = Coulomb::get_coulomb().pair_force_elc_kernel(); + auto const kernel = coulomb.pair_force_kernel(); + auto const elc_kernel = coulomb.pair_force_elc_kernel(); icc_cfg.citeration = 0; auto global_max_rel_diff = 0.; @@ -274,17 +276,19 @@ void ICCStar::sanity_check() const { } void ICCStar::sanity_checks_active_solver() const { - if (Coulomb::get_coulomb().solver) { - std::visit(SanityChecksICC(), *(Coulomb::get_coulomb().solver)); + auto &system = System::get_system(); + if (system.coulomb.impl->solver) { + std::visit(SanityChecksICC(), *system.coulomb.impl->solver); } else { throw std::runtime_error("An electrostatics solver is needed by ICC"); } } void update_icc_particles() { - if (Coulomb::get_coulomb().extension) { + auto &system = System::get_system(); + if (system.coulomb.impl->extension) { if (auto icc = std::get_if>( - get_ptr(Coulomb::get_coulomb().extension))) { + get_ptr(system.coulomb.impl->extension))) { (**icc).iteration(cell_structure, cell_structure.local_particles(), cell_structure.ghost_particles()); } diff --git a/src/core/electrostatics/p3m.cpp b/src/core/electrostatics/p3m.cpp index 10e6ed3cf15..244faa27f8d 100644 --- a/src/core/electrostatics/p3m.cpp +++ b/src/core/electrostatics/p3m.cpp @@ -48,6 +48,7 @@ #include "event.hpp" #include "grid.hpp" #include "integrate.hpp" +#include "system/System.hpp" #include "tuning.hpp" #include @@ -244,7 +245,7 @@ void CoulombP3M::init() { sanity_checks(); - auto const &solver = Coulomb::get_coulomb().solver; + auto const &solver = System::get_system().coulomb.impl->solver; double elc_layer = 0.; if (auto actor = get_actor_by_type(solver)) { elc_layer = actor->elc.space_layer; @@ -567,7 +568,7 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { void setup_logger(bool verbose) override { #ifdef CUDA - auto const &solver = Coulomb::get_coulomb().solver; + auto const &solver = System::get_system().coulomb.impl->solver; auto const on_gpu = has_actor_of_type(solver); #else auto const on_gpu = false; @@ -582,7 +583,7 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { std::optional layer_correction_veto_r_cut(double r_cut) const override { - auto const &solver = Coulomb::get_coulomb().solver; + auto const &solver = System::get_system().coulomb.impl->solver; if (auto actor = get_actor_by_type(solver)) { return actor->veto_r_cut(r_cut); } @@ -614,7 +615,7 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { rs_err = p3m_real_space_error(m_prefactor, r_cut_iL, p3m.sum_qpart, p3m.sum_q2, alpha_L); #ifdef CUDA - auto const &solver = Coulomb::get_coulomb().solver; + auto const &solver = System::get_system().coulomb.impl->solver; if (has_actor_of_type(solver)) { ks_err = p3mgpu_k_space_error(m_prefactor, mesh, cao, p3m.sum_qpart, p3m.sum_q2, alpha_L); @@ -662,7 +663,7 @@ class CoulombTuningAlgorithm : public TuningAlgorithm { auto tuned_params = TuningAlgorithm::Parameters{}; auto time_best = time_sentinel; auto mesh_density = m_mesh_density_min; - auto const &solver = Coulomb::get_coulomb().solver; + auto const &solver = System::get_system().coulomb.impl->solver; while (mesh_density <= m_mesh_density_max) { auto trial_params = TuningAlgorithm::Parameters{}; if (m_tune_mesh) { diff --git a/src/core/electrostatics/p3m_gpu.cpp b/src/core/electrostatics/p3m_gpu.cpp index d085132ea8d..c0f8c0e94fd 100644 --- a/src/core/electrostatics/p3m_gpu.cpp +++ b/src/core/electrostatics/p3m_gpu.cpp @@ -42,7 +42,7 @@ void CoulombP3MGPU::add_long_range_forces(ParticleRange const &) { void CoulombP3MGPU::init() { if (has_actor_of_type( - Coulomb::get_coulomb().solver)) { + System::get_system().coulomb.impl->solver)) { init_cpu_kernels(); } p3m_gpu_init(p3m.params.cao, p3m.params.mesh.data(), p3m.params.alpha); diff --git a/src/core/electrostatics/solver.hpp b/src/core/electrostatics/solver.hpp index 17b0ce4d584..665c449a67a 100644 --- a/src/core/electrostatics/solver.hpp +++ b/src/core/electrostatics/solver.hpp @@ -33,59 +33,16 @@ #include #include #include -#include - -#ifdef ELECTROSTATICS -// forward declarations -struct DebyeHueckel; -struct ReactionField; -struct ICCStar; -#ifdef P3M -struct CoulombP3M; -#ifdef CUDA -struct CoulombP3MGPU; -#endif -struct ElectrostaticLayerCorrection; -#endif -struct CoulombMMM1D; -#ifdef MMM1D_GPU -class CoulombMMM1DGpu; -#endif -#ifdef SCAFACOS -struct CoulombScafacos; -#endif - -using ElectrostaticsActor = - std::variant, -#ifdef P3M - std::shared_ptr, -#ifdef CUDA - std::shared_ptr, -#endif // CUDA - std::shared_ptr, -#endif // P3M - std::shared_ptr, -#ifdef MMM1D_GPU - std::shared_ptr, -#endif // MMM1D_GPU -#ifdef SCAFACOS - std::shared_ptr, -#endif // SCAFACOS - std::shared_ptr>; - -using ElectrostaticsExtension = std::variant>; -#endif // ELECTROSTATICS namespace Coulomb { struct Solver { #ifdef ELECTROSTATICS - /// @brief Main electrostatics solver. - std::optional solver; - /// @brief Extension that modifies the solver behavior. - std::optional extension; + struct Implementation; + /// @brief Pointer-to-implementation. + std::unique_ptr impl; /// @brief Whether to reinitialize the solver on observable calculation. - bool reinit_on_observable_calc = false; + bool reinit_on_observable_calc; Utils::Vector9d calc_pressure_long_range(ParticleRange const &particles) const; @@ -103,6 +60,9 @@ struct Solver { void calc_long_range_force(ParticleRange const &particles) const; double calc_energy_long_range(ParticleRange const &particles) const; + Solver(); +#else // ELECTROSTATICS + Solver() = default; #endif // ELECTROSTATICS using ShortRangeForceKernel = diff --git a/src/core/event.cpp b/src/core/event.cpp index 397467bac5d..9541c21235a 100644 --- a/src/core/event.cpp +++ b/src/core/event.cpp @@ -113,7 +113,7 @@ void on_integration_start(double time_step) { } #ifdef ELECTROSTATICS { - auto const &actor = System::get_system().coulomb.solver; + auto const &actor = System::get_system().coulomb.impl->solver; if (not Utils::Mpi::all_compare(comm_cart, static_cast(actor)) or (actor and not Utils::Mpi::all_compare(comm_cart, (*actor).index()))) runtimeErrorMsg() << "Nodes disagree about Coulomb long-range method"; @@ -121,7 +121,7 @@ void on_integration_start(double time_step) { #endif #ifdef DIPOLES { - auto const &actor = System::get_system().dipoles.solver; + auto const &actor = System::get_system().dipoles.impl->solver; if (not Utils::Mpi::all_compare(comm_cart, static_cast(actor)) or (actor and not Utils::Mpi::all_compare(comm_cart, (*actor).index()))) runtimeErrorMsg() << "Nodes disagree about dipolar long-range method"; diff --git a/src/core/forces.cpp b/src/core/forces.cpp index 62386976271..059b75346d7 100644 --- a/src/core/forces.cpp +++ b/src/core/forces.cpp @@ -158,9 +158,9 @@ void force_calc(CellStructure &cell_structure, double time_step, double kT) { auto particles = cell_structure.local_particles(); auto ghost_particles = cell_structure.ghost_particles(); #ifdef ELECTROSTATICS - if (espresso_system.coulomb.extension) { + if (espresso_system.coulomb.impl->extension) { if (auto icc = std::get_if>( - get_ptr(espresso_system.coulomb.extension))) { + get_ptr(espresso_system.coulomb.impl->extension))) { (**icc).iteration(cell_structure, particles, ghost_particles); } } diff --git a/src/core/magnetostatics/dipoles.cpp b/src/core/magnetostatics/dipoles.cpp index 1a480726215..5739c7e32e4 100644 --- a/src/core/magnetostatics/dipoles.cpp +++ b/src/core/magnetostatics/dipoles.cpp @@ -19,6 +19,8 @@ #include "config/config.hpp" +#include "magnetostatics/solver.hpp" + #ifdef DIPOLES #include "magnetostatics/dipoles.hpp" @@ -45,44 +47,58 @@ namespace Dipoles { +Solver::Solver() { + impl = std::make_unique(); + reinit_on_observable_calc = false; +} + Solver const &get_dipoles() { return System::get_system().dipoles; } void Solver::sanity_checks() const { - if (solver) { - std::visit([](auto &actor) { actor->sanity_checks(); }, *solver); + if (impl->solver) { + std::visit([](auto &ptr) { ptr->sanity_checks(); }, *impl->solver); } } void Solver::on_dipoles_change() { reinit_on_observable_calc = true; - visit_active_actor_try_catch([](auto &actor) { actor->init(); }, solver); + if (impl->solver) { + visit_try_catch([](auto &ptr) { ptr->init(); }, *impl->solver); + } } void Solver::on_boxl_change() { - visit_active_actor_try_catch([](auto &actor) { actor->on_boxl_change(); }, - solver); + if (impl->solver) { + visit_try_catch([](auto &ptr) { ptr->on_boxl_change(); }, *impl->solver); + } } void Solver::on_node_grid_change() { - if (solver) { - std::visit([](auto &actor) { actor->on_node_grid_change(); }, *solver); + if (impl->solver) { + std::visit([](auto &ptr) { ptr->on_node_grid_change(); }, *impl->solver); } } void Solver::on_periodicity_change() { - visit_active_actor_try_catch( - [](auto &actor) { actor->on_periodicity_change(); }, solver); + if (impl->solver) { + visit_try_catch([](auto &ptr) { ptr->on_periodicity_change(); }, + *impl->solver); + } } void Solver::on_cell_structure_change() { - visit_active_actor_try_catch( - [](auto &actor) { actor->on_cell_structure_change(); }, solver); + if (impl->solver) { + visit_try_catch([](auto &ptr) { ptr->on_cell_structure_change(); }, + *impl->solver); + } } double Solver::cutoff() const { #ifdef DP3M - if (auto dp3m = get_actor_by_type(solver)) { - return dp3m->dp3m.params.r_cut; + if (impl->solver) { + if (auto dp3m = get_actor_by_type(impl->solver)) { + return dp3m->dp3m.params.r_cut; + } } #endif return -1.; @@ -91,8 +107,10 @@ double Solver::cutoff() const { void Solver::on_observable_calc() { if (reinit_on_observable_calc) { #ifdef DP3M - if (auto dp3m = get_actor_by_type(solver)) { - dp3m->count_magnetic_particles(); + if (impl->solver) { + if (auto dp3m = get_actor_by_type(impl->solver)) { + dp3m->count_magnetic_particles(); + } } #endif reinit_on_observable_calc = false; @@ -199,28 +217,28 @@ struct LongRangeField { #endif void Solver::calc_pressure_long_range() const { - if (solver) { + if (impl->solver) { runtimeWarningMsg() << "pressure calculated, but pressure not implemented."; } } void Solver::calc_long_range_force(ParticleRange const &particles) const { - if (solver) { - std::visit(LongRangeForce(particles), *solver); + if (impl->solver) { + std::visit(LongRangeForce(particles), *impl->solver); } } double Solver::calc_energy_long_range(ParticleRange const &particles) const { - if (solver) { - return std::visit(LongRangeEnergy(particles), *solver); + if (impl->solver) { + return std::visit(LongRangeEnergy(particles), *impl->solver); } return 0.; } #ifdef DIPOLE_FIELD_TRACKING void Solver::calc_long_range_field(ParticleRange const &particles) const { - if (solver) { - std::visit(LongRangeField(particles), *solver); + if (impl->solver) { + std::visit(LongRangeField(particles), *impl->solver); } } #endif diff --git a/src/core/magnetostatics/dipoles.hpp b/src/core/magnetostatics/dipoles.hpp index 5b8d76c87e7..0da87340d59 100644 --- a/src/core/magnetostatics/dipoles.hpp +++ b/src/core/magnetostatics/dipoles.hpp @@ -38,25 +38,35 @@ #include #include #include +#include #include #include +#include -/** Get the magnetostatics prefactor. */ -struct GetDipolesPrefactor { - template - double operator()(std::shared_ptr const &actor) const { - return actor->prefactor; - } -}; +namespace Dipoles { + +using MagnetostaticsActor = + std::variant, +#ifdef DIPOLAR_DIRECT_SUM + std::shared_ptr, +#endif +#ifdef DIPOLAR_BARNES_HUT + std::shared_ptr, +#endif +#ifdef DP3M + std::shared_ptr, +#endif +#ifdef SCAFACOS_DIPOLES + std::shared_ptr, +#endif + std::shared_ptr>; -/** Run actor sanity checks. */ -struct DipolesSanityChecks { - template void operator()(std::shared_ptr const &actor) const { - actor->sanity_checks(); - } +struct Solver::Implementation { + /// @brief Main electrostatics solver. + std::optional solver; + Implementation() : solver{} {} }; -namespace Dipoles { namespace traits { /** @brief Whether an actor is a solver. */ diff --git a/src/core/magnetostatics/dipoles_inline.hpp b/src/core/magnetostatics/dipoles_inline.hpp index 0c8c3f131ef..faf6f5834d1 100644 --- a/src/core/magnetostatics/dipoles_inline.hpp +++ b/src/core/magnetostatics/dipoles_inline.hpp @@ -100,9 +100,9 @@ struct ShortRangeEnergyKernel { inline std::optional Solver::pair_force_kernel() const { #ifdef DIPOLES - if (solver) { + if (impl->solver) { auto const visitor = Dipoles::ShortRangeForceKernel(); - return std::visit(visitor, *solver); + return std::visit(visitor, *impl->solver); } #endif // DIPOLES return {}; @@ -111,9 +111,9 @@ Solver::pair_force_kernel() const { inline std::optional Solver::pair_energy_kernel() const { #ifdef DIPOLES - if (solver) { + if (impl->solver) { auto const visitor = Dipoles::ShortRangeEnergyKernel(); - return std::visit(visitor, *solver); + return std::visit(visitor, *impl->solver); } #endif // DIPOLES return {}; diff --git a/src/core/magnetostatics/solver.hpp b/src/core/magnetostatics/solver.hpp index facc05b0c95..58856326d0f 100644 --- a/src/core/magnetostatics/solver.hpp +++ b/src/core/magnetostatics/solver.hpp @@ -33,50 +33,16 @@ #include #include #include -#include - -#ifdef DIPOLES -// forward declarations -struct DipolarLayerCorrection; -struct DipolarDirectSum; -#ifdef DIPOLAR_DIRECT_SUM -struct DipolarDirectSumGpu; -#endif -#ifdef DIPOLAR_BARNES_HUT -struct DipolarBarnesHutGpu; -#endif -#ifdef DP3M -struct DipolarP3M; -#endif -#ifdef SCAFACOS_DIPOLES -struct DipolarScafacos; -#endif - -using MagnetostaticsActor = - std::variant, -#ifdef DIPOLAR_DIRECT_SUM - std::shared_ptr, -#endif -#ifdef DIPOLAR_BARNES_HUT - std::shared_ptr, -#endif -#ifdef DP3M - std::shared_ptr, -#endif -#ifdef SCAFACOS_DIPOLES - std::shared_ptr, -#endif - std::shared_ptr>; -#endif // DIPOLES namespace Dipoles { struct Solver { #ifdef DIPOLES - /// @brief Main electrostatics solver. - std::optional solver; + struct Implementation; + /// @brief Pointer-to-implementation. + std::unique_ptr impl; /// @brief Whether to reinitialize the solver on observable calculation. - bool reinit_on_observable_calc = false; + bool reinit_on_observable_calc; void sanity_checks() const; double cutoff() const; @@ -95,6 +61,9 @@ struct Solver { #ifdef DIPOLE_FIELD_TRACKING void calc_long_range_field(ParticleRange const &particles) const; #endif + Solver(); +#else // DIPOLES + Solver() = default; #endif // DIPOLES using ShortRangeForceKernel = diff --git a/src/core/npt.cpp b/src/core/npt.cpp index 5e255a60174..bdf2278fca6 100644 --- a/src/core/npt.cpp +++ b/src/core/npt.cpp @@ -48,19 +48,22 @@ void synchronize_npt_state() { } void NptIsoParameters::coulomb_dipole_sanity_checks() const { +#if defined(ELECTROSTATICS) or defined(DIPOLES) + auto &system = System::get_system(); #ifdef ELECTROSTATICS - if (dimension < 3 and not cubic_box and System::get_system().coulomb.solver) { + if (dimension < 3 and not cubic_box and system.coulomb.impl->solver) { throw std::runtime_error("If electrostatics is being used you must " "use the cubic box NpT."); } #endif #ifdef DIPOLES - if (dimension < 3 and not cubic_box and System::get_system().dipoles.solver) { + if (dimension < 3 and not cubic_box and system.dipoles.impl->solver) { throw std::runtime_error("If magnetostatics is being used you must " "use the cubic box NpT."); } #endif +#endif } NptIsoParameters::NptIsoParameters(double ext_pressure, double piston, diff --git a/src/core/pressure_inline.hpp b/src/core/pressure_inline.hpp index f99d036f731..8467c2249b6 100644 --- a/src/core/pressure_inline.hpp +++ b/src/core/pressure_inline.hpp @@ -26,6 +26,7 @@ #include "pressure.hpp" #include "bonded_interactions/bonded_interaction_data.hpp" +#include "magnetostatics/dipoles.hpp" #include "nonbonded_interactions/nonbonded_interaction_data.hpp" #include "Observable_stat.hpp" @@ -87,7 +88,7 @@ inline void add_non_bonded_pair_virials( #ifdef DIPOLES /* real space magnetic dipole-dipole */ - if (Dipoles::get_dipoles().solver) { + if (Dipoles::get_dipoles().impl->solver) { fprintf(stderr, "calculating pressure for magnetostatics which doesn't " "have it implemented\n"); } diff --git a/src/core/system/System.cpp b/src/core/system/System.cpp index 11912d8fd95..a712a59c0dc 100644 --- a/src/core/system/System.cpp +++ b/src/core/system/System.cpp @@ -18,7 +18,9 @@ */ #include "System.hpp" +#include "electrostatics/coulomb.hpp" #include "grid.hpp" +#include "magnetostatics/dipoles.hpp" #include diff --git a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp index 0164da8f059..3c9de972eb5 100644 --- a/src/core/unit_tests/EspressoSystemStandAlone_test.cpp +++ b/src/core/unit_tests/EspressoSystemStandAlone_test.cpp @@ -36,11 +36,13 @@ namespace utf = boost::unit_test; #include "bonded_interactions/harmonic.hpp" #include "cells.hpp" #include "communication.hpp" +#include "electrostatics/coulomb.hpp" #include "electrostatics/p3m.hpp" #include "energy.hpp" #include "event.hpp" #include "galilei/Galilei.hpp" #include "integrate.hpp" +#include "magnetostatics/dipoles.hpp" #include "nonbonded_interactions/lj.hpp" #include "observables/ParticleVelocities.hpp" #include "particle_node.hpp" @@ -261,7 +263,7 @@ BOOST_FIXTURE_TEST_CASE(espresso_system_stand_alone, ParticleFactory) { 1e-3}; auto solver = std::make_shared(std::move(p3m), prefactor, 1, false, true); - add_actor(comm, System::get_system().coulomb.solver, solver, + add_actor(comm, System::get_system().coulomb.impl->solver, solver, ::on_coulomb_change); // measure energies diff --git a/src/core/unit_tests/EspressoSystem_test.cpp b/src/core/unit_tests/EspressoSystem_test.cpp index a67c19d3947..332f643e78c 100644 --- a/src/core/unit_tests/EspressoSystem_test.cpp +++ b/src/core/unit_tests/EspressoSystem_test.cpp @@ -32,6 +32,8 @@ #include "Particle.hpp" #include "cells.hpp" #include "communication.hpp" +#include "electrostatics/coulomb.hpp" +#include "magnetostatics/dipoles.hpp" #include "particle_node.hpp" #include "system/GpuParticleData.hpp" #include "system/System.hpp" diff --git a/src/core/unit_tests/ResourceCleanup_test.cpp b/src/core/unit_tests/ResourceCleanup_test.cpp index ae14ccb6ec4..20f327ec036 100644 --- a/src/core/unit_tests/ResourceCleanup_test.cpp +++ b/src/core/unit_tests/ResourceCleanup_test.cpp @@ -27,6 +27,8 @@ #include "communication.hpp" #include "cuda/utils.hpp" +#include "electrostatics/coulomb.hpp" +#include "magnetostatics/dipoles.hpp" #include "system/GpuParticleData.hpp" #include "system/ResourceCleanup.hpp" #include "system/System.hpp" diff --git a/src/script_interface/electrostatics/Actor_impl.hpp b/src/script_interface/electrostatics/Actor_impl.hpp index 87ff49a89fd..8741dce53f6 100644 --- a/src/script_interface/electrostatics/Actor_impl.hpp +++ b/src/script_interface/electrostatics/Actor_impl.hpp @@ -86,18 +86,11 @@ template Variant Actor::do_call_method(std::string const &name, VariantMap const ¶ms) { if (name == "activate") { - auto &coulomb = System::get_system().coulomb; - decltype(coulomb.extension) old_extension = std::nullopt; - coulomb.extension.swap(old_extension); - try { - context()->parallel_try_catch([&]() { - add_actor(context()->get_comm(), coulomb.solver, m_actor, - ::on_coulomb_change); - }); - } catch (...) { - coulomb.extension.swap(old_extension); - throw; - } + context()->parallel_try_catch([&]() { + auto &coulomb = System::get_system().coulomb; + add_actor(context()->get_comm(), coulomb.impl->solver, m_actor, + ::on_coulomb_change); + }); return {}; } return {}; diff --git a/src/script_interface/electrostatics/Container.hpp b/src/script_interface/electrostatics/Container.hpp index 0505a72c3f3..ce3d73aa1b7 100644 --- a/src/script_interface/electrostatics/Container.hpp +++ b/src/script_interface/electrostatics/Container.hpp @@ -43,15 +43,15 @@ class Container : public AutoParameters { auto &coulomb = System::get_system().coulomb; m_solver.reset(); m_extension.reset(); - coulomb.extension = std::nullopt; - coulomb.solver = std::nullopt; + coulomb.impl->extension = std::nullopt; + coulomb.impl->solver = std::nullopt; ::on_coulomb_change(); } void reset_extension() { auto &coulomb = System::get_system().coulomb; m_extension.reset(); - coulomb.extension = std::nullopt; + coulomb.impl->extension = std::nullopt; ::on_coulomb_change(); } @@ -60,6 +60,13 @@ class Container : public AutoParameters { add_parameters({ {"solver", [this](Variant const &v) { + if (m_extension) { + if (context()->is_head_node()) { + throw std::runtime_error( + "Cannot change solver when an extension is active"); + } + throw Exception(""); + } if (is_none(v)) { reset_solver(); } else { diff --git a/src/script_interface/electrostatics/ICCStar.hpp b/src/script_interface/electrostatics/ICCStar.hpp index c09026bb67d..615a25089a9 100644 --- a/src/script_interface/electrostatics/ICCStar.hpp +++ b/src/script_interface/electrostatics/ICCStar.hpp @@ -101,8 +101,9 @@ class ICCStar : public AutoParameters { VariantMap const ¶ms) override { if (name == "activate") { context()->parallel_try_catch([&]() { - add_actor(context()->get_comm(), System::get_system().coulomb.extension, - m_actor, ::on_coulomb_change); + add_actor(context()->get_comm(), + System::get_system().coulomb.impl->extension, m_actor, + ::on_coulomb_change); }); return {}; } diff --git a/src/script_interface/magnetostatics/Actor_impl.hpp b/src/script_interface/magnetostatics/Actor_impl.hpp index 925ca00d908..71d319764d3 100644 --- a/src/script_interface/magnetostatics/Actor_impl.hpp +++ b/src/script_interface/magnetostatics/Actor_impl.hpp @@ -40,8 +40,9 @@ Variant Actor::do_call_method(std::string const &name, VariantMap const ¶ms) { if (name == "activate") { context()->parallel_try_catch([&]() { - add_actor(context()->get_comm(), System::get_system().dipoles.solver, - m_actor, ::on_dipoles_change); + add_actor(context()->get_comm(), + System::get_system().dipoles.impl->solver, m_actor, + ::on_dipoles_change); }); return {}; } diff --git a/src/script_interface/magnetostatics/Container.hpp b/src/script_interface/magnetostatics/Container.hpp index 3fd5ef55d57..f371abd4a74 100644 --- a/src/script_interface/magnetostatics/Container.hpp +++ b/src/script_interface/magnetostatics/Container.hpp @@ -41,7 +41,7 @@ class Container : public AutoParameters { void reset_solver() { auto &dipoles = System::get_system().dipoles; m_solver.reset(); - dipoles.solver = std::nullopt; + dipoles.impl->solver = std::nullopt; ::on_dipoles_change(); } diff --git a/src/script_interface/system/System.cpp b/src/script_interface/system/System.cpp index 9644822d1b8..212542b48e3 100644 --- a/src/script_interface/system/System.cpp +++ b/src/script_interface/system/System.cpp @@ -20,8 +20,10 @@ #include "System.hpp" #include "core/cells.hpp" +#include "core/electrostatics/coulomb.hpp" #include "core/event.hpp" #include "core/grid.hpp" +#include "core/magnetostatics/dipoles.hpp" #include "core/nonbonded_interactions/nonbonded_interaction_data.hpp" #include "core/object-in-fluid/oif_global_forces.hpp" #include "core/particle_node.hpp" diff --git a/src/script_interface/tests/Actors_test.cpp b/src/script_interface/tests/Actors_test.cpp index 33c22e2a693..bda702f2a60 100644 --- a/src/script_interface/tests/Actors_test.cpp +++ b/src/script_interface/tests/Actors_test.cpp @@ -99,9 +99,9 @@ BOOST_AUTO_TEST_CASE(coulomb_actor) { BOOST_CHECK_CLOSE(std::as_const(actor).actor()->prefactor, 2., tol); // check visitors BOOST_CHECK(has_actor_of_type<::DebyeHueckel>( - std::optional(actor.actor()))); + std::optional(actor.actor()))); BOOST_CHECK(not has_actor_of_type( - std::optional(actor.actor()))); + std::optional(actor.actor()))); } #endif // ELECTROSTATICS diff --git a/testsuite/python/dawaanr-and-dds-gpu.py b/testsuite/python/dawaanr-and-dds-gpu.py index 2a344d54146..3f3c0c97281 100644 --- a/testsuite/python/dawaanr-and-dds-gpu.py +++ b/testsuite/python/dawaanr-and-dds-gpu.py @@ -100,12 +100,12 @@ def test(self): np.array(dawaanr_t[i]), ratio_dawaanr_dds_gpu * np.array(ddsgpu_t[i]), err_msg=f'Torques do not match for particle {i}', - atol=3e-3) + atol=3.2e-3) np.testing.assert_allclose( np.array(dawaanr_f[i]), ratio_dawaanr_dds_gpu * np.array(ddsgpu_f[i]), err_msg=f'Forces do not match for particle {i}', - atol=3e-3) + atol=3.2e-3) self.assertAlmostEqual( dawaanr_e, ddsgpu_e * ratio_dawaanr_dds_gpu, diff --git a/testsuite/python/long_range_actors.py b/testsuite/python/long_range_actors.py index e06b4b74a5a..7b37911c2bb 100644 --- a/testsuite/python/long_range_actors.py +++ b/testsuite/python/long_range_actors.py @@ -103,11 +103,22 @@ def test_electrostatics_registration(self): self.system.electrostatics.solver = p3m self.system.electrostatics.extension = icc - self.system.electrostatics.solver = p3m_new + if espressomd.has_features(["NPT"]): + with self.assertRaisesRegex(Exception, "ERROR: ICC does not work in the NPT ensemble"): + self.system.integrator.set_isotropic_npt( + ext_pressure=2., piston=0.01) + self.system.integrator.run(0) + self.system.integrator.set_vv() + with self.assertRaisesRegex(RuntimeError, "Cannot change solver when an extension is active"): + self.system.electrostatics.solver = p3m_new + with self.assertRaisesRegex(RuntimeError, "Cannot change solver when an extension is active"): + self.system.electrostatics.solver = None self.assertIsNotNone(self.system.electrostatics.extension) - self.system.electrostatics.solver = None + self.system.electrostatics.clear() + self.assertIsNone(self.system.electrostatics.solver) self.assertIsNone(self.system.electrostatics.extension) self.system.integrator.run(0) + self.assertIsNone(self.system.electrostatics.call_method("unknown")) @utx.skipIfMissingFeatures(["DP3M"]) def test_magnetostatics_registration(self): @@ -117,6 +128,7 @@ def test_magnetostatics_registration(self): self.assertIsNone(dp3m.call_method("unknown")) self.system.magnetostatics.solver = dp3m + self.assertIsNone(self.system.electrostatics.call_method("unknown")) def check_obs_stats(self, key): # check observable statistics have the correct shape From 61ea4e4917d6a04cde80e58a5896ddb61b139bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Mon, 10 Jul 2023 20:10:42 +0200 Subject: [PATCH 5/5] script_interface: Add automatic feature checks --- src/python/espressomd/electrokinetics.py | 27 ++----------- src/python/espressomd/electrostatics.py | 44 ++++----------------- src/python/espressomd/lb.py | 9 +---- src/python/espressomd/magnetostatics.py | 40 +++---------------- src/python/espressomd/script_interface.pxd | 3 ++ src/python/espressomd/script_interface.pyx | 6 +++ src/script_interface/code_info/CodeInfo.cpp | 25 ++++++++++++ src/script_interface/code_info/CodeInfo.hpp | 3 ++ testsuite/python/script_interface.py | 23 ++++++++++- 9 files changed, 78 insertions(+), 102 deletions(-) diff --git a/src/python/espressomd/electrokinetics.py b/src/python/espressomd/electrokinetics.py index 48532405f8f..f7585cd6cc7 100644 --- a/src/python/espressomd/electrokinetics.py +++ b/src/python/espressomd/electrokinetics.py @@ -25,7 +25,6 @@ from .script_interface import ScriptInterfaceHelper, script_interface_register, ScriptObjectList, array_variant import espressomd.detail.walberla import espressomd.shapes -import espressomd.code_features @script_interface_register @@ -34,16 +33,10 @@ class EKFFT(ScriptInterfaceHelper): A FFT-based Poisson solver. """ - _so_name = "walberla::EKFFT" + _so_features = ("WALBERLA_FFT",) _so_creation_policy = "GLOBAL" - def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA_FFT"): - raise NotImplementedError("Feature WALBERLA not compiled in") - - super().__init__(*args, **kwargs) - @script_interface_register class EKNone(ScriptInterfaceHelper): @@ -53,24 +46,14 @@ class EKNone(ScriptInterfaceHelper): """ _so_name = "walberla::EKNone" + _so_features = ("WALBERLA",) _so_creation_policy = "GLOBAL" - def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") - - super().__init__(*args, **kwargs) - @script_interface_register class EKContainer(ScriptObjectList): _so_name = "walberla::EKContainer" - - def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") - - super().__init__(*args, **kwargs) + _so_features = ("WALBERLA",) def add(self, ekspecies): self.call_method("add", object=ekspecies) @@ -165,6 +148,7 @@ class EKSpecies(ScriptInterfaceHelper, """ _so_name = "walberla::EKSpecies" + _so_features = ("WALBERLA",) _so_creation_policy = "GLOBAL" _so_bind_methods = ( "clear_density_boundaries", @@ -176,9 +160,6 @@ class EKSpecies(ScriptInterfaceHelper, ) def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") - if "sip" not in kwargs: params = self.default_params() params.update(kwargs) diff --git a/src/python/espressomd/electrostatics.py b/src/python/espressomd/electrostatics.py index 2ed531fc354..b24b86f2505 100644 --- a/src/python/espressomd/electrostatics.py +++ b/src/python/espressomd/electrostatics.py @@ -19,20 +19,14 @@ from . import utils from .script_interface import ScriptInterfaceHelper, script_interface_register -from .code_features import has_features @script_interface_register class Container(ScriptInterfaceHelper): _so_name = "Coulomb::Container" + _so_features = ("ELECTROSTATICS",) _so_bind_methods = ("clear",) - def __init__(self, *args, **kwargs): - if not has_features("ELECTROSTATICS"): - raise NotImplementedError("Feature ELECTROSTATICS not compiled in") - - super().__init__(*args, **kwargs) - class ElectrostaticInteraction(ScriptInterfaceHelper): """ @@ -45,10 +39,9 @@ class ElectrostaticInteraction(ScriptInterfaceHelper): """ _so_creation_policy = "GLOBAL" + _so_features = ("ELECTROSTATICS",) def __init__(self, **kwargs): - self._check_required_features() - if 'sip' not in kwargs: for key in self.required_keys(): if key not in kwargs: @@ -64,10 +57,6 @@ def __init__(self, **kwargs): else: super().__init__(**kwargs) - def _check_required_features(self): - if not has_features("ELECTROSTATICS"): - raise NotImplementedError("Feature ELECTROSTATICS not compiled in") - def validate_params(self, params): """Check validity of given parameters. """ @@ -243,10 +232,7 @@ class P3M(_P3MBase): """ _so_name = "Coulomb::CoulombP3M" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("P3M"): - raise NotImplementedError("Feature P3M not compiled in") + _so_features = ("P3M",) @script_interface_register @@ -296,12 +282,7 @@ class P3MGPU(_P3MBase): """ _so_name = "Coulomb::CoulombP3MGPU" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("P3M"): - raise NotImplementedError("Feature P3M not compiled in") - if not has_features("CUDA"): - raise NotImplementedError("Feature CUDA not compiled in") + _so_features = ("P3M", "CUDA") @script_interface_register @@ -360,10 +341,7 @@ class ELC(ElectrostaticInteraction): """ _so_name = "Coulomb::ElectrostaticLayerCorrection" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("P3M"): - raise NotImplementedError("Feature P3M not compiled in") + _so_features = ("P3M",) def validate_params(self, params): utils.check_type_or_throw_except( @@ -447,10 +425,7 @@ class MMM1DGPU(ElectrostaticInteraction): """ _so_name = "Coulomb::CoulombMMM1DGpu" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("MMM1D_GPU"): - raise NotImplementedError("Feature MMM1D_GPU not compiled in") + _so_features = ("MMM1D_GPU",) def default_params(self): return {"far_switch_radius": -1., @@ -505,17 +480,12 @@ class Scafacos(ElectrostaticInteraction): """ _so_name = "Coulomb::CoulombScafacos" _so_creation_policy = "GLOBAL" + _so_features = ("ELECTROSTATICS", "SCAFACOS") _so_bind_methods = ElectrostaticInteraction._so_bind_methods + \ ("get_available_methods", "get_near_field_delegation", "set_near_field_delegation") - def _check_required_features(self): - if not has_features("ELECTROSTATICS"): - raise NotImplementedError("Feature ELECTROSTATICS not compiled in") - if not has_features("SCAFACOS"): - raise NotImplementedError("Feature SCAFACOS not compiled in") - def default_params(self): return {"check_neutrality": True} diff --git a/src/python/espressomd/lb.py b/src/python/espressomd/lb.py index d2bc3f34c70..24afb8bb374 100644 --- a/src/python/espressomd/lb.py +++ b/src/python/espressomd/lb.py @@ -226,6 +226,7 @@ class LBFluidWalberla(HydrodynamicInteraction, """ _so_name = "walberla::LBFluid" + _so_features = ("WALBERLA",) _so_creation_policy = "GLOBAL" _so_bind_methods = ( "add_force_at_pos", @@ -237,9 +238,6 @@ class LBFluidWalberla(HydrodynamicInteraction, ) def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") - if "sip" not in kwargs: params = self.default_params() params.update(kwargs) @@ -329,13 +327,10 @@ class LBFluidWalberlaGPU(HydrodynamicInteraction): list of parameters. """ + _so_features = ("WALBERLA", "CUDA") # pylint: disable=unused-argument def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("CUDA"): - raise NotImplementedError("Feature CUDA not compiled in") - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") raise NotImplementedError("Not implemented yet") diff --git a/src/python/espressomd/magnetostatics.py b/src/python/espressomd/magnetostatics.py index a3ddba7c829..79473672cdf 100644 --- a/src/python/espressomd/magnetostatics.py +++ b/src/python/espressomd/magnetostatics.py @@ -19,20 +19,14 @@ from . import utils from .script_interface import ScriptInterfaceHelper, script_interface_register -from .code_features import has_features @script_interface_register class Container(ScriptInterfaceHelper): _so_name = "Dipoles::Container" + _so_features = ("DIPOLES",) _so_bind_methods = ("clear",) - def __init__(self, *args, **kwargs): - if not has_features("DIPOLES"): - raise NotImplementedError("Feature DIPOLES not compiled in") - - super().__init__(*args, **kwargs) - class MagnetostaticInteraction(ScriptInterfaceHelper): """ @@ -45,10 +39,9 @@ class MagnetostaticInteraction(ScriptInterfaceHelper): """ _so_creation_policy = "GLOBAL" + _so_features = ("DIPOLES",) def __init__(self, **kwargs): - self._check_required_features() - if 'sip' not in kwargs: for key in self.required_keys(): if key not in kwargs: @@ -65,10 +58,6 @@ def __init__(self, **kwargs): else: super().__init__(**kwargs) - def _check_required_features(self): - if not has_features("DIPOLES"): - raise NotImplementedError("Feature DIPOLES not compiled in") - def validate_params(self, params): """Check validity of given parameters. """ @@ -128,10 +117,7 @@ class DipolarP3M(MagnetostaticInteraction): """ _so_name = "Dipoles::DipolarP3M" - - def _check_required_features(self): - if not has_features("DP3M"): - raise NotImplementedError("Feature DP3M not compiled in") + _so_features = ("DP3M",) def validate_params(self, params): """Check validity of parameters. @@ -234,16 +220,10 @@ class Scafacos(MagnetostaticInteraction): """ _so_name = "Dipoles::DipolarScafacos" _so_creation_policy = "GLOBAL" + _so_features = ("DIPOLES", "SCAFACOS_DIPOLES") _so_bind_methods = MagnetostaticInteraction._so_bind_methods + \ ("get_available_methods", ) - def _check_required_features(self): - if not has_features("DIPOLES"): - raise NotImplementedError("Feature DIPOLES not compiled in") - if not has_features("SCAFACOS_DIPOLES"): - raise NotImplementedError( - "Feature SCAFACOS_DIPOLES not compiled in") - def default_params(self): return {} @@ -274,11 +254,7 @@ class DipolarDirectSumGpu(MagnetostaticInteraction): """ _so_name = "Dipoles::DipolarDirectSumGpu" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("DIPOLAR_DIRECT_SUM"): - raise NotImplementedError( - "Features CUDA and DIPOLES not compiled in") + _so_features = ("DIPOLAR_DIRECT_SUM", "CUDA") def default_params(self): return {} @@ -310,11 +286,7 @@ class DipolarBarnesHutGpu(MagnetostaticInteraction): """ _so_name = "Dipoles::DipolarBarnesHutGpu" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("DIPOLAR_BARNES_HUT"): - raise NotImplementedError( - "Features CUDA and DIPOLES not compiled in") + _so_features = ("DIPOLAR_BARNES_HUT", "CUDA") def default_params(self): return {"epssq": 100.0, "itolsq": 4.0} diff --git a/src/python/espressomd/script_interface.pxd b/src/python/espressomd/script_interface.pxd index d485b3db740..bf074a0c542 100644 --- a/src/python/espressomd/script_interface.pxd +++ b/src/python/espressomd/script_interface.pxd @@ -73,4 +73,7 @@ cdef extern from "script_interface/initialize.hpp" namespace "ScriptInterface": cdef extern from "script_interface/get_value.hpp" namespace "ScriptInterface": T get_value[T](const Variant T) +cdef extern from "script_interface/code_info/CodeInfo.hpp" namespace "ScriptInterface::CodeInfo": + void check_features(const vector[string] & features) except + + cdef void init(MpiCallbacks &) diff --git a/src/python/espressomd/script_interface.pyx b/src/python/espressomd/script_interface.pyx index d341c829d3f..d663a96adb4 100644 --- a/src/python/espressomd/script_interface.pyx +++ b/src/python/espressomd/script_interface.pyx @@ -426,10 +426,16 @@ def _unpickle_so_class(so_name, state): class ScriptInterfaceHelper(PScriptInterface): _so_name = None + _so_features = () _so_bind_methods = () _so_creation_policy = "GLOBAL" def __init__(self, **kwargs): + cdef vector[string] features_vec + if self._so_features: + for feature in self._so_features: + features_vec.push_back(utils.to_char_pointer(feature)) + check_features(features_vec) super().__init__(self._so_name, policy=self._so_creation_policy, **kwargs) self.define_bound_methods() diff --git a/src/script_interface/code_info/CodeInfo.cpp b/src/script_interface/code_info/CodeInfo.cpp index 8cb57175d4b..eb2870a42d1 100644 --- a/src/script_interface/code_info/CodeInfo.cpp +++ b/src/script_interface/code_info/CodeInfo.cpp @@ -23,12 +23,19 @@ #include "config/version.hpp" #include "script_interface/scafacos/scafacos.hpp" +#include + +#include #include #include namespace ScriptInterface { namespace CodeInfo { +static auto get_feature_vector(char const *const ptr[], unsigned int len) { + return std::vector{ptr, ptr + len}; +} + static Variant get_feature_list(char const *const ptr[], unsigned int len) { return make_vector_of_variants(std::vector{ptr, ptr + len}); } @@ -54,5 +61,23 @@ Variant CodeInfo::do_call_method(std::string const &name, return {}; } +void check_features(std::vector const &features) { + auto const allowed = get_feature_vector(FEATURES_ALL, NUM_FEATURES_ALL); + auto const built = get_feature_vector(FEATURES, NUM_FEATURES); + std::vector missing_features{}; + for (auto const &feature : features) { + if (std::find(allowed.begin(), allowed.end(), feature) == allowed.end()) { + throw std::runtime_error("Unknown feature '" + feature + "'"); + } + if (std::find(built.begin(), built.end(), feature) == built.end()) { + missing_features.emplace_back(feature); + } + } + if (not missing_features.empty()) { + throw std::runtime_error("Missing features " + + boost::algorithm::join(missing_features, ", ")); + } +} + } // namespace CodeInfo } // namespace ScriptInterface diff --git a/src/script_interface/code_info/CodeInfo.hpp b/src/script_interface/code_info/CodeInfo.hpp index 70f80c2471e..4e2cbf72bb5 100644 --- a/src/script_interface/code_info/CodeInfo.hpp +++ b/src/script_interface/code_info/CodeInfo.hpp @@ -23,6 +23,7 @@ #include "script_interface/ScriptInterface.hpp" #include +#include namespace ScriptInterface { namespace CodeInfo { @@ -33,6 +34,8 @@ class CodeInfo : public ObjectHandle { VariantMap const ¶meters) override; }; +void check_features(std::vector const &features); + } // namespace CodeInfo } // namespace ScriptInterface diff --git a/testsuite/python/script_interface.py b/testsuite/python/script_interface.py index ad973686aa5..7665a682514 100644 --- a/testsuite/python/script_interface.py +++ b/testsuite/python/script_interface.py @@ -22,6 +22,8 @@ import espressomd.shapes import espressomd.constraints import espressomd.interactions +import espressomd.script_interface +import espressomd.code_info class SphereWithProperties(espressomd.shapes.Sphere): @@ -100,8 +102,27 @@ def test_autoparameter_exceptions(self): with self.assertRaisesRegex(AttributeError, "Object 'HarmonicBond' has no attribute 'unknown'"): bond.unknown + def test_feature_exceptions(self): + """Check feature verification""" + all_features = set(espressomd.code_info.all_features()) + active_features = set(espressomd.code_info.features()) + missing_features = sorted(list(all_features - active_features)) + + class Unknown(espressomd.script_interface.ScriptInterfaceHelper): + _so_features = ("UNKNOWN",) + + class Missing(espressomd.script_interface.ScriptInterfaceHelper): + _so_features = missing_features + + with self.assertRaisesRegex(RuntimeError, "Unknown feature 'UNKNOWN'"): + Unknown() + + if missing_features: + with self.assertRaisesRegex(RuntimeError, f"Missing features {', '.join(missing_features)}"): + Missing() + def test_variant_exceptions(self): - """Check AutoParameters framework""" + """Check variant conversion""" constraint = espressomd.constraints.ShapeBasedConstraint() # check conversion of unsupported types err_msg = "No conversion from type 'module' to 'Variant'"