From a40b3c5c7481bfe4618e2ec3969f5c1020740d44 Mon Sep 17 00:00:00 2001
From: Sebastian Eibl <sebastian.eibl@fau.de>
Date: Mon, 17 Jun 2019 16:34:28 +0200
Subject: [PATCH] added particle sorting to ParticleStorage

---
 .../templates/data/ParticleStorage.templ.h    |  56 +++++-
 .../HeatFluxNotification.templ.h              |   7 +
 src/mesa_pd/data/ParticleStorage.h            |  74 ++++++-
 .../mpi/notifications/HeatFluxNotification.h  |   7 +
 src/mesa_pd/sorting/HilbertCompareFunctor.cpp | 187 ++++++++++++++++++
 src/mesa_pd/sorting/HilbertCompareFunctor.h   |  72 +++++++
 .../sorting/LinearizedCompareFunctor.cpp      |  72 +++++++
 .../sorting/LinearizedCompareFunctor.h        |  71 +++++++
 src/mesa_pd/vtk/ParticleVtkOutput.cpp         |   4 +-
 tests/mesa_pd/CMakeLists.txt                  |   3 +
 tests/mesa_pd/Sorting.cpp                     |  89 +++++++++
 11 files changed, 636 insertions(+), 6 deletions(-)
 create mode 100644 src/mesa_pd/sorting/HilbertCompareFunctor.cpp
 create mode 100644 src/mesa_pd/sorting/HilbertCompareFunctor.h
 create mode 100644 src/mesa_pd/sorting/LinearizedCompareFunctor.cpp
 create mode 100644 src/mesa_pd/sorting/LinearizedCompareFunctor.h
 create mode 100644 tests/mesa_pd/Sorting.cpp

diff --git a/python/mesa_pd/templates/data/ParticleStorage.templ.h b/python/mesa_pd/templates/data/ParticleStorage.templ.h
index 7d6b1fa4b..c62e04564 100644
--- a/python/mesa_pd/templates/data/ParticleStorage.templ.h
+++ b/python/mesa_pd/templates/data/ParticleStorage.templ.h
@@ -133,7 +133,7 @@ public:
 
    iterator begin() { return iterator(this, 0); }
    iterator end()   { return iterator(this, size()); }
-   iterator operator[](const size_t n) { return iterator(this, n); }
+   Particle operator[](const size_t n) { return *iterator(this, n); }
 
    {% for prop in properties %}
    const {{prop.type}}& get{{prop.name | capFirst}}(const size_t idx) const {return {{prop.name}}_[idx];}
@@ -159,6 +159,8 @@ public:
    inline void reserve(const size_t size);
    inline void clear();
    inline size_t size() const;
