Changes / Improvements
- more consistent subsetting, interface
* Extend the use of subset and non-subset collections with uniform
internal getters to ensure that the subset/non-subset versions
are robustly handled.
* operator[](label) and objectIndex(label) for standardized access
to the underlying item, or the original index, regardless of
subsetting or not.
* centres() and centre(label) for representative point cloud
information.
* nDim() returns the object dimensionality (0: point, 1: line, etc)
these can be used to determine how 'fat' each shape may be
and whether bounds(labelList) may contribute any useful information.
* bounds(labelList) to return the full bound box required for
specific items. Eg, the overall bounds for various 3D cells.
- easier construction of non-caching versions. The bounding boxes are
rarely cached, so simpler constructors without the caching bool
are provided.
- expose findNearest (bound sphere) method to allow general use
since this does not actually need a tree.
- static helpers
The boxes() static methods can be used by callers that need to build
their own treeBoundBoxList of common shapes (edge, face, cell)
that are also available as treeData types.
The bounds() static methods can be used by callers to determine the
overall bound-box size prior to constructing an indexedOctree
without writing ad hoc code inplace.
Not implemented for treeDataPrimitivePatch since similiar
functionality is available directly from the PrimitivePatch::box()
method with less typing.
========
BREAKING: cellLabels(), faceLabels(), edgeLabel() access methods
- it was always unsafe to use the treeData xxxLabels() methods without
subsetting elements. However, since the various classes
(treeDataCell, treeDataEdge, etc) automatically provided
an identity lookup, this problem was not apparent.
Use objectIndex(label) to safely de-reference to the original index
and operator[](index) to de-reference to the original object.
- use default initialize boundBox instead of invertedBox
- reset() instead of assigning from invertedBox
- extend (three parameter version) and grow method
- inflate(Random) instead of extend + re-assigning
ENH: use direct access to pointHit as point(), use dist(), distSqr()
- if the pointHit has already been checked for hit(), can/should
simply use point() noexcept access subsequently to avoid redundant
checks. Using vector distSqr() methods provides a minor optimization
(no itermediate temporary), but can also make for clearer code.
ENH: copy construct pointIndexHit with different index
- symmetric with constructing from a pointHit with an index
STYLE: prefer pointHit point() instead of rawPoint()
General:
* -roots, -hostRoots, -fileHandler
Specific:
* -to <coordinateSystem> -from <coordinateSystem>
- Display -help-compat when compatibility or ignored options are available
STYLE: capitalization of options text
- The bitSet class replaces the old PackedBoolList class.
The redesign provides better block-wise access and reduced method
calls. This helps both in cases where the bitSet may be relatively
sparse, and in cases where advantage of contiguous operations can be
made. This makes it easier to work with a bitSet as top-level object.
In addition to the previously available count() method to determine
if a bitSet is being used, now have simpler queries:
- all() - true if all bits in the addressable range are empty
- any() - true if any bits are set at all.
- none() - true if no bits are set.
These are faster than count() and allow early termination.
The new test() method tests the value of a single bit position and
returns a bool without any ambiguity caused by the return type
(like the get() method), nor the const/non-const access (like
operator[] has). The name corresponds to what std::bitset uses.
The new find_first(), find_last(), find_next() methods provide a faster
means of searching for bits that are set.
This can be especially useful when using a bitSet to control an
conditional:
OLD (with macro):
forAll(selected, celli)
{
if (selected[celli])
{
sumVol += mesh_.cellVolumes()[celli];
}
}
NEW (with const_iterator):
for (const label celli : selected)
{
sumVol += mesh_.cellVolumes()[celli];
}
or manually
for
(
label celli = selected.find_first();
celli != -1;
celli = selected.find_next()
)
{
sumVol += mesh_.cellVolumes()[celli];
}
- When marking up contiguous parts of a bitset, an interval can be
represented more efficiently as a labelRange of start/size.
For example,
OLD:
if (isA<processorPolyPatch>(pp))
{
forAll(pp, i)
{
ignoreFaces.set(i);
}
}
NEW:
if (isA<processorPolyPatch>(pp))
{
ignoreFaces.set(pp.range());
}
- eliminate iterators from PackedList since they were unused, had
lower performance than direct access and added unneeded complexity.
- eliminate auto-vivify for the PackedList '[] operator.
The set() method provides any required auto-vivification and
removing this ability from the '[]' operator allows for a lower
when accessing the values. Replaced the previous cascade of iterators
with simpler reference class.
PackedBoolList:
- (temporarily) eliminate logic and addition operators since
these contained partially unclear semantics.
- the new test() method tests the value of a single bit position and
returns a bool without any ambiguity caused by the return type
(like the get() method), nor the const/non-const access (like
operator[] has). The name corresponds to what std::bitset uses.
- more consistent use of PackedBoolList test(), set(), unset() methods
for fewer operation and clearer code. Eg,
if (list.test(index)) ... | if (list[index]) ...
if (!list.test(index)) ... | if (list[index] == 0u) ...
list.set(index); | list[index] = 1u;
list.unset(index); | list[index] = 0u;
- deleted the operator=(const labelUList&) and replaced with a setMany()
method for more clarity about the intended operation and to avoid any
potential inadvertent behaviour.
- subsetList, inplaceSubsetList with optional inverted logic.
- use moveable elements where possible.
- allow optional starting offset for the identity global function.
Eg, 'identity(10, start)' vs 'identity(10) + start'
- use succincter method names that more closely resemble dictionary
and HashTable method names. This improves method name consistency
between classes and also requires less typing effort:
args.found(optName) vs. args.optionFound(optName)
args.readIfPresent(..) vs. args.optionReadIfPresent(..)
...
args.opt<scalar>(optName) vs. args.optionRead<scalar>(optName)
args.read<scalar>(index) vs. args.argRead<scalar>(index)
- the older method names forms have been retained for code compatibility,
but are now deprecated
Original commit message:
------------------------
Parallel IO: New collated file format
When an OpenFOAM simulation runs in parallel, the data for decomposed fields and
mesh(es) has historically been stored in multiple files within separate
directories for each processor. Processor directories are named 'processorN',
where N is the processor number.
This commit introduces an alternative "collated" file format where the data for
each decomposed field (and mesh) is collated into a single file, which is
written and read on the master processor. The files are stored in a single
directory named 'processors'.
The new format produces significantly fewer files - one per field, instead of N
per field. For large parallel cases, this avoids the restriction on the number
of open files imposed by the operating system limits.
The file writing can be threaded allowing the simulation to continue running
while the data is being written to file. NFS (Network File System) is not
needed when using the the collated format and additionally, there is an option
to run without NFS with the original uncollated approach, known as
"masterUncollated".
The controls for the file handling are in the OptimisationSwitches of
etc/controlDict:
OptimisationSwitches
{
...
//- Parallel IO file handler
// uncollated (default), collated or masterUncollated
fileHandler uncollated;
//- collated: thread buffer size for queued file writes.
// If set to 0 or not sufficient for the file size threading is not used.
// Default: 2e9
maxThreadFileBufferSize 2e9;
//- masterUncollated: non-blocking buffer size.
// If the file exceeds this buffer size scheduled transfer is used.
// Default: 2e9
maxMasterFileBufferSize 2e9;
}
When using the collated file handling, memory is allocated for the data in the
thread. maxThreadFileBufferSize sets the maximum size of memory in bytes that
is allocated. If the data exceeds this size, the write does not use threading.
When using the masterUncollated file handling, non-blocking MPI communication
requires a sufficiently large memory buffer on the master node.
maxMasterFileBufferSize sets the maximum size in bytes of the buffer. If the
data exceeds this size, the system uses scheduled communication.
The installation defaults for the fileHandler choice, maxThreadFileBufferSize
and maxMasterFileBufferSize (set in etc/controlDict) can be over-ridden within
the case controlDict file, like other parameters. Additionally the fileHandler
can be set by:
- the "-fileHandler" command line argument;
- a FOAM_FILEHANDLER environment variable.
A foamFormatConvert utility allows users to convert files between the collated
and uncollated formats, e.g.
mpirun -np 2 foamFormatConvert -parallel -fileHandler uncollated
An example case demonstrating the file handling methods is provided in:
$FOAM_TUTORIALS/IO/fileHandling
The work was undertaken by Mattijs Janssens, in collaboration with Henry Weller.