-
Notifications
You must be signed in to change notification settings - Fork 189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
P3M cleanup of ParticleRanges #4841
Conversation
The |
35cfc64
to
7d6434b
Compare
The PR was rebased on top of the python branch, which brought a lot of improvements regarding separation of concerns with call to long-range energy/force/pressure kernels. As I understand it, we still haven't made P3M fully independent of ESPResSo implementation details. The next step would be to remove all void charge_assign(ParticleRange const &particles); to a signature that takes a boost zip iterator of the particle positions and electrical charges. We cannot make this method a templated method that takes any type of zip iterator, because this would force us to move the method definition to the header file, and thus expose a lot of the internal logic. Alternatively, we could provide the full type of the boost zip iterator in the function signature, like so: void charge_assign(
decltype(boost::combine(
std::declval<std::invoke_result_t<decltype(ParticlePropertyRange::q_range),ParticleRange const &>>(),
std::declval<std::invoke_result_t<decltype(ParticlePropertyRange::pos_range),ParticleRange const &>>())
) const &p_q_pos_range); Not only is this a mouthful, but it also contains the
I'm not completely sure if it's safe to use Since we want to hide the As a quick refresher, here is what type erasure looks like: struct TypeErasedRange {
template <typename Range>
TypeErasedRange(Range &range) : m_data{new Impl<Range>(range)} {}
struct Base {};
private:
template <typename Range> struct Impl : Base {
Impl(Range &range) : m_begin(range.begin()), m_end(range.end()) {}
private:
typename Range::iterator m_begin;
typename Range::iterator m_end;
};
std::unique_ptr<Base> m_data;
};
int main() {
std::vector<int> vec = {1, 2, 3};
TypeErasedRange erased_vec{vec};
} Here
With these 3 functions, we fulfill the minimal requirements of a LegacyForwardIterator. There is a lot of boilerplate code to completely implement the iterator, but this can be done automatically with a boost iterator facade. A minimal working example can be found at the bottom of this message. With it, the boost combine symbol that originally contained a tuple of It is not immediately obvious to me, how to avoid that cost. We cannot increment the pointer by adding the size of the pointee (e.g., @RudolfWeeber before I invest more time into this, please let me know if you think this is the right approach. Maybe I overlooked a more suitable design pattern. MWE: #include <boost/iterator/iterator_facade.hpp>
#include <boost/range/combine.hpp>
#include <boost/range/iterator_range.hpp>
#include <memory>
#include <utility>
#include <vector>
template <typename ValueType>
class TypeErasedIterator
: public boost::iterator_facade<TypeErasedIterator<ValueType>, ValueType,
boost::forward_traversal_tag> {
public:
template <typename Range>
TypeErasedIterator(Range &range, int flag)
: m_data{new Impl<Range>(range, flag)} {}
private:
friend class boost::iterator_core_access;
void increment() { m_data->increment(); };
bool equal(TypeErasedIterator<ValueType> const &other) const {
return pos() == other.pos();
};
auto &dereference() const { return *pos(); };
ValueType *begin() const { return m_data->begin(); }
ValueType *end() const { return m_data->end(); }
ValueType *pos() const { return m_data->pos(); }
struct Base {
virtual ValueType *begin() const = 0;
virtual ValueType *end() const = 0;
virtual ValueType *pos() const = 0;
virtual void increment() = 0;
virtual ~Base() = default;
};
private:
template <typename Range> struct Impl : Base {
Impl(Range &range, int flag)
: m_begin(range.begin()),
m_end(range.end()), m_pos{(flag == 1) ? range.begin() : range.end()} {
}
~Impl() override = default;
private:
typename Range::iterator m_begin;
typename Range::iterator m_end;
typename Range::iterator m_pos;
ValueType *pos() const override { return &*m_pos; };
ValueType *begin() const override { return &*m_begin; };
ValueType *end() const override { return &*m_end; };
void increment() override { ++m_pos; };
};
std::shared_ptr<Base> m_data;
};
template <typename T>
class TypeErasedRange : public boost::iterator_range<TypeErasedIterator<T>> {
using base_type = boost::iterator_range<TypeErasedIterator<T>>;
public:
using base_type::base_type;
typename base_type::size_type size() const {
return std::distance(base_type::begin(), base_type::end());
}
template <typename Range>
TypeErasedRange(Range &&range)
: boost::iterator_range<TypeErasedIterator<T>>{
TypeErasedIterator<T>(range, 1), TypeErasedIterator<T>(range, 2)} {}
};
template <typename T> void foor(T range);
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3};
std::ignore = boost::combine(vec, TypeErasedRange<int>(vec));
for (auto const val : TypeErasedRange<int>(vec)) {
std::cout << val << "\n";
}
} |
We could also pre-compute |
Continuation of #4721.
Description of changes:
ParticleRanges
out of function signatures and parsingcombined_ranges
instead.charge_assign
I had to move the definition to the.hpp
file to avoid specifying the type of theseboost
ranges.kernel
method inp3m.hpp
that probably was forgotten during some refactoringMoving the construction of the new boost ranges would require to change the signature of
long_range_kernel
, which would require to touchlong_range_forces
, which would require bigger refactoring.Through discussions with Rudolf the idea of an
P3MImpl
class to abstract implementation from the espresso implementation, as well as a base class that contains these ranges came up. I am not sure if this should be part of the PR, what do you think @jngrad?