6868#include " gromacs/domdec/domdec_network.h"
6969#include " gromacs/domdec/domdec_struct.h"
7070#include " gromacs/domdec/localatomset.h"
71+ #include " gromacs/math/boxmatrix.h"
7172#include " gromacs/mdlib/broadcaststructs.h"
7273#include " gromacs/mdrunutility/mdmodulesnotifiers.h"
7374#include " gromacs/mdtypes/enerdata.h"
@@ -152,6 +153,8 @@ struct MetatomicData
152153 torch::Tensor cachedTypes;
153154 torch::Tensor cachedPbc;
154155
156+ // ! Sparse/dense force exchange threshold (atom count). Env: GMX_METATOMIC_SPARSE_THRESHOLD.
157+ int32_t sparseThreshold = 1000 ;
155158};
156159
157160MetatomicForceProvider::MetatomicForceProvider (const MetatomicOptions& options,
@@ -172,6 +175,15 @@ MetatomicForceProvider::MetatomicForceProvider(const MetatomicOptions& options,
172175
173176 data_->debugEnabled = (std::getenv (" GMX_METATOMIC_DEBUG" ) != nullptr );
174177
178+ if (const char * env = std::getenv (" GMX_METATOMIC_SPARSE_THRESHOLD" ))
179+ {
180+ data_->sparseThreshold = std::stoi (env);
181+ GMX_LOG (logger_.info )
182+ .asParagraph ()
183+ .appendTextFormatted (" Metatomic sparse force threshold: %d" ,
184+ data_->sparseThreshold );
185+ }
186+
175187 // Force single-threaded PyTorch operations in all parallel runs.
176188 // In thread-MPI, ranks share a process; in real MPI, each rank is a process.
177189 // In both cases, GROMACS manages CPU affinity, and having PyTorch spawn its
@@ -710,7 +722,7 @@ int32_t MetatomicForceProvider::exchangeBackwardGhosts(
710722}
711723
712724
713- void MetatomicForceProvider::exchangeBackwardPairs (const matrix box)
725+ void MetatomicForceProvider::exchangeBackwardPairs (const matrix box, int maxRounds )
714726{
715727 backwardPairsMta_.clear ();
716728 backwardShiftsMta_.clear ();
@@ -772,10 +784,14 @@ void MetatomicForceProvider::exchangeBackwardPairs(const matrix box)
772784 const int sendTo = (myRank + 1 ) % numRanks;
773785 const int recvFrom = (myRank - 1 + numRanks) % numRanks;
774786
787+ // Compute box inverse once for triclinic-safe shift computation.
788+ matrix boxInv;
789+ invertBoxMatrix (box, boxInv);
790+
775791 std::vector<int > sendBuf = myPairsBuf;
776792 std::vector<int > recvBuf;
777793
778- for (int round = 0 ; round < numRanks - 1 ; round++)
794+ for (int round = 0 ; round < maxRounds ; round++)
779795 {
780796 // Exchange counts first so receiver knows buffer size.
781797 int sendCount = static_cast <int >(sendBuf.size ());
@@ -820,7 +836,8 @@ void MetatomicForceProvider::exchangeBackwardPairs(const matrix box)
820836 const int32_t localI = itI->second ;
821837 const int32_t localJ = itJ->second ;
822838
823- // Compute minimum-image shift from local positions (orthorhombic).
839+ // Triclinic-safe minimum-image shift (matches LAMMPS cell_shifts).
840+ // invertBoxMatrix returns lower-triangular inverse, so upper triangle is 0.
824841 const double rawDx =
825842 static_cast <double >(positions_[localJ][XX ] - positions_[localI][XX ]);
826843 const double rawDy =
@@ -829,12 +846,11 @@ void MetatomicForceProvider::exchangeBackwardPairs(const matrix box)
829846 static_cast <double >(positions_[localJ][ZZ ] - positions_[localI][ZZ ]);
830847
831848 IVec shift;
832- shift[XX ] = static_cast <int >(
833- std::round (-rawDx / static_cast <double >(box[XX ][XX ])));
834- shift[YY ] = static_cast <int >(
835- std::round (-rawDy / static_cast <double >(box[YY ][YY ])));
836- shift[ZZ ] = static_cast <int >(
837- std::round (-rawDz / static_cast <double >(box[ZZ ][ZZ ])));
849+ shift[XX ] = static_cast <int >(std::round (
850+ -(boxInv[XX ][XX ] * rawDx + boxInv[YY ][XX ] * rawDy + boxInv[ZZ ][XX ] * rawDz)));
851+ shift[YY ] = static_cast <int >(std::round (
852+ -(boxInv[YY ][YY ] * rawDy + boxInv[ZZ ][YY ] * rawDz)));
853+ shift[ZZ ] = static_cast <int >(std::round (-(boxInv[ZZ ][ZZ ] * rawDz)));
838854
839855 backwardPairsMta_.push_back (localI);
840856 backwardPairsMta_.push_back (localJ);
@@ -872,7 +888,7 @@ void MetatomicForceProvider::distributeNonHomeForces(const double* forces
872888
873889 // For small systems, dense allreduce has lower latency than the
874890 // sparse exchange (gather counts + allgatherv).
875- constexpr int32_t sparseThreshold = 1000 ;
891+ const int32_t sparseThreshold = data_-> sparseThreshold ;
876892
877893 if (numTotalMta < sparseThreshold)
878894 {
@@ -997,16 +1013,19 @@ void MetatomicForceProvider::calculateForces(const ForceProviderInput& inputs, F
9971013
9981014 if (useNewtonNL)
9991015 {
1016+ // Compute max cutoff across all NL requests (used by both ghost
1017+ // exchange and ring hop limit).
1018+ double maxCutoff = 0.0 ;
1019+ for (const auto & req : data_->nl_requests )
1020+ {
1021+ maxCutoff = std::max (maxCutoff, req->engine_cutoff (" nm" ));
1022+ }
1023+
10001024 // Step 1: Exchange backward ghost atoms to fill the backward gap
10011025 // in the DD halo. Extends positions_, atomNumbers_, mtaToGlobalMta_
10021026 // and numLocalMta_ with atoms from the backward PBC neighbor.
10031027 {
10041028 MetatomicTimer timer (" exchangeBackwardGhosts" , mpiComm_);
1005- double maxCutoff = 0.0 ;
1006- for (const auto & req : data_->nl_requests )
1007- {
1008- maxCutoff = std::max (maxCutoff, req->engine_cutoff (" nm" ));
1009- }
10101029 exchangeBackwardGhosts (inputs.dd_ , inputs.box_ , maxCutoff);
10111030 }
10121031
@@ -1016,9 +1035,27 @@ void MetatomicForceProvider::calculateForces(const ForceProviderInput& inputs, F
10161035
10171036 // Step 2: Exchange backward-direction pairs. Discovers pairs from
10181037 // other ranks' pairlists that involve this rank's home atoms.
1038+ // Limit ring rounds to ceil(cutoff/minCellSize) when DD is available.
10191039 {
10201040 MetatomicTimer timer (" exchangeBackwardPairs" , mpiComm_);
1021- exchangeBackwardPairs (inputs.box_ );
1041+ int maxRounds = mpiComm_.size () - 1 ;
1042+ if (inputs.dd_ != nullptr && inputs.dd_ ->ndim > 0 )
1043+ {
1044+ double minCellSize = 1e30 ;
1045+ for (int d = 0 ; d < inputs.dd_ ->ndim ; d++)
1046+ {
1047+ const int dim = inputs.dd_ ->dim [d];
1048+ const double cs = static_cast <double >(inputs.box_ [dim][dim])
1049+ / inputs.dd_ ->numCells [dim];
1050+ minCellSize = std::min (minCellSize, cs);
1051+ }
1052+ if (minCellSize > 0.0 )
1053+ {
1054+ maxRounds = std::min (maxRounds,
1055+ static_cast <int >(std::ceil (maxCutoff / minCellSize)));
1056+ }
1057+ }
1058+ exchangeBackwardPairs (inputs.box_ , maxRounds);
10221059 }
10231060
10241061 // Step 3: Temporarily extend pairlistMta_ with backward pairs
0 commit comments