ENH: extend bitSet functionality

- num_blocks(), test_set() as per boost
- broadcast(), reduceAnd(), reduceOr() to simplify parallel operations
- matrix-like output for PackedList::writeList()

BUG: Pstream::broadcastList() missing resize on sub-ranks

- latent bug since it was unused in any OpenFOAM code
This commit is contained in:
Mark Olesen
2025-08-28 14:33:18 +02:00
parent 19caabbd56
commit bd57627955
17 changed files with 433 additions and 117 deletions

View File

@ -1,3 +1,3 @@
Test-PackedList.C
Test-PackedList.cxx
EXE = $(FOAM_USER_APPBIN)/Test-PackedList

View File

@ -142,6 +142,9 @@ int main(int argc, char *argv[])
Info<< "got: " << bset1 << nl
<< "and: " << bset2 << nl
<< "and: " << bset3 << nl;
Info<< "==";
bset3.writeList(Info, 10) << nl; // matrix-like output
}
}

View File

@ -137,11 +137,11 @@ inline bool compare
const std::string& expected
)
{
const List<unsigned int>& store = bitset.storage();
const auto& store = bitset.storage();
std::string has;
for (label blocki=0; blocki < bitset.nBlocks(); ++blocki)
for (label blocki=0; blocki < bitset.num_blocks(); ++blocki)
{
has += toString(store[blocki]);
}

View File

@ -185,9 +185,9 @@ int main(int argc, char *argv[])
}
broadcast_chunks<labelList, label>(input1);
Pstream::maxCommsSize = 33;
UPstream::maxCommsSize = 33;
args.readIfPresent("comms-size", Pstream::maxCommsSize);
args.readIfPresent("comms-size", UPstream::maxCommsSize);
broadcast_chunks<labelList, label>(input1);
@ -197,11 +197,11 @@ int main(int argc, char *argv[])
PstreamBuffers pBufs;
labelList sendData;
if (Pstream::master())
if (UPstream::master())
{
sendData = identity(500);
for (const int proci : Pstream::subProcs())
for (const int proci : UPstream::subProcs())
{
UOPstream os(proci, pBufs);
os << sendData;
@ -211,7 +211,7 @@ int main(int argc, char *argv[])
Info<< "call finishedSends()" << endl;
pBufs.finishedScatters();
if (!Pstream::master())
if (UPstream::is_subrank())
{
UIPstream is(UPstream::masterNo(), pBufs);
is >> sendData;
@ -225,11 +225,11 @@ int main(int argc, char *argv[])
labelListList recvBufs(UPstream::nProcs());
labelList recvSizes;
if (Pstream::master())
if (UPstream::master())
{
for (const int proci : Pstream::allProcs())
for (const int proci : UPstream::allProcs())
{
if (proci != Pstream::myProcNo())
if (proci != UPstream::myProcNo())
{
sendBufs[proci] = identity(500);
}
@ -253,11 +253,11 @@ int main(int argc, char *argv[])
Map<labelList> recvBufs;
Map<label> recvSizes;
if (Pstream::master())
if (UPstream::master())
{
for (const int proci : Pstream::allProcs())
for (const int proci : UPstream::allProcs())
{
if (proci != Pstream::myProcNo())
if (proci != UPstream::myProcNo())
{
sendBufs(proci) = identity(500);
}

View File

@ -110,21 +110,25 @@ int main(int argc, char *argv[])
<< " (self) reduced " << selfVal << nl;
// Identical size on all procs
bitSet procUsed(nProcs);
if ((myRank % 4) == 0)
{
procUsed.set(myRank);
bitSet localUsed(nProcs);
localUsed.set(myRank, ((myRank % 4) == 0));
Pout<< "local procUsed " << localUsed << nl;
localUsed.reduceOr(UPstream::worldComm, false);
Pout<< "reduce procUsed " << localUsed << nl;
}
// With allGather
{
bitSet procUsed
(
bitSet::allGather((myRank % 4) == 0)
);
Pout<< "allGather: " << procUsed << nl;
}
Pout<< "local procUsed " << procUsed << nl;
reduce
(
procUsed.data(),
procUsed.size_data(),
bitOrOp<unsigned int>()
);
Pout<< "reduce procUsed " << procUsed << nl;
// Identical size on all procs
// encode as 0:empty, 1:uniform, 2:nonuniform, 3:mixed
@ -147,12 +151,26 @@ int main(int argc, char *argv[])
}
Pout<< "local uniform " << uniformity << nl;
reduce
// reduce with op<..>()
#if 1
Foam::reduce
(
uniformity.data(),
uniformity.size_data(),
bitOrOp<unsigned int>()
uniformity.num_blocks(),
bitOrOp<unsigned int>(),
UPstream::msgType(), // ignored
UPstream::worldComm
);
#else
// Direct call to MPI_Allreduce
UPstream::mpiAllReduce
(
uniformity.data(),
uniformity.num_blocks(),
UPstream::opCodes::op_bit_or,
UPstream::worldComm
);
#endif
Pout<< "reduce uniform " << uniformity << nl;
}
@ -160,8 +178,8 @@ int main(int argc, char *argv[])
{
Pair<label> val
(
Pstream::myProcNo(UPstream::commWorld()),
Pstream::myProcNo(UPstream::commWorld())
UPstream::myProcNo(UPstream::commWorld()),
UPstream::myProcNo(UPstream::commWorld())
);
Pair<label> worldVal = val;

View File

@ -79,7 +79,7 @@ int main(int argc, char *argv[])
#include "setRootCase.H"
if (!Pstream::parRun())
if (!UPstream::parRun())
{
Info<< "\nWarning: not parallel - skipping further tests\n" << endl;
return 0;
@ -97,7 +97,7 @@ int main(int argc, char *argv[])
DynamicList<MPI_Request> recvRequests(10);
if (!Pstream::master())
if (UPstream::is_subrank())
{
// Send some random length to master

View File

@ -76,7 +76,7 @@ int main(int argc, char *argv[])
#include "setRootCase.H"
if (!Pstream::parRun())
if (!UPstream::parRun())
{
Info<< "\nWarning: not parallel - skipping further tests\n" << endl;
return 0;
@ -96,7 +96,7 @@ int main(int argc, char *argv[])
// Map request indices to procs
Map<label> recvFromProc(20);
if (!Pstream::master())
if (UPstream::is_subrank())
{
// Send some random length to master

View File

@ -52,7 +52,7 @@ int main(int argc, char *argv[])
const bool optNonBlocking = args.found("non-blocking");
if (!Pstream::parRun())
if (!UPstream::parRun())
{
Info<< "\nWarning: not parallel - skipping further tests\n" << endl;
return 0;
@ -73,7 +73,7 @@ int main(int argc, char *argv[])
DynamicList<UPstream::Request> sendRequests(10);
DynamicList<UPstream::Request> recvRequests(10);
if (!Pstream::master())
if (UPstream::is_subrank())
{
// Send some random length to master

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2016 OpenFOAM Foundation
Copyright (C) 2017-2023 OpenCFD Ltd.
Copyright (C) 2017-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -422,20 +422,20 @@ public:
// Low-level access
//- The number of internal storage blocks
inline label nBlocks() const;
inline label num_blocks() const noexcept;
//- Return the underlying storage blocks
inline const List<unsigned int>& storage() const;
const List<block_type>& storage() const noexcept { return blocks_; }
//- Return the underlying storage blocks
// Manipulate with utmost caution
inline List<unsigned int>& storage();
List<block_type>& storage() noexcept { return blocks_; }
//- A const pointer to the raw storage
inline const unsigned int* cdata() const noexcept;
const block_type* cdata() const noexcept { return blocks_.cdata(); }
//- A pointer to the raw storage
inline unsigned int* data() noexcept;
block_type* data() noexcept { return blocks_.data(); }
//- A const pointer to the raw storage, reinterpreted as byte data
inline const char* cdata_bytes() const noexcept;
@ -443,15 +443,15 @@ public:
//- A pointer to the raw storage, reinterpreted as byte data
inline char* data_bytes() noexcept;
//- The number of integer blocks addressed in the raw storage
//- The number of integer blocks addressed in the raw storage.
//- Same as num_blocks().
inline std::streamsize size_data() const noexcept;
//- The number of bytes used in the raw storage
//- including any unused padding.
//- The number of bytes addressed in the raw storage
//- including any padding.
inline std::streamsize size_bytes() const noexcept;
//- The number of bytes used in the raw storage
//- including any unused padding.
//- Same as size_bytes()
inline std::streamsize byteSize() const noexcept;
@ -465,7 +465,7 @@ public:
//- Write List, with line-breaks in ASCII when length exceeds shortLen.
// Using '0' suppresses line-breaks entirely.
Ostream& writeList(Ostream& os, const label shortLen=0) const;
Ostream& writeList(Ostream& os, label shortLen=0) const;
//- Write as a dictionary entry with keyword
void writeEntry(const word& keyword, Ostream& os) const;
@ -529,10 +529,10 @@ public:
public:
//- Copy construct
reference(const reference&) = default;
reference(const reference&) noexcept = default;
//- Move construct
reference(reference&&) = default;
reference(reference&&) noexcept = default;
//- Value assignment
inline void operator=(const reference& other);

View File

@ -609,40 +609,12 @@ inline void Foam::PackedList<Width>::shrink_to_fit()
template<unsigned Width>
inline Foam::List<unsigned int>& Foam::PackedList<Width>::storage()
{
return blocks_;
}
template<unsigned Width>
inline const Foam::List<unsigned int>& Foam::PackedList<Width>::storage() const
{
return blocks_;
}
template<unsigned Width>
inline Foam::label Foam::PackedList<Width>::nBlocks() const
inline Foam::label Foam::PackedList<Width>::num_blocks() const noexcept
{
return num_blocks(size());
}
template<unsigned Width>
inline const unsigned int* Foam::PackedList<Width>::cdata() const noexcept
{
return blocks_.cdata();
}
template<unsigned Width>
inline unsigned int* Foam::PackedList<Width>::data() noexcept
{
return blocks_.data();
}
template<unsigned Width>
inline const char* Foam::PackedList<Width>::cdata_bytes() const noexcept
{
@ -667,7 +639,7 @@ inline std::streamsize Foam::PackedList<Width>::size_data() const noexcept
template<unsigned Width>
inline std::streamsize Foam::PackedList<Width>::size_bytes() const noexcept
{
return size_data() * sizeof(block_type);
return num_blocks(size()) * sizeof(block_type);
}

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2018-2022 OpenCFD Ltd.
Copyright (C) 2018-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -178,12 +178,14 @@ template<unsigned Width>
Foam::Ostream& Foam::PackedList<Width>::writeList
(
Ostream& os,
const label shortLen
label shortLen
) const
{
const PackedList<Width>& list = *this;
const label len = list.size();
if (shortLen < 0) shortLen = 1; // <- sanity
if (os.format() == IOstreamOption::BINARY)
{
// Binary (always contiguous)
@ -209,7 +211,7 @@ Foam::Ostream& Foam::PackedList<Width>::writeList
os << len << token::BEGIN_LIST;
// Contents
for (label i=0; i < len; ++i)
for (label i = 0; i < len; ++i)
{
if (i) os << token::SPACE;
os << label(list.get(i));
@ -226,9 +228,33 @@ Foam::Ostream& Foam::PackedList<Width>::writeList
os << nl << len << nl << token::BEGIN_LIST << nl;
// Contents
for (label i=0; i < len; ++i)
if (shortLen <= 1)
{
os << label(list.get(i)) << nl;
// simple multi-line
for (label i = 0; i < len; ++i)
{
os << label(list.get(i)) << nl;
}
}
else
{
// 'matrix' of values
label line = 0;
for (label i = 0; i < len; ++i, ++line)
{
if (line == shortLen)
{
os << nl;
line = 0;
}
else if (line)
{
os << token::SPACE;
}
os << label(list.get(i));
}
if (line) os << nl;
}
// End delimiter

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2018-2022 OpenCFD Ltd.
Copyright (C) 2018-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -28,14 +28,19 @@ License
#include "bitSet.H"
#include "labelRange.H"
#include "IOstreams.H"
#include "UPstream.H"
#include "addToRunTimeSelectionTable.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
namespace Foam
{
defineTypeNameAndDebug(bitSet, 0);
}
// TBD: add IO support of compound type?
// defineNamedCompoundTypeName(bitSet, List<1>);
// addNamedCompoundToRunTimeSelectionTable(bitSet, bitSet, List<1>);
}
// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
@ -76,7 +81,7 @@ Foam::bitSet& Foam::bitSet::minusEq(const bitSet& other)
Foam::bitSet& Foam::bitSet::andEq(const bitSet& other)
{
if (&other == this)
if (FOAM_UNLIKELY(&other == this))
{
// Self '&=' : no-op
@ -273,19 +278,24 @@ Foam::bitSet::bitSet(const labelRange& range)
void Foam::bitSet::assign(const UList<bool>& bools)
{
const label len = bools.size();
fill(false);
resize(bools.size());
clear();
resize(len);
unsigned bitIdx = 0u;
auto* packed = blocks_.data();
// Could also handle block-wise (in the future?)
// Set according to indices that are true.
for (label i = 0; i < len; ++i)
// Set according to indices that are true
for (const auto b : bools)
{
if (bools[i])
if (b)
{
set(i);
*packed |= (1u << bitIdx);
}
if (++bitIdx >= PackedList<1>::elem_per_block)
{
bitIdx = 0u;
++packed;
}
}
}
@ -540,4 +550,209 @@ Foam::List<bool> Foam::bitSet::values() const
}
// * * * * * * * * * * * * * * Parallel Functions * * * * * * * * * * * * * //
void Foam::bitSet::broadcast(int communicator, bool syncSizes)
{
if (communicator < 0)
{
communicator = UPstream::worldComm;
}
if (!UPstream::is_parallel(communicator))
{
return;
}
int64_t len(size());
if (syncSizes)
{
UPstream::broadcast(&len, 1, communicator);
if (UPstream::is_subrank(communicator))
{
fill(false);
resize(len);
}
}
if (len)
{
// Only broadcast non-empty
UPstream::broadcast(this->data(), this->num_blocks(), communicator);
}
}
void Foam::bitSet::reduceAnd(int communicator, bool syncSizes)
{
if (communicator < 0)
{
communicator = UPstream::worldComm;
}
if (!UPstream::is_parallel(communicator))
{
return;
}
const label origSize(size());
if (syncSizes)
{
// Operation is an intersection
// - common size may be smaller than the original size
int64_t commonSize(size());
UPstream::mpiAllReduce
(
&commonSize,
1,
UPstream::opCodes::op_min,
communicator
);
resize(commonSize);
}
if (!empty())
{
UPstream::mpiAllReduce
(
this->data(),
this->num_blocks(),
UPstream::opCodes::op_bit_and,
communicator
);
clear_trailing_bits(); // safety
}
// Undo side effects from the reduction
if (syncSizes)
{
resize(origSize);
}
}
void Foam::bitSet::reduceOr(int communicator, bool syncSizes)
{
if (communicator < 0)
{
communicator = UPstream::worldComm;
}
if (!UPstream::is_parallel(communicator))
{
return;
}
// const label origSize(size());
if (syncSizes)
{
// Operation can increase the addressed size
// Extend size based on the addressed length.
// This is greedy, but produces consistent sizing
int64_t commonSize(size());
// Alternative: Extend size based on the bits used.
// - tighter, but inconsistent sizes result
// // label commonSize(find_last()+1);
UPstream::mpiAllReduce
(
&commonSize,
1,
UPstream::opCodes::op_max,
communicator
);
extend(commonSize);
}
if (!empty())
{
UPstream::mpiAllReduce
(
this->data(),
this->num_blocks(),
UPstream::opCodes::op_bit_or,
communicator
);
clear_trailing_bits(); // safety
}
}
Foam::bitSet Foam::bitSet::gatherValues(bool localValue, int communicator)
{
if (communicator < 0)
{
communicator = UPstream::worldComm;
}
bitSet allValues;
if (!UPstream::is_parallel(communicator))
{
// non-parallel: return own value
// TBD: only when UPstream::is_rank(communicator) as well?
allValues.resize(1);
allValues.set(0, localValue);
}
else
{
List<bool> bools;
if (UPstream::master(communicator))
{
bools.resize(UPstream::nProcs(communicator), false);
}
UPstream::mpiGather
(
&localValue, // Send
bools.data(), // Recv
1, // Num send/recv data per rank
communicator
);
// Transcribe to bitSet (on master)
allValues.assign(bools);
}
return allValues;
}
// Note that for allGather()
// - MPI_Gather of individual bool values and broadcast the packed result
// - this avoids bit_or on 32bit values everywhere, since we know a priori
// that each rank only contributes 1bit of info
Foam::bitSet Foam::bitSet::allGather(bool localValue, int communicator)
{
if (communicator < 0)
{
communicator = UPstream::worldComm;
}
bitSet allValues(bitSet::gatherValues(localValue, communicator));
if (UPstream::is_parallel(communicator))
{
// Identical size on all ranks
allValues.resize(UPstream::nProcs(communicator));
// Sizes are consistent - broadcast without resizing
allValues.broadcast(communicator, false);
}
return allValues;
}
// ************************************************************************* //

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2018-2023 OpenCFD Ltd.
Copyright (C) 2018-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -46,7 +46,6 @@ See also
#ifndef Foam_bitSet_H
#define Foam_bitSet_H
#include "className.H"
#include "PackedList.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -111,7 +110,11 @@ protected:
public:
// Forward declaration of access classes
//- A bitSet acts like a packed boolList
typedef bool value_type;
// Forward Declarations
class reference;
class const_iterator;
@ -331,6 +334,13 @@ public:
// set(pos) on individual bits.
void set(const labelRange& range);
//- Test for \em True value at specified position
//- and change the value at that position.
// Does auto-vivify for non-existent, non-zero entries.
//
// \note Method name compatibility with std::bitset
bool test_set(const label i, const bool val = true);
// Unsetting single or multiple values
@ -475,10 +485,10 @@ public:
public:
//- Copy construct
reference(const reference&) = default;
reference(const reference&) noexcept = default;
//- Move construct
reference(reference&&) = default;
reference(reference&&) noexcept = default;
//- Flip the bit at the position, no range-checking
inline void flip();
@ -605,6 +615,61 @@ public:
}
// Parallel Operations
//- Broadcast the contents
void broadcast
(
//! The UPstream communicator (default is worldComm)
int communicator = -1,
//! False: sizes already consistent. True: adjust sizes.
bool syncSizes = true
);
//- Inplace \c bit_and parallel reduction
void reduceAnd
(
//! The UPstream communicator (default is worldComm)
int communicator = -1,
//! False: sizes already consistent. True: adjust (shrink) sizes.
bool syncSizes = true
);
//- Inplace \c bit_or parallel reduction
void reduceOr
(
//! The UPstream communicator (default is worldComm)
int communicator = -1,
//! False: sizes already consistent. True: adjust (extend) sizes.
bool syncSizes = true
);
//- Gather individual values into bitSet locations.
// On master, the resulting bitSet has size == nProcs,
// otherwise zero length.
// \n
// For \b non-parallel :
// the length of the returned set is 1 with localValue.
static bitSet gatherValues
(
//! The processor-local value
bool localValue,
//! The UPstream communicator (default is worldComm)
int communicator = -1
);
//- Allgather individual values into bitSet locations.
// The resulting bitSet has size nProcs, identical on all ranks.
// Behaves like Pstream::allGatherValues() but returning a bitSet.
static bitSet allGather
(
//! The processor-local value
bool localValue,
//! The UPstream communicator (default is worldComm)
int communicator = -1
);
// Housekeeping
//- Same as contains()

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2018-2022 OpenCFD Ltd.
Copyright (C) 2018-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -482,23 +482,27 @@ inline void Foam::bitSet::fill(const bool val)
{
if (empty())
{
return; // Trivial case
return;
}
const label nblocks = num_blocks(size());
// Fill value for complete blocks
const unsigned int blockval = (val ? ~0u : 0u);
for (label blocki=0; blocki < nblocks; ++blocki)
{
blocks_[blocki] = blockval;
}
if (val)
else if (val)
{
std::fill_n(blocks_.data(), num_blocks(), ~0u);
clear_trailing_bits();
}
else
{
// or: PackedList<1>::reset();
// or: blocks_ = 0u;
std::fill_n(blocks_.data(), num_blocks(), 0u);
}
}
inline bool Foam::bitSet::test_set(const label i, const bool val)
{
bool old = test(i);
PackedList<1>::set(i, val);
return old;
}

View File

@ -51,6 +51,9 @@ SourceFiles
namespace Foam
{
// Forward Declarations
class bitSet;
/*---------------------------------------------------------------------------*\
Class Pstream Declaration
\*---------------------------------------------------------------------------*/

View File

@ -149,6 +149,11 @@ void Foam::Pstream::broadcastList
{
return;
}
else if constexpr (std::is_same_v<bitSet, ListType>)
{
// Specialized handling implemented within bitSet itself
list.broadcast(communicator);
}
else if constexpr (is_contiguous_v<typename ListType::value_type>)
{
// List data are contiguous
@ -166,6 +171,11 @@ void Foam::Pstream::broadcastList
communicator
);
if (UPstream::is_subrank(communicator))
{
list.resize_nocopy(len);
}
if (len)
{
// Only broadcast non-empty content