+   template <class Compare>
+   void sort(Compare comp);
 
    /**
     * Calls the provided functor \p func for all Particles.
@@ -256,6 +258,15 @@ ParticleStorage::Particle& ParticleStorage::Particle::operator=(ParticleStorage:
    return *this;
 }
 
+inline
+void swap(ParticleStorage::Particle lhs, ParticleStorage::Particle rhs)
+{
+   if (lhs.i_ == rhs.i_) return;
+   {%- for prop in properties %}
+   std::swap(lhs.get{{prop.name | capFirst}}Ref(), rhs.get{{prop.name | capFirst}}Ref());
+   {%- endfor %}
+}
+
 inline
 std::ostream& operator<<( std::ostream& os, const ParticleStorage::Particle& p )
 {
@@ -392,7 +403,7 @@ inline ParticleStorage::iterator ParticleStorage::find(const id_t& uid)
    //use unordered_map for faster lookup
    auto it = uidToIdx_.find(uid);
    if (it == uidToIdx_.end()) return end();
-   WALBERLA_ASSERT_EQUAL(it->first, uid, "Lookup via uidToIdx map is not up to date!!!");
+   WALBERLA_ASSERT_EQUAL(getUid(it->second), uid, "Lookup via uidToIdx map is not up to date!!!");
    return iterator(this, it->second);
 }
 
@@ -419,6 +430,47 @@ inline size_t ParticleStorage::size() const
    return {{properties[0].name}}_.size();
 }
 
+template <class Compare>
+void ParticleStorage::sort(Compare comp)
+{
+   using WeightPair = std::pair<size_t, double>; //idx, weight
+
+   const size_t length = size();
+   std::vector<size_t>     newIdx(length); //where is old idx now?
+   std::vector<size_t>     oldIdx(length); //what old idx is at that idx?
+   std::vector<WeightPair> weight(length);
+
+   for (size_t idx = 0; idx < length; ++idx)
+   {
+      newIdx[idx] = idx;
+      oldIdx[idx] = idx;
+      weight[idx] = std::make_pair(idx, comp.getWeight(operator[](idx)));
+   }
+   std::sort(weight.begin(), weight.end(), [](const WeightPair& lhs, const WeightPair& rhs){return lhs.second < rhs.second;});
+   for (size_t idx = 0; idx < length; ++idx)
+   {
+      using std::swap;
+      WALBERLA_ASSERT_IDENTICAL(weight[idx].second, comp.getWeight(operator[](newIdx[weight[idx].first])));
+
+      WALBERLA_ASSERT_LESS_EQUAL(comp.getWeight(operator[](newIdx[weight[idx].first])), comp.getWeight(operator[](idx)));
+      swap( operator[](idx), operator[](newIdx[weight[idx].first]) );
+      const auto lhsIDX = idx;
+      const auto rhsIDX = newIdx[weight[idx].first];
+
+      newIdx[oldIdx[lhsIDX]] = rhsIDX;
+      newIdx[oldIdx[rhsIDX]] = lhsIDX;
+
+      swap(oldIdx[lhsIDX], oldIdx[rhsIDX]);
+   }
+
+   //rebuild lookup table
+   uidToIdx_.clear();
+   for (size_t idx = 0; idx < length; ++idx)
+   {
+      uidToIdx_[getUid(idx)] = idx;
+   }
+}
+
 {%- for const in ["", "const"] %}
 template <typename Selector, typename Accessor, typename Func, typename... Args>
 inline void ParticleStorage::forEachParticle(const bool openmp,
diff --git a/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h b/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h
index fa5caceda..ce463a214 100644
--- a/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h
+++ b/python/mesa_pd/templates/mpi/notifications/HeatFluxNotification.templ.h
@@ -29,6 +29,7 @@
 #include <mesa_pd/data/DataTypes.h>
 #include <mesa_pd/data/ParticleStorage.h>
 #include <mesa_pd/mpi/notifications/NotificationType.h>
+#include <mesa_pd/mpi/notifications/reset.h>
 
 #include <core/mpi/Datatype.h>
 #include <core/mpi/RecvBuffer.h>
@@ -54,6 +55,12 @@ public:
    const data::Particle& p_;
 };
 
+template <>
+void reset<HeatFluxNotification>(data::Particle& p)
+{
+   p.setHeatFlux( real_t(0) );
+}
+
 void reduce(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
 {
    p.getHeatFluxRef()  += objparam.heatFlux_;
diff --git a/src/mesa_pd/data/ParticleStorage.h b/src/mesa_pd/data/ParticleStorage.h
index e4d766318..aba34e3fe 100644
--- a/src/mesa_pd/data/ParticleStorage.h
+++ b/src/mesa_pd/data/ParticleStorage.h
@@ -223,7 +223,7 @@ public:
 
    iterator begin() { return iterator(this, 0); }
    iterator end()   { return iterator(this, size()); }
-   iterator operator[](const size_t n) { return iterator(this, n); }
+   Particle operator[](const size_t n) { return *iterator(this, n); }
 
    
    const walberla::id_t& getUid(const size_t idx) const {return uid_[idx];}
@@ -341,6 +341,8 @@ public:
    inline void reserve(const size_t size);
    inline void clear();
    inline size_t size() const;
+   template <class Compare>
+   void sort(Compare comp);
 
    /**
     * Calls the provided functor \p func for all Particles.
@@ -501,6 +503,33 @@ ParticleStorage::Particle& ParticleStorage::Particle::operator=(ParticleStorage:
    return *this;
 }
 
+inline
+void swap(ParticleStorage::Particle lhs, ParticleStorage::Particle rhs)
+{
+   if (lhs.i_ == rhs.i_) return;
+   std::swap(lhs.getUidRef(), rhs.getUidRef());
+   std::swap(lhs.getPositionRef(), rhs.getPositionRef());
+   std::swap(lhs.getInteractionRadiusRef(), rhs.getInteractionRadiusRef());
+   std::swap(lhs.getFlagsRef(), rhs.getFlagsRef());
+   std::swap(lhs.getOwnerRef(), rhs.getOwnerRef());
+   std::swap(lhs.getGhostOwnersRef(), rhs.getGhostOwnersRef());
+   std::swap(lhs.getShapeIDRef(), rhs.getShapeIDRef());
+   std::swap(lhs.getRotationRef(), rhs.getRotationRef());
+   std::swap(lhs.getAngularVelocityRef(), rhs.getAngularVelocityRef());
+   std::swap(lhs.getTorqueRef(), rhs.getTorqueRef());
+   std::swap(lhs.getLinearVelocityRef(), rhs.getLinearVelocityRef());
+   std::swap(lhs.getInvMassRef(), rhs.getInvMassRef());
+   std::swap(lhs.getForceRef(), rhs.getForceRef());
+   std::swap(lhs.getOldForceRef(), rhs.getOldForceRef());
+   std::swap(lhs.getOldTorqueRef(), rhs.getOldTorqueRef());
+   std::swap(lhs.getTypeRef(), rhs.getTypeRef());
+   std::swap(lhs.getNextParticleRef(), rhs.getNextParticleRef());
+   std::swap(lhs.getOldContactHistoryRef(), rhs.getOldContactHistoryRef());
+   std::swap(lhs.getNewContactHistoryRef(), rhs.getNewContactHistoryRef());
+   std::swap(lhs.getTemperatureRef(), rhs.getTemperatureRef());
+   std::swap(lhs.getHeatFluxRef(), rhs.getHeatFluxRef());
+}
+
 inline
 std::ostream& operator<<( std::ostream& os, const ParticleStorage::Particle& p )
 {
@@ -700,7 +729,7 @@ inline ParticleStorage::iterator ParticleStorage::find(const id_t& uid)
    //use unordered_map for faster lookup
    auto it = uidToIdx_.find(uid);
    if (it == uidToIdx_.end()) return end();
-   WALBERLA_ASSERT_EQUAL(it->first, uid, "Lookup via uidToIdx map is not up to date!!!");
+   WALBERLA_ASSERT_EQUAL(getUid(it->second), uid, "Lookup via uidToIdx map is not up to date!!!");
    return iterator(this, it->second);
 }
 
@@ -789,6 +818,47 @@ inline size_t ParticleStorage::size() const
    //WALBERLA_ASSERT_EQUAL( uid_.size(), neighborState.size() );
    return uid_.size();
 }
+
+template <class Compare>
+void ParticleStorage::sort(Compare comp)
+{
+   using WeightPair = std::pair<size_t, double>; //idx, weight
+
+   const size_t length = size();
+   std::vector<size_t>     newIdx(length); //where is old idx now?
+   std::vector<size_t>     oldIdx(length); //what old idx is at that idx?
+   std::vector<WeightPair> weight(length);
+
+   for (size_t idx = 0; idx < length; ++idx)
+   {
+      newIdx[idx] = idx;
+      oldIdx[idx] = idx;
+      weight[idx] = std::make_pair(idx, comp.getWeight(operator[](idx)));
+   }
+   std::sort(weight.begin(), weight.end(), [](const WeightPair& lhs, const WeightPair& rhs){return lhs.second < rhs.second;});
+   for (size_t idx = 0; idx < length; ++idx)
+   {
+      using std::swap;
+      WALBERLA_ASSERT_IDENTICAL(weight[idx].second, comp.getWeight(operator[](newIdx[weight[idx].first])));
+
+      WALBERLA_ASSERT_LESS_EQUAL(comp.getWeight(operator[](newIdx[weight[idx].first])), comp.getWeight(operator[](idx)));
+      swap( operator[](idx), operator[](newIdx[weight[idx].first]) );
+      const auto lhsIDX = idx;
+      const auto rhsIDX = newIdx[weight[idx].first];
+
+      newIdx[oldIdx[lhsIDX]] = rhsIDX;
+      newIdx[oldIdx[rhsIDX]] = lhsIDX;
+
+      swap(oldIdx[lhsIDX], oldIdx[rhsIDX]);
+   }
+
+   //rebuild lookup table
+   uidToIdx_.clear();
+   for (size_t idx = 0; idx < length; ++idx)
+   {
+      uidToIdx_[getUid(idx)] = idx;
+   }
+}
 template <typename Selector, typename Accessor, typename Func, typename... Args>
 inline void ParticleStorage::forEachParticle(const bool openmp,
                                              const Selector& selector,
diff --git a/src/mesa_pd/mpi/notifications/HeatFluxNotification.h b/src/mesa_pd/mpi/notifications/HeatFluxNotification.h
index 6fee2c541..884a684e9 100644
--- a/src/mesa_pd/mpi/notifications/HeatFluxNotification.h
+++ b/src/mesa_pd/mpi/notifications/HeatFluxNotification.h
@@ -29,6 +29,7 @@
 #include <mesa_pd/data/DataTypes.h>
 #include <mesa_pd/data/ParticleStorage.h>
 #include <mesa_pd/mpi/notifications/NotificationType.h>
+#include <mesa_pd/mpi/notifications/reset.h>
 
 #include <core/mpi/Datatype.h>
 #include <core/mpi/RecvBuffer.h>
@@ -54,6 +55,12 @@ public:
    const data::Particle& p_;
 };
 
+template <>
+void reset<HeatFluxNotification>(data::Particle& p)
+{
+   p.setHeatFlux( real_t(0) );
+}
+
 void reduce(data::Particle&& p, const HeatFluxNotification::Parameters& objparam)
 {
    p.getHeatFluxRef()  += objparam.heatFlux_;
diff --git a/src/mesa_pd/sorting/HilbertCompareFunctor.cpp b/src/mesa_pd/sorting/HilbertCompareFunctor.cpp
new file mode 100644
index 000000000..2cf2a8466
--- /dev/null
+++ b/src/mesa_pd/sorting/HilbertCompareFunctor.cpp
@@ -0,0 +1,187 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla 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.
+//
+//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file HilbertCompareFunctor.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include "HilbertCompareFunctor.h"
+
+#include <blockforest/HilbertCurveConstruction.h>
+#include <core/DataTypes.h>
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+#include <core/math/Vector3.h>
+#include <core/mpi/MPIManager.h>
+
+#include <stack>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace sorting {
+
+math::Vector3<uint_t> getChildPosition(const uint_t currentSize, const math::Vector3<uint_t>& parent, const uint_t childId)
+{
+   auto size = currentSize >> 1;
+   auto child = parent;
+
+   if (!(childId & 0b001))
+   {
+      child[0] -= size;
+   }
+
+   if (!(childId & 0b010))
+   {
+      child[1] -= size;
+   }
+
+   if (!(childId & 0b100))
+   {
+      child[2] -= size;
+   }
+
+   return child;
+}
+
+std::vector< math::Vector3<uint_t> > generateHilbertCurve(const uint_t size)
+{
+   using Node = std::pair<uint_t, Vector3<uint_t>>; //level, coordinate
+
+   std::vector< math::Vector3<uint_t> > result;
+
+   std::stack< Node > stack;
+   std::stack< uint_t > orientation;
+
+   stack.push( std::make_pair(size, math::Vector3<uint_t>(size)) );
+   orientation.push( uint_t(0) );
+
+   while( !stack.empty() )
+   {
+      const Node node = stack.top();
+      uint_t hilbertIndex = orientation.top();
+
+      stack.pop();
+      orientation.pop();
+
+      if( node.first > 1 )
+      {
+         for( uint_t c = 8; c-- != 0; )
+         {
+            stack.push( std::make_pair( node.first >> 1,
+                                        getChildPosition(node.first, node.second, blockforest::hilbertOrder[hilbertIndex][c]) ));
+            orientation.push( blockforest::hilbertOrientation[hilbertIndex][c] );
+         }
+      }
+      else
+      {
+         result.push_back( node.second );
+      }
+   }
+   return result;
+}
+
+
+HilbertCompareFunctor::HilbertCompareFunctor(const math::AABB& domain, const uint_t cells)
+   : domain_(domain)
+   , cells_(cells)
+   , hilbertLookup_(cells_ * cells_ * cells_)
+{
+   WALBERLA_CHECK(math::uintIsPowerOfTwo(cells));
+   inverse_dx[0] = real_t(1.0) / (domain_.xSize() / real_c(cells_));
+   inverse_dx[1] = real_t(1.0) / (domain_.ySize() / real_c(cells_));
+   inverse_dx[2] = real_t(1.0) / (domain_.zSize() / real_c(cells_));
+   initializeLookup();
+}
+
+bool HilbertCompareFunctor::operator()(const data::Particle p1, const data::Particle p2) const
+{
+   const auto hash1 = discretize(p1.getPosition() - domain_.minCorner());
+   WALBERLA_ASSERT_LESS( hash1, cells_*cells_*cells_);
+   const auto hash2 = discretize(p2.getPosition() - domain_.minCorner());
+   WALBERLA_ASSERT_LESS( hash2, cells_*cells_*cells_);
+
+   return hilbertLookup_[hash1] < hilbertLookup_[hash2];
+}
+
+uint_t HilbertCompareFunctor::discretize(const Vec3& pos) const
+{
+   const real_t& x = pos[0];
+   const real_t& y = pos[1];
+   const real_t& z = pos[2];
+
+   const uint_t hashMask = (cells_ - 1);
+
+   math::Vector3<uint_t> hash;
+
+   if( x < 0 ) {
+      real_t i = ( -x ) * inverse_dx[0];
+      hash[0]  = cells_ - 1 - ( static_cast<uint_t>( i ) & hashMask );
+   }
+   else {
+      real_t i = x * inverse_dx[0];
+      hash[0]  = static_cast<uint_t>( i ) & hashMask;
+   }
+
+   if( y < 0 ) {
+      real_t i = ( -y ) * inverse_dx[1];
+      hash[1]  = cells_ - 1 - ( static_cast<uint_t>( i ) & hashMask );
+   }
+   else {
+      real_t i = y * inverse_dx[1];
+      hash[1]  = static_cast<uint_t>( i ) & hashMask;
+   }
+
+   if( z < 0 ) {
+      real_t i = ( -z ) * inverse_dx[2];
+      hash[2]  = cells_ - 1 - ( static_cast<uint_t>( i ) & hashMask );
+   }
+   else {
+      real_t i = z * inverse_dx[2];
+      hash[2]  = static_cast<uint_t>( i ) & hashMask;
+   }
+
+   WALBERLA_ASSERT_LESS(hash[0], cells_);
+   WALBERLA_ASSERT_LESS(hash[1], cells_);
+   WALBERLA_ASSERT_LESS(hash[2], cells_);
+
+   return hash[2] * cells_ * cells_ + hash[1] * cells_ + hash[0];
+}
+
+
+void HilbertCompareFunctor::initializeLookup()
+{
+   const auto sfc = generateHilbertCurve(cells_);
+   uint_t counter = 0;
+   for (auto p : sfc)
+   {
+      WALBERLA_ASSERT_GREATER(p[0], 0);
+      WALBERLA_ASSERT_GREATER(p[1], 0);
+      WALBERLA_ASSERT_GREATER(p[2], 0);
+      p[0] -= 1;
+      p[1] -= 1;
+      p[2] -= 1;
+
+      hilbertLookup_[p[2] * cells_ * cells_ + p[1] * cells_ + p[0]] = counter;
+      ++counter;
+   }
+   WALBERLA_ASSERT_EQUAL(counter, sfc.size());
+   WALBERLA_ASSERT_EQUAL(counter, hilbertLookup_.size());
+}
+
+} //namespace sorting
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/sorting/HilbertCompareFunctor.h b/src/mesa_pd/sorting/HilbertCompareFunctor.h
new file mode 100644
index 000000000..70e91665f
--- /dev/null
+++ b/src/mesa_pd/sorting/HilbertCompareFunctor.h
@@ -0,0 +1,72 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla 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.
+//
+//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file HilbertCompareFunctor.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <core/math/AABB.h>
+#include <core/math/Vector3.h>
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace sorting {
+
+/**
+ * Compare functor which sorts particles according to the hilbert space filling curve.
+ */
+class HilbertCompareFunctor
+{
+public:
+   /**
+    * Subdivides a domain into cells and arranges the cells according to the sfc.
+    * The position within the sfc gives the particles their weight.
+    * \param domain domain which should be partitioned into cells
+    * \param cells number of cells in every direction.
+    * \attention cells has to be a power of 2!
+    */
+   HilbertCompareFunctor(const math::AABB& domain, const uint_t cells);
+   bool operator()(const data::Particle bd1, const data::Particle bd2) const;
+   double getWeight(const data::Particle p1) const;
+private:
+   uint_t discretize(const Vec3& pos) const;
+   void initializeLookup();
+
+   math::AABB  domain_;
+   const uint_t cells_;
+   Vec3 inverse_dx;
+   std::vector<uint_t> hilbertLookup_;
+};
+
+inline
+double HilbertCompareFunctor::getWeight(const data::Particle p1) const
+{
+   const auto hash1 = discretize(p1.getPosition() - domain_.minCorner());
+   WALBERLA_ASSERT_LESS( hash1, cells_*cells_*cells_);
+
+   return double_c(hilbertLookup_[hash1]);
+}
+
+} //namespace sorting
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/sorting/LinearizedCompareFunctor.cpp b/src/mesa_pd/sorting/LinearizedCompareFunctor.cpp
new file mode 100644
index 000000000..7588285a4
--- /dev/null
+++ b/src/mesa_pd/sorting/LinearizedCompareFunctor.cpp
@@ -0,0 +1,72 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla 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.
+//
+//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file LinearizedCompareFunctor.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include "LinearizedCompareFunctor.h"
+
+#include <core/DataTypes.h>
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+#include <core/math/Vector3.h>
+#include <core/mpi/MPIManager.h>
+
+#include <stack>
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace sorting {
+
+LinearizedCompareFunctor::LinearizedCompareFunctor(const math::AABB& domain, const Vector3<uint_t> cells)
+   : domain_(domain)
+   , cells_(cells)
+{
+   inverse_dx[0] = real_c(cells_[0]) / domain_.xSize();
+   inverse_dx[1] = real_c(cells_[1]) / domain_.ySize();
+   inverse_dx[2] = real_c(cells_[2]) / domain_.zSize();
+}
+
+bool LinearizedCompareFunctor::operator()(const data::Particle p1, const data::Particle p2) const
+{
+   const auto hash1 = discretize(p1.getPosition() - domain_.minCorner());
+   WALBERLA_ASSERT_LESS( hash1, cells_[0]*cells_[1]*cells_[2]);
+   const auto hash2 = discretize(p2.getPosition() - domain_.minCorner());
+   WALBERLA_ASSERT_LESS( hash2, cells_[0]*cells_[1]*cells_[2]);
+
+   return hash1 < hash2;
+}
+
+uint_t LinearizedCompareFunctor::discretize(const Vec3& pos) const
+{
+   int x = int_c(pos[0] * inverse_dx[0]);
+   int y = int_c(pos[1] * inverse_dx[1]);
+   int z = int_c(pos[2] * inverse_dx[2]);
+   if (x<0) x=0;
+   if (y<0) y=0;
+   if (z<0) z=0;
+   if (x>=int_c(cells_[0])) x=int_c(cells_[0])-1;
+   if (y>=int_c(cells_[1])) y=int_c(cells_[1])-1;
+   if (z>=int_c(cells_[2])) z=int_c(cells_[2])-1;
+
+   return uint_c(z) * cells_[1] * cells_[0] + uint_c(y) * cells_[0] + uint_c(x);
+}
+
+} //namespace sorting
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/sorting/LinearizedCompareFunctor.h b/src/mesa_pd/sorting/LinearizedCompareFunctor.h
new file mode 100644
index 000000000..9caf02bd0
--- /dev/null
+++ b/src/mesa_pd/sorting/LinearizedCompareFunctor.h
@@ -0,0 +1,71 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla 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.
+//
+//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file LinearizedCompareFunctor.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <core/math/AABB.h>
+#include <core/math/Vector3.h>
+
+#include <mesa_pd/data/DataTypes.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <vector>
+
+namespace walberla {
+namespace mesa_pd {
+namespace sorting {
+
+/**
+ * Compare functor which sorts particles along the x, y and z axes.
+ */
+class LinearizedCompareFunctor
+{
+public:
+   /**
+    * Subdivides a domain into cells and arranges the cells in a linearized fashion.
+    * The position within the linearization gives the particles their weight.
+    * \param domain domain which should be partitioned into cells
+    * \param cells number of cells in every direction.
+    */
+   LinearizedCompareFunctor(const math::AABB& domain, const Vector3<uint_t> cells);
+   bool operator()(const data::Particle bd1, const data::Particle bd2) const;
+   double getWeight(const data::Particle p1) const;
+private:
+   uint_t discretize(const Vec3& pos) const;
+   void initializeLookup();
+
+   math::AABB  domain_;
+   const Vector3<uint_t> cells_;
+   Vec3 inverse_dx;
+   std::vector<uint_t> hilbertLookup_;
+};
+
+inline
+double LinearizedCompareFunctor::getWeight(const data::Particle p1) const
+{
+   const auto hash = discretize(p1.getPosition() - domain_.minCorner());
+   WALBERLA_ASSERT_LESS( hash, cells_[0]*cells_[1]*cells_[2]);
+
+   return double_c(hash);
+}
+
+} //namespace sorting
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/vtk/ParticleVtkOutput.cpp b/src/mesa_pd/vtk/ParticleVtkOutput.cpp
index 0210b4e29..b63295893 100644
--- a/src/mesa_pd/vtk/ParticleVtkOutput.cpp
+++ b/src/mesa_pd/vtk/ParticleVtkOutput.cpp
@@ -60,7 +60,7 @@ void ParticleVtkOutput::push( std::ostream& os, const uint_t data, const uint_t
    WALBERLA_ASSERT_LESS( point, particleIndices_.size() );
    WALBERLA_ASSERT_LESS( particleIndices_[point], ps_->size() );
 
-   selectors_[data].second->push(os, *(*ps_)[particleIndices_[point]], component);
+   selectors_[data].second->push(os, (*ps_)[particleIndices_[point]], component);
 }
 
 void ParticleVtkOutput::push( walberla::vtk::Base64Writer& b64, const uint_t data, const uint_t point, const uint_t component )
@@ -69,7 +69,7 @@ void ParticleVtkOutput::push( walberla::vtk::Base64Writer& b64, const uint_t dat
    WALBERLA_ASSERT_LESS( point, particleIndices_.size() );
    WALBERLA_ASSERT_LESS( particleIndices_[point], ps_->size() );
 
-   selectors_[data].second->push(b64, *(*ps_)[particleIndices_[point]], component);
+   selectors_[data].second->push(b64, (*ps_)[particleIndices_[point]], component);
 }
 
 void ParticleVtkOutput::addOutput(const std::string& name, std::shared_ptr<IOutputSelector> selector)
diff --git a/tests/mesa_pd/CMakeLists.txt b/tests/mesa_pd/CMakeLists.txt
index ae8501523..1938034f1 100644
--- a/tests/mesa_pd/CMakeLists.txt
+++ b/tests/mesa_pd/CMakeLists.txt
@@ -128,6 +128,9 @@ waLBerla_execute_test( NAME   MESA_PD_MPI_ReduceContactHistory PROCESSES 8 )
 waLBerla_compile_test( NAME   MESA_PD_MPI_ReduceProperty FILES mpi/ReduceProperty.cpp DEPENDS core )
 waLBerla_execute_test( NAME   MESA_PD_MPI_ReduceProperty PROCESSES 8 )
 
+waLBerla_compile_test( NAME   MESA_PD_Sorting FILES Sorting.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_Sorting )
+
 waLBerla_compile_test( NAME   MESA_PD_VTK_Outputs FILES vtk/VTKOutputs.cpp DEPENDS blockforest core vtk )
 waLBerla_execute_test( NAME   MESA_PD_VTK_Outputs PROCESSES 8 )
 
diff --git a/tests/mesa_pd/Sorting.cpp b/tests/mesa_pd/Sorting.cpp
new file mode 100644
index 000000000..d4408cd71
--- /dev/null
+++ b/tests/mesa_pd/Sorting.cpp
@@ -0,0 +1,89 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla 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.
+//
+//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   Sorting.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/sorting/HilbertCompareFunctor.h>
+#include <mesa_pd/sorting/LinearizedCompareFunctor.h>
+#include <mesa_pd/data/ParticleStorage.h>
+
+#include <core/Abort.h>
+#include <core/Environment.h>
+#include <core/math/Random.h>
+#include <core/grid_generator/SCIterator.h>
+#include <core/logging/Logging.h>
+
+namespace walberla {
+namespace mesa_pd {
+
+int main()
+{
+   auto ps = std::make_shared<data::ParticleStorage>(100);
+
+   const math::AABB domain(real_t(0), real_t(0), real_t(0),
+                           real_t(2), real_t(2), real_t(2));
+   for (auto pt : grid_generator::SCGrid(domain, Vec3(real_c(0.5)), real_t(1)))
+   {
+      auto p                       = ps->create();
+      p->getPositionRef()          = pt;
+   }
+
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[0].getPosition(), Vec3(real_t(0.5), real_t(0.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[1].getPosition(), Vec3(real_t(1.5), real_t(0.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[2].getPosition(), Vec3(real_t(0.5), real_t(1.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[3].getPosition(), Vec3(real_t(1.5), real_t(1.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[4].getPosition(), Vec3(real_t(0.5), real_t(0.5), real_t(1.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[5].getPosition(), Vec3(real_t(1.5), real_t(0.5), real_t(1.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[6].getPosition(), Vec3(real_t(0.5), real_t(1.5), real_t(1.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[7].getPosition(), Vec3(real_t(1.5), real_t(1.5), real_t(1.5)));
+
+   sorting::HilbertCompareFunctor hilbert(domain, 2);
+   ps->sort(hilbert);
+
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[0].getPosition(), Vec3(real_t(0.5), real_t(0.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[1].getPosition(), Vec3(real_t(1.5), real_t(0.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[2].getPosition(), Vec3(real_t(1.5), real_t(1.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[3].getPosition(), Vec3(real_t(0.5), real_t(1.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[4].getPosition(), Vec3(real_t(0.5), real_t(1.5), real_t(1.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[5].getPosition(), Vec3(real_t(1.5), real_t(1.5), real_t(1.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[6].getPosition(), Vec3(real_t(1.5), real_t(0.5), real_t(1.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[7].getPosition(), Vec3(real_t(0.5), real_t(0.5), real_t(1.5)));
+
+   sorting::LinearizedCompareFunctor linear(domain, Vector3<uint_t>(2,2,2));
+   ps->sort(linear);
+
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[0].getPosition(), Vec3(real_t(0.5), real_t(0.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[1].getPosition(), Vec3(real_t(1.5), real_t(0.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[2].getPosition(), Vec3(real_t(0.5), real_t(1.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[3].getPosition(), Vec3(real_t(1.5), real_t(1.5), real_t(0.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[4].getPosition(), Vec3(real_t(0.5), real_t(0.5), real_t(1.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[5].getPosition(), Vec3(real_t(1.5), real_t(0.5), real_t(1.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[6].getPosition(), Vec3(real_t(0.5), real_t(1.5), real_t(1.5)));
+   WALBERLA_CHECK_FLOAT_EQUAL((*ps)[7].getPosition(), Vec3(real_t(1.5), real_t(1.5), real_t(1.5)));
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace mesa_pd
+} // namespace walberla
+
+int main( int argc, char* argv[] )
+{
+   walberla::Environment env(argc, argv);
+   return walberla::mesa_pd::main();
+}
-- 
GitLab