diff --git a/src/core/ParticlePropertyIterator.hpp b/src/core/ParticlePropertyIterator.hpp new file mode 100644 index 0000000000..2034be8ac3 --- /dev/null +++ b/src/core/ParticlePropertyIterator.hpp @@ -0,0 +1,64 @@ +/* + * 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 "BoxGeometry.hpp" +#include "Particle.hpp" +#include "ParticleRange.hpp" + +#include +#include + +#include + +namespace ParticlePropertyRange { + +namespace detail { +template +auto create_transform_range(ParticleRange const &particles, Kernel kernel) { + auto transform_iterator_begin = + boost::make_transform_iterator(particles.begin(), kernel); + auto transform_iterator_end = + boost::make_transform_iterator(particles.end(), kernel); + return boost::make_iterator_range( + transform_iterator_begin, transform_iterator_end); +} +} // namespace detail + +auto unfolded_pos_range(ParticleRange const &particles, + BoxGeometry const &box) { + auto return_unfolded_pos = [&box](Particle &p) { + return ::detail::unfolded_position(p.pos(), p.image_box(), box.length()); + }; + return detail::create_transform_range(particles, return_unfolded_pos); +} + +auto charge_range(ParticleRange const &particles) { + auto return_charge = [](Particle &p) -> double & { return p.q(); }; + return detail::create_transform_range(particles, return_charge); +} +auto force_range(ParticleRange const &particles) { + auto return_force = [](Particle &p) -> Utils::Vector3d & { + return p.force(); + }; + return detail::create_transform_range(particles, return_force); +} + +} // namespace ParticlePropertyRange diff --git a/src/core/electrostatics/p3m.cpp b/src/core/electrostatics/p3m.cpp index a5b01443c4..d7b70dc285 100644 --- a/src/core/electrostatics/p3m.cpp +++ b/src/core/electrostatics/p3m.cpp @@ -41,6 +41,7 @@ #include "BoxGeometry.hpp" #include "LocalBox.hpp" #include "Particle.hpp" +#include "ParticlePropertyIterator.hpp" #include "ParticleRange.hpp" #include "actor/visitors.hpp" #include "cell_system/CellStructure.hpp" @@ -64,6 +65,7 @@ #include #include #include +#include #include #include @@ -354,8 +356,9 @@ void CoulombP3M::assign_charge(double q, Utils::Vector3d const &real_pos) { } template struct AssignForces { + template void operator()(p3m_data_struct &p3m, double force_prefac, - ParticleRange const &particles) const { + combined_ranges const &p_q_force_range) const { using Utils::make_const_span; using Utils::Span; using Utils::Vector; @@ -365,9 +368,11 @@ template struct AssignForces { /* charged particle counter */ auto p_index = std::size_t{0ul}; - for (auto &p : particles) { - if (p.q() != 0.0) { - auto const pref = p.q() * force_prefac; + for (auto zipped : p_q_force_range) { + auto p_q = boost::get<0>(zipped); + auto &p_force = boost::get<1>(zipped); + if (p_q != 0.0) { + auto const pref = p_q * force_prefac; auto const w = p3m.inter_weights.load(p_index); Utils::Vector3d force{}; @@ -376,22 +381,23 @@ template struct AssignForces { p3m.E_mesh[2][ind]}; }); - p.force() -= pref * force; + p_force -= pref * force; ++p_index; } } } }; +template static auto calc_dipole_moment(boost::mpi::communicator const &comm, - ParticleRange const &particles, - BoxGeometry const &box_geo) { - auto const local_dip = boost::accumulate( - particles, Utils::Vector3d{}, - [&box_geo](Utils::Vector3d const &dip, auto const &p) { - return dip + p.q() * box_geo.unfolded_position(p.pos(), p.image_box()); - }); - + combined_ranges const &p_q_unfolded_pos_range) { + auto const local_dip = + boost::accumulate(p_q_unfolded_pos_range, Utils::Vector3d{}, + [](Utils::Vector3d const &dip, auto const &q_pos) { + auto const p_q = boost::get<0>(q_pos); + auto const &p_unfolded_pos = boost::get<1>(q_pos); + return dip + p_q * p_unfolded_pos; + }); return boost::mpi::all_reduce(comm, local_dip, std::plus<>()); } @@ -467,13 +473,19 @@ double CoulombP3M::long_range_kernel(bool force_flag, bool energy_flag, p3m.sm.gather_grid(p3m.rs_mesh.data(), comm_cart, p3m.local_mesh.dim); fft_perform_forw(p3m.rs_mesh.data(), p3m.fft, comm_cart); + auto p_q_range = ParticlePropertyRange::charge_range(particles); + auto p_force_range = ParticlePropertyRange::force_range(particles); + auto p_unfolded_pos_range = + ParticlePropertyRange::unfolded_pos_range(particles, box_geo); + // 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) - ? calc_dipole_moment(comm_cart, particles, box_geo) - : Utils::Vector3d::broadcast(0.); + ? std::make_optional(calc_dipole_moment( + comm_cart, boost::combine(p_q_range, p_unfolded_pos_range))) + : std::nullopt; auto const volume = box_geo.volume(); auto const pref = 4. * Utils::pi() / volume / (2. * p3m.params.epsilon + 1.); @@ -520,14 +532,17 @@ double CoulombP3M::long_range_kernel(bool force_flag, bool energy_flag, p3m.local_mesh.dim); auto const force_prefac = prefactor / volume; - Utils::integral_parameter(p3m.params.cao, p3m, - force_prefac, particles); + Utils::integral_parameter( + p3m.params.cao, p3m, force_prefac, + boost::combine(p_q_range, p_force_range)); // add dipole forces - if (p3m.params.epsilon != P3M_EPSILON_METALLIC) { - auto const dm = prefactor * pref * box_dipole; - for (auto &p : particles) { - p.force() -= p.q() * dm; + if (box_dipole) { + auto const dm = prefactor * pref * box_dipole.value(); + for (auto zipped : boost::combine(p_q_range, p_force_range)) { + auto p_q = boost::get<0>(zipped); + auto &p_force = boost::get<1>(zipped); + p_force -= p_q * dm; } } } @@ -551,8 +566,8 @@ double CoulombP3M::long_range_kernel(bool force_flag, bool energy_flag, energy -= p3m.square_sum_q * Utils::pi() / (2. * volume * Utils::sqr(p3m.params.alpha)); /* dipole correction */ - if (p3m.params.epsilon != P3M_EPSILON_METALLIC) { - energy += pref * box_dipole.norm2(); + if (box_dipole) { + energy += pref * box_dipole.value().norm2(); } } return prefactor * energy;