File mpicartesiangroup.h
File List > cartesian > mpicartesiangroup.h
Go to the documentation of this file
#ifndef TEMPLAT_PARALLEL_MPI_COMM_MPICARTESIANGROUP_H
#define TEMPLAT_PARALLEL_MPI_COMM_MPICARTESIANGROUP_H
/* This file is part of TempLat, available at https://cosmolattice.github.io/templat .
Copyright 2021-2026 The TempLat authors, see AUTHORS.md.
Released under the MIT license, see LICENSE.md. */
// File info: Main contributor(s): Wessel Valkenburg, Year: 2019
#include "TempLat/util/exception.h"
#include "TempLat/parallel/mpi/comm/mpicommreference.h"
#include "TempLat/parallel/mpi/comm/mpidomainsplit.h"
namespace TempLat
{
MakeException(MPICartesianGroupException);
class MPICartesianGroup
{
public:
// Put public methods here. These should change very little over time.
MPICartesianGroup(MPICommReference topologyGroup, ptrdiff_t nDimensions, std::vector<int> decomposition,
std::vector<int> expectedCoords = std::vector<int>(),
std::vector<int> periodic = std::vector<int>())
: mBaseGroup(topologyGroup), mCartesianGroup(mBaseGroup), mNDimensions(nDimensions),
mDecomposition(decomposition), mPeriodic(periodic), mExpectedCoords(expectedCoords)
{
verifyInput();
createGroups();
verifyCoordsMatchTopology();
}
MPICartesianGroup(ptrdiff_t nDimensions, std::vector<int> decomposition)
: MPICartesianGroup(MPICommReference(), nDimensions, decomposition)
{
}
MPI_Comm getComm() const { return mCartesianGroup; }
MPICommReference getBaseComm() const { return mBaseGroup; }
int getRank() { return mCartesianGroup.getRank(); }
ptrdiff_t getNDimensions() const { return mNDimensions; }
const std::vector<int> &getPosition() const { return mSelfPosition; }
const std::vector<int> &getDecomposition() const { return mDecomposition; }
auto size() const { return mBaseGroup.size(); }
friend std::ostream &operator<<(std::ostream &ostream, MPICartesianGroup mGr)
{
ostream << "MPICartesianGroup:\n decomposition: " << mGr.getDecomposition() << "\n this rank: " << mGr.getRank()
<< "\n this position: " << mGr.getPosition() << "\n";
return ostream;
}
private:
/* Put all member variables and private methods here. These may change arbitrarily. */
MPICommReference mBaseGroup;
MPICommReference mCartesianGroup;
const ptrdiff_t mNDimensions;
std::vector<int> mDecomposition;
std::vector<int> mPeriodic;
std::vector<int> mExpectedCoords;
std::vector<int> mSelfPosition;
std::vector<int> fetchPosition(int ofRank)
{
std::vector<int> result(mDecomposition.size(), 0);
#ifdef HAVE_MPI
// sayMPI << this << "Getting coords: " << ofRank << ", " << result << " nd: " <<mNDimensions<< "\n";
if (0 != MPI_Cart_coords(mCartesianGroup, ofRank, (int)result.size(), result.data())) {
throw MPICartesianGroupException("Could not get result from MPI_Cart_coords.");
}
#endif
return result;
}
void createGroups()
{
#ifdef HAVE_MPI
/* No reordering. The rank order of mBaseGroup is the topology, so the Cartesian
* communicator must preserve it: the layout's local starts are derived from that order,
* and ghost neighbours are derived from MPI_Cart_shift on the communicator built here.
* Letting MPI permute ranks lets those two disagree. */
mCartesianGroup = createOneGroup(mBaseGroup, mNDimensions, false);
#endif
mSelfPosition = fetchPosition(mCartesianGroup.rank());
}
MPICommReference createOneGroup(MPICommReference fromGroup, ptrdiff_t nDim, bool reorder)
{
MPI_Comm newComm;
#ifdef HAVE_MPI
/* Build from fromGroup, not mBaseGroup: this used to ignore its own argument. */
MPI_Cart_create(fromGroup, nDim, mDecomposition.data(), mPeriodic.data(), reorder, &newComm);
#else
(void)fromGroup;
(void)nDim;
(void)reorder;
newComm = MPI_COMM_WORLD;
#endif
return MPICommReference(newComm);
}
void verifyCoordsMatchTopology() const
{
if (mExpectedCoords.empty()) return;
if ((ptrdiff_t)mExpectedCoords.size() != mNDimensions)
throw MPICartesianGroupException("Expected coordinates have ", mExpectedCoords.size(), " entries, but this is a ",
mNDimensions, "-dimensional group.");
for (ptrdiff_t i = 0; i < mNDimensions; ++i) {
if (mSelfPosition[i] != mExpectedCoords[i])
throw MPICartesianGroupException(
"Cartesian coordinates disagree with the topology that produced this group: MPI places this rank at ",
mSelfPosition, " but the topology's owner reports ", mExpectedCoords, " (decomposition ", mDecomposition,
"). The layout's local starts follow the latter while ghost exchange follows the former, so continuing "
"would silently exchange the wrong data at subdomain boundaries.");
}
}
void verifyInput()
{
while ((ptrdiff_t)mDecomposition.size() < mNDimensions)
mDecomposition.push_back(1);
while (mPeriodic.size() < mDecomposition.size())
mPeriodic.push_back(1);
if ((ptrdiff_t)mDecomposition.size() > mNDimensions) mDecomposition.resize(mNDimensions);
if ((ptrdiff_t)mPeriodic.size() > mNDimensions) mPeriodic.resize(mNDimensions);
/* The decomposition must tile the communicator exactly. This used to "repair" a mismatch
* by overwriting mDecomposition[0] and then zeroing trailing entries from the right, which
* silently mangled an explicitly-pinned backend grid into something the backend would not
* use — precisely the disagreement this class now exists to prevent. */
ptrdiff_t groupSize = mBaseGroup.size();
ptrdiff_t product = 1;
for (auto &&it : mDecomposition)
product *= it;
if (product != groupSize)
throw MPICartesianGroupException("Decomposition ", mDecomposition, " describes ", product,
" ranks but the communicator has ", groupSize,
". The decomposition must tile the communicator exactly; build the group via "
"FFTMPIDomainSplit::makeMPIGroup so it matches the FFT backend.");
}
};
} // namespace TempLat
#endif