/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2013-2017 OpenFOAM Foundation Copyright (C) 2019-2025 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. OpenFOAM 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. OpenFOAM 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 OpenFOAM. If not, see . \*---------------------------------------------------------------------------*/ #include "globalIndex.H" // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * // // Cannot use non-blocking for non-contiguous data. // template // inline Foam::UPstream::commsTypes getCommsType // ( // const UPstream::commsTypes preferred // ) // { // return // ( // ( // !is_contiguous_v // && UPstream::commsTypes::nonBlocking == preferred // ) // ? UPstream::commsTypes::scheduled // : preferred // ); // } template Foam::labelList Foam::globalIndex::calcOffsets ( const IndirectListBase& counts, const bool checkOverflow ) { labelList values; const label len = counts.size(); if (len) { values.resize(len+1); label start = 0; for (label i = 0; i < len; ++i) { const label count = counts[i]; values[i] = start; start += count; if (checkOverflow && start < values[i]) { reportOverflowAndExit(i, values[i], count); } } values[len] = start; } return values; } template Foam::labelList Foam::globalIndex::calcListOffsets ( const List& lists, const bool checkOverflow ) { labelList values; const label len = lists.size(); if (len) { values.resize(len+1); label start = 0; for (label i = 0; i < len; ++i) { const label count = lists[i].size(); values[i] = start; start += count; if (checkOverflow && start < values[i]) { reportOverflowAndExit(i, values[i], count); } } values[len] = start; } return values; } template void Foam::globalIndex::gatherValues ( const label comm, const ProcIDsContainer& procIDs, const Type& localValue, List& allValues, const int tag, const UPstream::commsTypes preferredCommsType ) { // low-level: no parRun guard // Cannot use non-blocking for non-contiguous data. const UPstream::commsTypes commsType = ( ( !is_contiguous_v && UPstream::commsTypes::nonBlocking == preferredCommsType ) ? UPstream::commsTypes::scheduled : preferredCommsType ); const label startOfRequests = UPstream::nRequests(); const int masterProci = procIDs.size() ? procIDs[0] : 0; if (UPstream::myProcNo(comm) == masterProci) { allValues.resize_nocopy(procIDs.size()); allValues[0] = localValue; for (label i = 1; i < procIDs.size(); ++i) { if constexpr (is_contiguous_v) { UIPstream::read ( commsType, procIDs[i], reinterpret_cast(&allValues[i]), sizeof(Type), tag, comm ); } else { IPstream::recv(allValues[i], procIDs[i], tag, comm); } } } else { allValues.clear(); // safety: zero-size on non-master if constexpr (is_contiguous_v) { UOPstream::write ( commsType, masterProci, reinterpret_cast(&localValue), sizeof(Type), tag, comm ); } else { OPstream::send(localValue, commsType, masterProci, tag, comm); } } if (commsType == UPstream::commsTypes::nonBlocking) { // Wait for outstanding requests UPstream::waitRequests(startOfRequests); } } template void Foam::globalIndex::gather ( const labelUList& off, // needed on master only const label comm, const ProcIDsContainer& procIDs, const UList& fld, List& allFld, const int tag, const UPstream::commsTypes preferredCommsType ) { // low-level: no parRun guard // Cannot use non-blocking for non-contiguous data. const UPstream::commsTypes commsType = ( ( !is_contiguous_v && UPstream::commsTypes::nonBlocking == preferredCommsType ) ? UPstream::commsTypes::scheduled : preferredCommsType ); const label startOfRequests = UPstream::nRequests(); const int masterProci = procIDs.size() ? procIDs[0] : 0; if (UPstream::myProcNo(comm) == masterProci) { allFld.resize_nocopy(off.back()); // == totalSize() // Assign my local data - respect offset information // so that we can request 0 entries to be copied. // Also handle the case where we have a slice of the full // list. SubList(allFld, off[1]-off[0], off[0]) = SubList(fld, off[1]-off[0]); for (label i = 1; i < procIDs.size(); ++i) { SubList procSlot(allFld, off[i+1]-off[i], off[i]); if (procSlot.empty()) { // Nothing to do } else if constexpr (is_contiguous_v) { UIPstream::read ( commsType, procIDs[i], procSlot, tag, comm ); } else { IPstream::recv(procSlot, procIDs[i], tag, comm); } } } else { if (fld.empty()) { // Nothing to do } else if constexpr (is_contiguous_v) { UOPstream::write ( commsType, masterProci, fld, tag, comm ); } else { OPstream::send(fld, commsType, masterProci, tag, comm); } } if (commsType == UPstream::commsTypes::nonBlocking) { // Wait for outstanding requests UPstream::waitRequests(startOfRequests); } } template void Foam::globalIndex::gather ( const labelUList& off, // needed on master only const label comm, const ProcIDsContainer& procIDs, const IndirectListBase& fld, List& allFld, const int tag, const UPstream::commsTypes preferredCommsType ) { // low-level: no parRun guard if constexpr (is_contiguous_v) { // Flatten list (locally) so that we can benefit from using direct // read/write of contiguous data gather ( off, comm, procIDs, List(fld), allFld, tag, preferredCommsType ); return; } // Cannot use non-blocking for non-contiguous data. const UPstream::commsTypes commsType = ( ( !is_contiguous_v && UPstream::commsTypes::nonBlocking == preferredCommsType ) ? UPstream::commsTypes::scheduled : preferredCommsType ); const label startOfRequests = UPstream::nRequests(); const int masterProci = procIDs.size() ? procIDs[0] : 0; if (UPstream::myProcNo(comm) == masterProci) { allFld.resize_nocopy(off.back()); // == totalSize() // Assign my local data - respect offset information // so that we can request 0 entries to be copied SubList localSlot(allFld, off[1]-off[0], off[0]); if (!localSlot.empty()) { localSlot = fld; } // Already verified commsType != nonBlocking for (label i = 1; i < procIDs.size(); ++i) { SubList procSlot(allFld, off[i+1]-off[i], off[i]); if (procSlot.empty()) { // Nothing to do } else { IPstream::recv(procSlot, procIDs[i], tag, comm); } } } else { if (fld.empty()) { // Nothing to do } else { OPstream::send(fld, commsType, masterProci, tag, comm); } } if (commsType == UPstream::commsTypes::nonBlocking) { // Wait for outstanding requests UPstream::waitRequests(startOfRequests); } } // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // template void Foam::globalIndex::gather ( const UList& sendData, List& allData, const int tag, const UPstream::commsTypes commsType, const label comm ) const { if (!UPstream::parRun()) { // Serial: direct copy allData = sendData; return; } { globalIndex::gather ( offsets_, // needed on master only comm, UPstream::allProcs(comm), // All communicator ranks sendData, allData, tag, commsType ); if (!UPstream::master(comm)) { allData.clear(); // safety: zero-size on non-master } } } template void Foam::globalIndex::gather ( const IndirectListBase& sendData, List& allData, const int tag, const UPstream::commsTypes commsType, const label comm ) const { if (!UPstream::parRun()) { // Serial: direct copy allData = sendData; return; } { globalIndex::gather ( offsets_, // needed on master only comm, UPstream::allProcs(comm), // All communicator ranks sendData, allData, tag, commsType ); if (!UPstream::master(comm)) { allData.clear(); // safety: zero-size on non-master } } } template OutputContainer Foam::globalIndex::gather ( const UList& sendData, const int tag, const UPstream::commsTypes commsType, const label comm ) const { OutputContainer allData; gather(sendData, allData, tag, commsType, comm); return allData; } template OutputContainer Foam::globalIndex::gather ( const IndirectListBase& sendData, const int tag, const UPstream::commsTypes commsType, const label comm ) const { OutputContainer allData; gather(sendData, allData, tag, commsType, comm); return allData; } template void Foam::globalIndex::gatherInplace ( List& fld, const int tag, const UPstream::commsTypes commsType, const label comm ) const { if (UPstream::parRun()) { List allData; gather(fld, allData, tag, commsType, comm); if (UPstream::master(comm)) { fld.transfer(allData); } else { fld.clear(); // zero-size on non-master } } // Serial: (no-op) } template void Foam::globalIndex::mpiGather ( const UList& sendData, OutputContainer& allData, const label comm, const UPstream::commsTypes commsType, const int tag ) const { if (!UPstream::parRun()) { // Serial: direct copy allData = sendData; return; } // MPI_Gatherv requires contiguous data, but a byte-wise transfer can // quickly exceed the 'int' limits used for MPI sizes/offsets. // Thus gather label/scalar components when possible to increase the // effective size limit. // // Note: cannot rely on pTraits (cmptType, nComponents) since this method // needs to compile (and work) even with things like strings etc. // Single char ad hoc "enum": // - b(yte): gather bytes // - f(loat): gather scalars components // - i(nt): gather label components // - 0: gather with Pstream read/write etc. List recvCounts; List recvOffsets; char dataMode(0); int nCmpts(0); if constexpr (is_contiguous_v) { if constexpr (is_contiguous_scalar::value) { dataMode = 'f'; nCmpts = static_cast(sizeof(Type)/sizeof(scalar)); } else if constexpr (is_contiguous_label::value) { dataMode = 'i'; nCmpts = static_cast(sizeof(Type)/sizeof(label)); } else { dataMode = 'b'; nCmpts = static_cast(sizeof(Type)); } // Offsets must fit into int if (UPstream::master(comm)) { const globalIndex& globalAddr = *this; if (globalAddr.totalSize() > (INT_MAX/nCmpts)) { // Offsets do not fit into int - revert to manual. dataMode = 0; } else { // Must be same as Pstream::nProcs(comm), at least on master! const label nproc = globalAddr.nProcs(); allData.resize_nocopy(globalAddr.totalSize()); recvCounts.resize(nproc); recvOffsets.resize(nproc+1); for (label proci = 0; proci < nproc; ++proci) { recvCounts[proci] = globalAddr.localSize(proci)*nCmpts; recvOffsets[proci] = globalAddr.localStart(proci)*nCmpts; } recvOffsets[nproc] = globalAddr.totalSize()*nCmpts; // Assign local data directly recvCounts[0] = 0; // ie, ignore for MPI_Gatherv SubList(allData, globalAddr.range(0)) = SubList(sendData, globalAddr.range(0)); } } // Consistent information for everyone UPstream::broadcast(&dataMode, 1, comm); } // Dispatch switch (dataMode) { case 'b': // Byte-wise { UPstream::gather ( sendData.cdata_bytes(), sendData.size_bytes(), allData.data_bytes(), recvCounts, recvOffsets, comm ); break; } case 'f': // Float (scalar) components { typedef scalar cmptType; UPstream::gather ( reinterpret_cast(sendData.cdata()), (sendData.size()*nCmpts), reinterpret_cast(allData.data()), recvCounts, recvOffsets, comm ); break; } case 'i': // Int (label) components { typedef label cmptType; UPstream::gather ( reinterpret_cast(sendData.cdata()), (sendData.size()*nCmpts), reinterpret_cast(allData.data()), recvCounts, recvOffsets, comm ); break; } default: // Regular (manual) gathering { globalIndex::gather ( offsets_, // needed on master only comm, UPstream::allProcs(comm), // All communicator ranks sendData, allData, tag, commsType ); break; } } if (!UPstream::master(comm)) { allData.clear(); // safety: zero-size on non-master } } template OutputContainer Foam::globalIndex::mpiGather ( const UList& sendData, const label comm, const UPstream::commsTypes commsType, const int tag ) const { OutputContainer allData; mpiGather(sendData, allData, comm, commsType, tag); return allData; } template void Foam::globalIndex::mpiGatherInplace ( List& fld, const label comm, const UPstream::commsTypes commsType, const int tag ) const { if (UPstream::parRun()) { List allData; mpiGather(fld, allData, comm, commsType, tag); if (UPstream::master(comm)) { fld.transfer(allData); } else { fld.clear(); // zero-size on non-master } } // Serial: (no-op) } template void Foam::globalIndex::mpiGatherOp ( const UList& sendData, OutputContainer& allData, const label comm, const UPstream::commsTypes commsType, const int tag ) { if (UPstream::parRun()) { // Gather sizes - only needed on master globalIndex(globalIndex::gatherOnly{}, sendData.size(), comm) .mpiGather(sendData, allData, comm, commsType, tag); } else { // Serial: direct copy allData = sendData; } } template OutputContainer Foam::globalIndex::mpiGatherOp ( const UList& sendData, const label comm, const UPstream::commsTypes commsType, const int tag ) { OutputContainer allData; mpiGatherOp(sendData, allData, comm, commsType, tag); return allData; } template void Foam::globalIndex::mpiGatherInplaceOp ( List& fld, const label comm, const UPstream::commsTypes commsType, const int tag ) { if (UPstream::parRun()) { List allData; mpiGatherOp(fld, allData, comm, commsType, tag); if (UPstream::master(comm)) { fld.transfer(allData); } else { fld.clear(); // zero-size on non-master } } // Serial: (no-op) } template void Foam::globalIndex::gatherOp ( const UList& sendData, List& allData, const int tag, const UPstream::commsTypes commsType, const label comm ) { if (UPstream::parRun()) { // Gather sizes - only needed on master globalIndex(globalIndex::gatherOnly{}, sendData.size(), comm) .gather(sendData, allData, tag, commsType, comm); } else { // Serial: direct copy allData = sendData; } } template void Foam::globalIndex::gatherOp ( const IndirectListBase& sendData, List& allData, const int tag, const UPstream::commsTypes commsType, const label comm ) { if (UPstream::parRun()) { // Gather sizes - only needed on master globalIndex(globalIndex::gatherOnly{}, sendData.size(), comm) .gather(sendData, allData, tag, commsType, comm); } else { // Serial: direct copy allData = List(sendData); } } template OutputContainer Foam::globalIndex::gatherOp ( const UList& sendData, const int tag, const UPstream::commsTypes commsType, const label comm ) { OutputContainer allData; gatherOp(sendData, allData, tag, commsType, comm); return allData; } template OutputContainer Foam::globalIndex::gatherOp ( const IndirectListBase& sendData, const int tag, const UPstream::commsTypes commsType, const label comm ) { OutputContainer allData; gatherOp(sendData, allData, tag, commsType, comm); return allData; } template void Foam::globalIndex::gatherInplaceOp ( List& fld, const int tag, const UPstream::commsTypes commsType, const label comm ) { if (UPstream::parRun()) { // Gather sizes - only needed on master globalIndex(globalIndex::gatherOnly{}, fld.size(), comm) .gather(fld, tag, commsType, comm); } // Serial: (no-op) } template void Foam::globalIndex::scatter ( const labelUList& off, // needed on master only const label comm, const ProcIDsContainer& procIDs, const UList& allFld, UList& fld, const int tag, const UPstream::commsTypes preferredCommsType ) { // low-level: no parRun guard // Cannot use non-blocking for non-contiguous data. const UPstream::commsTypes commsType = ( ( !is_contiguous_v && UPstream::commsTypes::nonBlocking == preferredCommsType ) ? UPstream::commsTypes::scheduled : preferredCommsType ); const label startOfRequests = UPstream::nRequests(); const int masterProci = procIDs.size() ? procIDs[0] : 0; if (UPstream::myProcNo(comm) == masterProci) { for (label i = 1; i < procIDs.size(); ++i) { const SubList procSlot(allFld, off[i+1]-off[i], off[i]); if (procSlot.empty()) { // Nothing to do } else if constexpr (is_contiguous_v) { UOPstream::write ( commsType, procIDs[i], procSlot, tag, comm ); } else { OPstream::send(procSlot, commsType, procIDs[i], tag, comm); } } // Assign my local data - respect offset information // so that we can request 0 entries to be copied. // Also handle the case where we have a slice of the full // list. SubList(fld, off[1]-off[0]) = SubList(allFld, off[1]-off[0], off[0]); } else { // Note: we are receiving into UList, so sizes MUST match or we // have a problem. Can therefore reasonably assume that a zero-sized // send matches a zero-sized receive, and we can skip that. if (fld.empty()) { // Nothing to do } else if constexpr (is_contiguous_v) { UIPstream::read ( commsType, masterProci, fld, tag, comm ); } else { IPstream::recv(fld, masterProci, tag, comm); } } if (commsType == UPstream::commsTypes::nonBlocking) { // Wait for outstanding requests UPstream::waitRequests(startOfRequests); } } template void Foam::globalIndex::scatter ( const UList& allData, UList& localData, const int tag, const UPstream::commsTypes commsType, const label comm ) const { if (UPstream::parRun()) { scatter ( offsets_, // needed on master only comm, UPstream::allProcs(comm), // All communicator ranks allData, localData, tag, commsType ); } else { // Serial: direct copy // - fails miserably if incorrectly dimensioned! localData.deepCopy(allData); } } template OutputContainer Foam::globalIndex::scatter ( const UList& allData, const int tag, const UPstream::commsTypes commsType, const label comm ) const { if (UPstream::parRun()) { // The globalIndex might be correct on master only, // so scatter local sizes to ensure consistency const label count ( UPstream::listScatterValues