Skip to content
Snippets Groups Projects
Commit 5c456a60 authored by Sebastian Eibl's avatar Sebastian Eibl
Browse files

made DynamicCurve balance levelwise optional

parent cb431e5e
Branches
No related merge requests found
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#pragma once #pragma once
#include "NoPhantomData.h" #include "NoPhantomData.h"
#include "blockforest/BlockForest.h"
#include "blockforest/HilbertCurveConstruction.h" #include "blockforest/HilbertCurveConstruction.h"
#include "blockforest/PhantomBlockForest.h" #include "blockforest/PhantomBlockForest.h"
...@@ -88,9 +89,14 @@ struct Node ...@@ -88,9 +89,14 @@ struct Node
} // namespace internal } // namespace internal
/**
* This class implements Hilber and Morton space filling curves for load balancing.
*
* All algorithms are implemented to work levelwise. Load balancing with levels ignored is possible
* by specifying levelwise = false in the constructor.
**/
template< typename PhantomData_T > template< typename PhantomData_T >
class DynamicLevelwiseCurveBalance class DynamicCurveBalance
{ {
public: public:
...@@ -99,8 +105,8 @@ public: ...@@ -99,8 +105,8 @@ public:
typedef uint16_t idx_t; // limits the maximum number of blocks per process to 65536 typedef uint16_t idx_t; // limits the maximum number of blocks per process to 65536
typedef internal::Node< pid_t, idx_t > Node; typedef internal::Node< pid_t, idx_t > Node;
DynamicLevelwiseCurveBalance( const bool hilbert = true, const bool allGather = true ) : DynamicCurveBalance( const bool hilbert = true, const bool allGather = true, const bool levelwise = true ) :
hilbert_( hilbert ), allGather_( allGather ) hilbert_( hilbert ), allGather_( allGather ), levelwise_(levelwise)
{} {}
bool operator()( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess, bool operator()( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess,
...@@ -180,12 +186,17 @@ private: ...@@ -180,12 +186,17 @@ private:
bool hilbert_; bool hilbert_;
bool allGather_; bool allGather_;
/// All gets for levels are wrapped like
/// \code levelwise_ ? getCorrectLevel() : 0
///
/// This allows to use the same algorithm for levelwise balancing as well as for balancing without levels.
bool levelwise_;
}; };
template< typename PhantomData_T > template< typename PhantomData_T >
bool DynamicLevelwiseCurveBalance< PhantomData_T >::operator()( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess, bool DynamicCurveBalance< PhantomData_T >::operator()( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess,
std::set< uint_t > & processesToRecvFrom, std::set< uint_t > & processesToRecvFrom,
const PhantomBlockForest & phantomForest, const uint_t ) const const PhantomBlockForest & phantomForest, const uint_t ) const
{ {
...@@ -214,7 +225,7 @@ bool DynamicLevelwiseCurveBalance< PhantomData_T >::operator()( std::vector< std ...@@ -214,7 +225,7 @@ bool DynamicLevelwiseCurveBalance< PhantomData_T >::operator()( std::vector< std
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherWeighted( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess, void DynamicCurveBalance< PhantomData_T >::allGatherWeighted( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess,
std::set< uint_t > & processesToRecvFrom, std::set< uint_t > & processesToRecvFrom,
const PhantomBlockForest & phantomForest ) const const PhantomBlockForest & phantomForest ) const
{ {
...@@ -241,7 +252,8 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherWeighted( std::vect ...@@ -241,7 +252,8 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherWeighted( std::vect
recvBuffer >> allBlocks[p]; recvBuffer >> allBlocks[p];
recvBuffer.reset(); recvBuffer.reset();
std::vector< std::vector< std::pair< pid_t, idx_t > > > blocksPerLevel( phantomForest.getNumberOfLevels() ); // for every level one vector of pair(source process ID, index in 'allBlocks') const uint_t numLevels = levelwise_ ? phantomForest.getNumberOfLevels() : uint_t(1);
std::vector< std::vector< std::pair< pid_t, idx_t > > > blocksPerLevel( numLevels ); // for every level one vector of pair(source process ID, index in 'allBlocks')
if( hilbert_ ) if( hilbert_ )
hilbertOrderWeighted( allBlocks, blocksPerLevel, phantomForest ); hilbertOrderWeighted( allBlocks, blocksPerLevel, phantomForest );
...@@ -259,7 +271,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherWeighted( std::vect ...@@ -259,7 +271,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherWeighted( std::vect
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherNoWeight( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess, void DynamicCurveBalance< PhantomData_T >::allGatherNoWeight( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess,
std::set< uint_t > & processesToRecvFrom, std::set< uint_t > & processesToRecvFrom,
const PhantomBlockForest & phantomForest ) const const PhantomBlockForest & phantomForest ) const
{ {
...@@ -283,7 +295,8 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherNoWeight( std::vect ...@@ -283,7 +295,8 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherNoWeight( std::vect
recvBuffer >> allBlocks[p]; recvBuffer >> allBlocks[p];
recvBuffer.reset(); recvBuffer.reset();
std::vector< std::vector< std::pair< pid_t, idx_t > > > blocksPerLevel( phantomForest.getNumberOfLevels() ); // for every level one vector of pair(source process ID, index in 'allBlocks') const uint_t numLevels = levelwise_ ? phantomForest.getNumberOfLevels() : uint_t(1);
std::vector< std::vector< std::pair< pid_t, idx_t > > > blocksPerLevel( numLevels ); // for every level one vector of pair(source process ID, index in 'allBlocks')
if( hilbert_ ) if( hilbert_ )
hilbertOrderNoWeight( allBlocks, blocksPerLevel, phantomForest ); hilbertOrderNoWeight( allBlocks, blocksPerLevel, phantomForest );
...@@ -301,7 +314,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherNoWeight( std::vect ...@@ -301,7 +314,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::allGatherNoWeight( std::vect
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::masterWeighted( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess, void DynamicCurveBalance< PhantomData_T >::masterWeighted( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess,
std::set< uint_t > & processesToRecvFrom, std::set< uint_t > & processesToRecvFrom,
const PhantomBlockForest & phantomForest ) const const PhantomBlockForest & phantomForest ) const
{ {
...@@ -352,7 +365,8 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterWeighted( std::vector< ...@@ -352,7 +365,8 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterWeighted( std::vector<
WALBERLA_ASSERT( allBlocks[0].empty() ); WALBERLA_ASSERT( allBlocks[0].empty() );
allBlocks[0] = localBlocks; allBlocks[0] = localBlocks;
blocksPerLevel.resize( phantomForest.getNumberOfLevels() ); const uint_t numLevels = levelwise_ ? phantomForest.getNumberOfLevels() : uint_t(1);
blocksPerLevel.resize( numLevels );
if( hilbert_ ) if( hilbert_ )
hilbertOrderWeighted( allBlocks, blocksPerLevel, phantomForest ); hilbertOrderWeighted( allBlocks, blocksPerLevel, phantomForest );
...@@ -377,7 +391,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterWeighted( std::vector< ...@@ -377,7 +391,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterWeighted( std::vector<
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::masterNoWeight( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess, void DynamicCurveBalance< PhantomData_T >::masterNoWeight( std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess,
std::set< uint_t > & processesToRecvFrom, std::set< uint_t > & processesToRecvFrom,
const PhantomBlockForest & phantomForest ) const const PhantomBlockForest & phantomForest ) const
{ {
...@@ -425,7 +439,8 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterNoWeight( std::vector< ...@@ -425,7 +439,8 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterNoWeight( std::vector<
WALBERLA_ASSERT( allBlocks[0].empty() ); WALBERLA_ASSERT( allBlocks[0].empty() );
allBlocks[0] = localBlocks; allBlocks[0] = localBlocks;
blocksPerLevel.resize( phantomForest.getNumberOfLevels() ); const uint_t numLevels = levelwise_ ? phantomForest.getNumberOfLevels() : uint_t(1);
blocksPerLevel.resize( numLevels );
if( hilbert_ ) if( hilbert_ )
hilbertOrderNoWeight( allBlocks, blocksPerLevel, phantomForest ); hilbertOrderNoWeight( allBlocks, blocksPerLevel, phantomForest );
...@@ -450,9 +465,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterNoWeight( std::vector< ...@@ -450,9 +465,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterNoWeight( std::vector<
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderWeighted( const std::vector< std::vector< std::pair< BlockID, typename PhantomData_T::weight_t > > > & allBlocks, void DynamicCurveBalance< PhantomData_T >::hilbertOrderWeighted( const std::vector< std::vector< std::pair< BlockID, typename PhantomData_T::weight_t > > > & allBlocks,
std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel, std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel,
const PhantomBlockForest & phantomForest ) const const PhantomBlockForest & phantomForest) const
{ {
// construct forest of octrees // construct forest of octrees
...@@ -519,8 +534,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderWeighted( const ...@@ -519,8 +534,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderWeighted( const
else else
{ {
auto & index = node->index_; auto & index = node->index_;
WALBERLA_ASSERT_LESS( blockforest.getLevelFromBlockId( allBlocks[ uint_c(index.first) ][ index.second ].first ), phantomForest.getNumberOfLevels() ); const uint_t level = levelwise_ ? blockforest.getLevelFromBlockId( allBlocks[ uint_c(index.first) ][ index.second ].first ) : uint_t(0);
blocksPerLevel[ blockforest.getLevelFromBlockId( allBlocks[ uint_c(index.first) ][ index.second ].first ) ].push_back( index ); WALBERLA_ASSERT_LESS( level, levelwise_ ? phantomForest.getNumberOfLevels() : uint_t(1) );
blocksPerLevel[ level ].push_back( index );
} }
} }
} }
...@@ -539,9 +555,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderWeighted( const ...@@ -539,9 +555,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderWeighted( const
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderNoWeight( const std::vector< std::vector< BlockID > > & allBlocks, void DynamicCurveBalance< PhantomData_T >::hilbertOrderNoWeight( const std::vector< std::vector< BlockID > > & allBlocks,
std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel, std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel,
const PhantomBlockForest & phantomForest ) const const PhantomBlockForest & phantomForest) const
{ {
// construct forest of octrees // construct forest of octrees
...@@ -608,8 +624,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderNoWeight( const ...@@ -608,8 +624,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderNoWeight( const
else else
{ {
auto & index = node->index_; auto & index = node->index_;
WALBERLA_ASSERT_LESS( blockforest.getLevelFromBlockId( allBlocks[ uint_c(index.first) ][ index.second ] ), phantomForest.getNumberOfLevels() ); const uint_t level = levelwise_ ? blockforest.getLevelFromBlockId( allBlocks[ uint_c(index.first) ][ index.second ] ) : uint_t(0);
blocksPerLevel[ blockforest.getLevelFromBlockId( allBlocks[ uint_c(index.first) ][ index.second ] ) ].push_back( index ); WALBERLA_ASSERT_LESS( level , levelwise_ ? phantomForest.getNumberOfLevels() : uint_t(1) );
blocksPerLevel[ level ].push_back( index );
} }
} }
} }
...@@ -628,7 +645,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderNoWeight( const ...@@ -628,7 +645,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::hilbertOrderNoWeight( const
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::addBlockToForest( std::vector< shared_ptr< Node > > & forest, void DynamicCurveBalance< PhantomData_T >::addBlockToForest( std::vector< shared_ptr< Node > > & forest,
const std::pair< pid_t, idx_t > & index, BlockID & id, const uint_t level ) const const std::pair< pid_t, idx_t > & index, BlockID & id, const uint_t level ) const
{ {
std::stack< uint_t > path; std::stack< uint_t > path;
...@@ -681,9 +698,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::addBlockToForest( std::vecto ...@@ -681,9 +698,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::addBlockToForest( std::vecto
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderWeighted( const std::vector< std::vector< std::pair< BlockID, typename PhantomData_T::weight_t > > > & allBlocks, void DynamicCurveBalance< PhantomData_T >::mortonOrderWeighted( const std::vector< std::vector< std::pair< BlockID, typename PhantomData_T::weight_t > > > & allBlocks,
std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel, std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel,
const PhantomBlockForest & phantomForest ) const const PhantomBlockForest & phantomForest) const
{ {
const uint_t processes = uint_c( mpi::MPIManager::instance()->numProcesses() ); const uint_t processes = uint_c( mpi::MPIManager::instance()->numProcesses() );
...@@ -691,8 +708,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderWeighted( const s ...@@ -691,8 +708,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderWeighted( const s
{ {
for( uint_t i = uint_t(0); i != allBlocks[p].size(); ++i ) for( uint_t i = uint_t(0); i != allBlocks[p].size(); ++i )
{ {
WALBERLA_ASSERT_LESS( phantomForest.getBlockForest().getLevelFromBlockId( allBlocks[p][i].first ), blocksPerLevel.size() ); uint_t level = levelwise_ ? phantomForest.getBlockForest().getLevelFromBlockId( allBlocks[p][i].first ) : uint_t(0);
blocksPerLevel[ phantomForest.getBlockForest().getLevelFromBlockId( allBlocks[p][i].first ) ].push_back( std::make_pair( pid_c(p), idx_c(i) ) ); WALBERLA_ASSERT_LESS( level, blocksPerLevel.size() );
blocksPerLevel[ level ].push_back( std::make_pair( pid_c(p), idx_c(i) ) );
} }
} }
...@@ -710,9 +728,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderWeighted( const s ...@@ -710,9 +728,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderWeighted( const s
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderNoWeight( const std::vector< std::vector< BlockID > > & allBlocks, void DynamicCurveBalance< PhantomData_T >::mortonOrderNoWeight( const std::vector< std::vector< BlockID > > & allBlocks,
std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel, std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel,
const PhantomBlockForest & phantomForest ) const const PhantomBlockForest & phantomForest) const
{ {
const uint_t processes = uint_c( mpi::MPIManager::instance()->numProcesses() ); const uint_t processes = uint_c( mpi::MPIManager::instance()->numProcesses() );
...@@ -720,8 +738,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderNoWeight( const s ...@@ -720,8 +738,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderNoWeight( const s
{ {
for( uint_t i = uint_t(0); i != allBlocks[p].size(); ++i ) for( uint_t i = uint_t(0); i != allBlocks[p].size(); ++i )
{ {
WALBERLA_ASSERT_LESS( phantomForest.getBlockForest().getLevelFromBlockId( allBlocks[p][i] ), blocksPerLevel.size() ); uint_t level = levelwise_ ? phantomForest.getBlockForest().getLevelFromBlockId( allBlocks[p][i] ) : uint_t(0);
blocksPerLevel[ phantomForest.getBlockForest().getLevelFromBlockId( allBlocks[p][i] ) ].push_back( std::make_pair( pid_c(p), idx_c(i) ) ); WALBERLA_ASSERT_LESS( level, blocksPerLevel.size() );
blocksPerLevel[ level ].push_back( std::make_pair( pid_c(p), idx_c(i) ) );
} }
} }
...@@ -739,7 +758,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderNoWeight( const s ...@@ -739,7 +758,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::mortonOrderNoWeight( const s
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::balanceWeighted( const std::vector< std::vector< std::pair< BlockID, typename PhantomData_T::weight_t > > > & allBlocks, void DynamicCurveBalance< PhantomData_T >::balanceWeighted( const std::vector< std::vector< std::pair< BlockID, typename PhantomData_T::weight_t > > > & allBlocks,
const std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel, const std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel,
std::vector< std::vector<pid_t> > & targets, std::vector< std::vector<pid_t> > & targets,
std::vector< std::set<pid_t> > & sender ) const std::vector< std::set<pid_t> > & sender ) const
...@@ -790,7 +809,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::balanceWeighted( const std:: ...@@ -790,7 +809,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::balanceWeighted( const std::
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::balanceNoWeight( const std::vector< std::vector< BlockID > > & allBlocks, void DynamicCurveBalance< PhantomData_T >::balanceNoWeight( const std::vector< std::vector< BlockID > > & allBlocks,
const std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel, const std::vector< std::vector< std::pair< pid_t, idx_t > > > & blocksPerLevel,
std::vector< std::vector<pid_t> > & targets, std::vector< std::vector<pid_t> > & targets,
std::vector< std::set<pid_t> > & sender ) const std::vector< std::set<pid_t> > & sender ) const
...@@ -825,7 +844,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::balanceNoWeight( const std:: ...@@ -825,7 +844,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::balanceNoWeight( const std::
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::masterEnd( std::vector< std::vector<pid_t> > & targets, void DynamicCurveBalance< PhantomData_T >::masterEnd( std::vector< std::vector<pid_t> > & targets,
std::vector< std::set<pid_t> > & sender, std::vector< std::set<pid_t> > & sender,
std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess, std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess,
std::set< uint_t > & processesToRecvFrom ) const std::set< uint_t > & processesToRecvFrom ) const
...@@ -893,7 +912,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterEnd( std::vector< std: ...@@ -893,7 +912,7 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::masterEnd( std::vector< std:
template< typename PhantomData_T > template< typename PhantomData_T >
void DynamicLevelwiseCurveBalance< PhantomData_T >::finalAssignment( const uint_t index, const std::vector< std::vector<pid_t> > & targets, void DynamicCurveBalance< PhantomData_T >::finalAssignment( const uint_t index, const std::vector< std::vector<pid_t> > & targets,
const std::vector< std::set<pid_t> > & sender, const std::vector< std::set<pid_t> > & sender,
std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess, std::vector< std::pair< const PhantomBlock *, uint_t > > & targetProcess,
std::set< uint_t > & processesToRecvFrom ) const std::set< uint_t > & processesToRecvFrom ) const
...@@ -907,7 +926,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::finalAssignment( const uint_ ...@@ -907,7 +926,9 @@ void DynamicLevelwiseCurveBalance< PhantomData_T >::finalAssignment( const uint_
processesToRecvFrom.insert( uint_c(*s) ) ; processesToRecvFrom.insert( uint_c(*s) ) ;
} }
///This class is deprecated use DynamicCurveBalance instead.
template< typename PhantomData_T >
using DynamicLevelwiseCurveBalance = DynamicCurveBalance<PhantomData_T> ;
} // namespace blockforest } // namespace blockforest
} // namespace walberla } // namespace walberla
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment