Pre-allocate arrays for MergePartitionedDataSet

The initial implementation of `MergePartitionedDataSet` would grow each
array as it was generated. As each partition was revisited, the arrays
being merged would be reallocated and data appended to the end. Although
this works, it is slower than necessary. Each reallocation has to copy
the previously saved data into the newly allocated memory space.

This new implementation first counts how big each array should be and
then copies data from each partition into the appropriate location of
each dataset.

Also changed the templating of how fields are copied so that all field
types are supported, not just those in the common types.
This commit is contained in:
Kenneth Moreland 2022-01-06 07:15:38 -07:00
parent 4444d260a3
commit 10f21b21ae

@ -10,105 +10,378 @@
#include <vtkm/cont/MergePartitionedDataSet.h>
#include <vtkm/cont/ArrayCopy.h>
#include <vtkm/cont/ArrayHandleGroupVecVariable.h>
#include <vtkm/cont/ArrayHandleView.h>
#include <vtkm/cont/ConvertNumComponentsToOffsets.h>
#include <vtkm/cont/CoordinateSystem.h>
#include <vtkm/cont/DataSet.h>
#include <vtkm/cont/Logging.h>
#include <vtkm/cont/PartitionedDataSet.h>
#include <vtkm/cont/UnknownCellSet.h>
#include <vtkm/worklet/CellDeepCopy.h>
#include <vtkm/worklet/WorkletMapField.h>
#include <vtkm/worklet/WorkletMapTopology.h>
namespace
{
void CountPointsAndCells(const vtkm::cont::PartitionedDataSet& partitionedDataSet,
vtkm::Id& numPointsTotal,
vtkm::Id& numCellsTotal)
{
numPointsTotal = 0;
numCellsTotal = 0;
for (vtkm::Id partitionId = 0; partitionId < partitionedDataSet.GetNumberOfPartitions();
++partitionId)
{
vtkm::cont::DataSet partition = partitionedDataSet.GetPartition(partitionId);
numPointsTotal += partition.GetNumberOfPoints();
numCellsTotal += partition.GetNumberOfCells();
}
}
struct PassCellShapesNumIndices : vtkm::worklet::WorkletVisitCellsWithPoints
{
using ControlSignature = void(CellSetIn inputTopology, FieldOut shapes, FieldOut numIndices);
using ExecutionSignature = void(CellShape, PointCount, _2, _3);
template <typename CellShape>
VTKM_EXEC void operator()(const CellShape& inShape,
vtkm::IdComponent inNumIndices,
vtkm::UInt8& outShape,
vtkm::IdComponent& outNumIndices) const
{
outShape = inShape.Id;
outNumIndices = inNumIndices;
}
};
void MergeShapes(const vtkm::cont::PartitionedDataSet& partitionedDataSet,
vtkm::Id numCellsTotal,
vtkm::cont::ArrayHandle<vtkm::UInt8>& shapes,
vtkm::cont::ArrayHandle<vtkm::IdComponent>& numIndices)
{
vtkm::cont::Invoker invoke;
shapes.Allocate(numCellsTotal);
numIndices.Allocate(numCellsTotal);
vtkm::Id cellStartIndex = 0;
for (vtkm::Id partitionId = 0; partitionId < partitionedDataSet.GetNumberOfPartitions();
++partitionId)
{
vtkm::cont::DataSet partition = partitionedDataSet.GetPartition(partitionId);
vtkm::Id numCellsPartition = partition.GetNumberOfCells();
auto shapesView = vtkm::cont::make_ArrayHandleView(shapes, cellStartIndex, numCellsPartition);
auto numIndicesView =
vtkm::cont::make_ArrayHandleView(numIndices, cellStartIndex, numCellsPartition);
invoke(PassCellShapesNumIndices{}, partition.GetCellSet(), shapesView, numIndicesView);
cellStartIndex += numCellsPartition;
}
VTKM_ASSERT(cellStartIndex == numCellsTotal);
}
struct PassCellIndices : vtkm::worklet::WorkletVisitCellsWithPoints
{
using ControlSignature = void(CellSetIn inputTopology, FieldOut pointIndices);
using ExecutionSignature = void(PointIndices, _2);
vtkm::Id IndexOffset;
PassCellIndices(vtkm::Id indexOffset)
: IndexOffset(indexOffset)
{
}
template <typename InPointIndexType, typename OutPointIndexType>
VTKM_EXEC void operator()(const InPointIndexType& inPoints, OutPointIndexType& outPoints) const
{
vtkm::IdComponent numPoints = inPoints.GetNumberOfComponents();
VTKM_ASSERT(numPoints == outPoints.GetNumberOfComponents());
for (vtkm::IdComponent pointIndex = 0; pointIndex < numPoints; pointIndex++)
{
outPoints[pointIndex] = inPoints[pointIndex] + this->IndexOffset;
}
}
};
void MergeIndices(const vtkm::cont::PartitionedDataSet& partitionedDataSet,
const vtkm::cont::ArrayHandle<vtkm::Id>& offsets,
vtkm::Id numIndicesTotal,
vtkm::cont::ArrayHandle<vtkm::Id>& indices)
{
vtkm::cont::Invoker invoke;
indices.Allocate(numIndicesTotal);
vtkm::Id pointStartIndex = 0;
vtkm::Id cellStartIndex = 0;
for (vtkm::Id partitionId = 0; partitionId < partitionedDataSet.GetNumberOfPartitions();
++partitionId)
{
vtkm::cont::DataSet partition = partitionedDataSet.GetPartition(partitionId);
vtkm::Id numCellsPartition = partition.GetNumberOfCells();
auto offsetsView =
vtkm::cont::make_ArrayHandleView(offsets, cellStartIndex, numCellsPartition + 1);
auto indicesGroupView = vtkm::cont::make_ArrayHandleGroupVecVariable(indices, offsetsView);
invoke(PassCellIndices{ pointStartIndex }, partition.GetCellSet(), indicesGroupView);
pointStartIndex += partition.GetNumberOfPoints();
cellStartIndex += numCellsPartition;
}
VTKM_ASSERT(cellStartIndex == (offsets.GetNumberOfValues() - 1));
}
vtkm::cont::CellSetExplicit<> MergeCellSets(
const vtkm::cont::PartitionedDataSet& partitionedDataSet,
vtkm::Id numPointsTotal,
vtkm::Id numCellsTotal)
{
vtkm::cont::ArrayHandle<vtkm::UInt8> shapes;
vtkm::cont::ArrayHandle<vtkm::IdComponent> numIndices;
MergeShapes(partitionedDataSet, numCellsTotal, shapes, numIndices);
vtkm::cont::ArrayHandle<vtkm::Id> offsets;
vtkm::Id numIndicesTotal;
vtkm::cont::ConvertNumComponentsToOffsets(numIndices, offsets, numIndicesTotal);
numIndices.ReleaseResources();
vtkm::cont::ArrayHandle<vtkm::Id> indices;
MergeIndices(partitionedDataSet, offsets, numIndicesTotal, indices);
vtkm::cont::CellSetExplicit<> outCells;
outCells.Fill(numPointsTotal, shapes, indices, offsets);
return outCells;
}
struct ClearPartitionWorklet : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn indices, WholeArrayInOut array);
using ExecutionSignature = void(WorkIndex, _2);
vtkm::Id IndexOffset;
ClearPartitionWorklet(vtkm::Id indexOffset)
: IndexOffset(indexOffset)
{
}
template <typename OutPortalType>
VTKM_EXEC void operator()(vtkm::Id index, OutPortalType& outPortal) const
{
// It's weird to get a value from a portal only to override it, but the expect type
// is weird (a variable-sized Vec), so this is the only practical way to set it.
auto outVec = outPortal.Get(index + this->IndexOffset);
for (vtkm::IdComponent comp = 0; comp < outVec.GetNumberOfComponents(); ++comp)
{
outVec[comp] = 0;
}
// Shouldn't really do anything.
outPortal.Set(index + this->IndexOffset, outVec);
}
};
template <typename OutArrayHandle>
void ClearPartition(OutArrayHandle& outArray, vtkm::Id startIndex, vtkm::Id numValues)
{
vtkm::cont::Invoker invoke;
invoke(ClearPartitionWorklet{ startIndex }, vtkm::cont::ArrayHandleIndex(numValues), outArray);
}
struct CopyPartitionWorklet : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn sourceArray, WholeArrayInOut mergedArray);
using ExecutionSignature = void(WorkIndex, _1, _2);
vtkm::Id IndexOffset;
CopyPartitionWorklet(vtkm::Id indexOffset)
: IndexOffset(indexOffset)
{
}
template <typename InVecType, typename OutPortalType>
VTKM_EXEC void operator()(vtkm::Id index, const InVecType& inVec, OutPortalType& outPortal) const
{
// It's weird to get a value from a portal only to override it, but the expect type
// is weird (a variable-sized Vec), so this is the only practical way to set it.
auto outVec = outPortal.Get(index + this->IndexOffset);
VTKM_ASSERT(inVec.GetNumberOfComponents() == outVec.GetNumberOfComponents());
for (vtkm::IdComponent comp = 0; comp < outVec.GetNumberOfComponents(); ++comp)
{
outVec[comp] = static_cast<typename decltype(outVec)::ComponentType>(inVec[comp]);
}
// Shouldn't really do anything.
outPortal.Set(index + this->IndexOffset, outVec);
}
};
template <typename OutArrayHandle>
void CopyPartition(const vtkm::cont::Field& inField, OutArrayHandle& outArray, vtkm::Id startIndex)
{
vtkm::cont::Invoker invoke;
using ComponentType = typename OutArrayHandle::ValueType::ComponentType;
if (inField.GetData().IsBaseComponentType<ComponentType>())
{
invoke(CopyPartitionWorklet{ startIndex },
inField.GetData().ExtractArrayFromComponents<ComponentType>(),
outArray);
}
else
{
VTKM_LOG_S(vtkm::cont::LogLevel::Info,
"Discovered mismatched types for field " << inField.GetName()
<< ". Requires extra copy.");
invoke(CopyPartitionWorklet{ startIndex },
inField.GetDataAsDefaultFloat().ExtractArrayFromComponents<vtkm::FloatDefault>(),
outArray);
}
}
template <typename HasFieldFunctor, typename GetFieldFunctor>
vtkm::cont::UnknownArrayHandle MergeArray(vtkm::Id numPartitions,
HasFieldFunctor&& hasField,
GetFieldFunctor&& getField,
vtkm::Id totalSize)
{
vtkm::cont::UnknownArrayHandle mergedArray = getField(0).GetData().NewInstanceBasic();
mergedArray.Allocate(totalSize);
vtkm::Id startIndex = 0;
for (vtkm::Id partitionId = 0; partitionId < numPartitions; ++partitionId)
{
vtkm::Id partitionSize;
if (hasField(partitionId, partitionSize))
{
vtkm::cont::Field sourceField = getField(partitionId);
mergedArray.CastAndCallWithExtractedArray(
[=](auto array) { CopyPartition(sourceField, array, startIndex); });
}
else
{
mergedArray.CastAndCallWithExtractedArray(
[=](auto array) { ClearPartition(array, startIndex, partitionSize); });
}
startIndex += partitionSize;
}
VTKM_ASSERT(startIndex == totalSize);
return mergedArray;
}
vtkm::cont::CoordinateSystem MergeCoordinateSystem(
const vtkm::cont::PartitionedDataSet& partitionedDataSet,
vtkm::IdComponent coordId,
vtkm::Id numPointsTotal)
{
std::string coordName = partitionedDataSet.GetPartition(0).GetCoordinateSystem(coordId).GetName();
auto hasField = [&](vtkm::Id partitionId, vtkm::Id& partitionSize) -> bool {
vtkm::cont::DataSet partition = partitionedDataSet.GetPartition(partitionId);
partitionSize = partition.GetNumberOfPoints();
// Should partitions match coordinates on name or coordinate id? They both should match, but
// for now let's go by id and check the name.
if (partition.GetNumberOfCoordinateSystems() <= coordId)
{
VTKM_LOG_S(vtkm::cont::LogLevel::Warn,
"When merging partitions, partition "
<< partitionId << " is missing coordinate system with index " << coordId);
return false;
}
if (partition.GetCoordinateSystem(coordId).GetName() != coordName)
{
VTKM_LOG_S(vtkm::cont::LogLevel::Warn,
"When merging partitions, partition "
<< partitionId << " reported a coordinate system with name '"
<< partition.GetCoordinateSystem(coordId).GetName()
<< "' instead of expected name '" << coordName << "'");
}
return true;
};
auto getField = [&](vtkm::Id partitionId) -> vtkm::cont::Field {
return partitionedDataSet.GetPartition(partitionId).GetCoordinateSystem(coordId);
};
vtkm::cont::UnknownArrayHandle mergedArray =
MergeArray(partitionedDataSet.GetNumberOfPartitions(), hasField, getField, numPointsTotal);
return vtkm::cont::CoordinateSystem{ coordName, mergedArray };
}
vtkm::cont::Field MergeField(const vtkm::cont::PartitionedDataSet& partitionedDataSet,
vtkm::IdComponent fieldId,
vtkm::Id numPointsTotal,
vtkm::Id numCellsTotal)
{
vtkm::cont::Field referenceField = partitionedDataSet.GetPartition(0).GetField(fieldId);
vtkm::Id totalSize = 0;
switch (referenceField.GetAssociation())
{
case vtkm::cont::Field::Association::POINTS:
totalSize = numPointsTotal;
break;
case vtkm::cont::Field::Association::CELL_SET:
totalSize = numCellsTotal;
break;
default:
VTKM_LOG_S(vtkm::cont::LogLevel::Info,
"Skipping merge of field '" << referenceField.GetName()
<< "' because it has an unsupported association.");
return referenceField;
}
auto hasField = [&](vtkm::Id partitionId, vtkm::Id& partitionSize) -> bool {
vtkm::cont::DataSet partition = partitionedDataSet.GetPartition(partitionId);
if (partition.HasField(referenceField.GetName(), referenceField.GetAssociation()))
{
partitionSize = partition.GetField(referenceField.GetName(), referenceField.GetAssociation())
.GetData()
.GetNumberOfValues();
return true;
}
else
{
VTKM_LOG_S(vtkm::cont::LogLevel::Info,
"Partition " << partitionId << " does not have field "
<< referenceField.GetName());
switch (referenceField.GetAssociation())
{
case vtkm::cont::Field::Association::POINTS:
partitionSize = partition.GetNumberOfPoints();
break;
case vtkm::cont::Field::Association::CELL_SET:
partitionSize = partition.GetNumberOfCells();
break;
default:
partitionSize = 0;
break;
}
return false;
}
};
auto getField = [&](vtkm::Id partitionId) -> vtkm::cont::Field {
return partitionedDataSet.GetPartition(partitionId)
.GetField(referenceField.GetName(), referenceField.GetAssociation());
};
vtkm::cont::UnknownArrayHandle mergedArray =
MergeArray(partitionedDataSet.GetNumberOfPartitions(), hasField, getField, totalSize);
return vtkm::cont::Field{ referenceField.GetName(),
referenceField.GetAssociation(),
mergedArray };
}
} // anonymous namespace
//-----------------------------------------------------------------------------
namespace vtkm
{
namespace cont
{
struct TransferCellsFunctor
{
template <typename T>
VTKM_CONT void operator()(const T& cellSetIn,
vtkm::cont::ArrayHandle<vtkm::UInt8>& shapes,
vtkm::cont::ArrayHandle<vtkm::Id>& numIndices,
vtkm::cont::ArrayHandle<vtkm::Id>& connectivity,
vtkm::Id pointStartIndex) const
{
// allocate shapes and numIndices
vtkm::Id cellStartIndex = shapes.GetNumberOfValues();
shapes.Allocate(cellStartIndex + cellSetIn.GetNumberOfCells(), vtkm::CopyFlag::On);
numIndices.Allocate(cellStartIndex + cellSetIn.GetNumberOfCells(), vtkm::CopyFlag::On);
// fill the view of numIndices
vtkm::cont::ArrayHandleView<vtkm::cont::ArrayHandle<vtkm::Id>> viewArrayNumIndices(
numIndices, cellStartIndex, cellSetIn.GetNumberOfCells());
vtkm::cont::Invoker invoke;
invoke(vtkm::worklet::CellDeepCopy::CountCellPoints{}, cellSetIn, viewArrayNumIndices);
// convert numIndices to offsets and derive numberOfConnectivity
vtkm::cont::ArrayHandle<vtkm::Id> offsets;
vtkm::Id numberOfConnectivity;
vtkm::cont::ConvertNumComponentsToOffsets(viewArrayNumIndices, offsets, numberOfConnectivity);
// allocate connectivity
vtkm::Id connectivityStartIndex = connectivity.GetNumberOfValues();
connectivity.Allocate(connectivityStartIndex + numberOfConnectivity, vtkm::CopyFlag::On);
// fill the view of shapes and connectivity
vtkm::cont::ArrayHandleView<vtkm::cont::ArrayHandle<vtkm::UInt8>> viewArrayShapes(
shapes, cellStartIndex, cellSetIn.GetNumberOfCells());
vtkm::cont::ArrayHandleView<vtkm::cont::ArrayHandle<vtkm::Id>> viewArrayConnectivity(
connectivity, connectivityStartIndex, numberOfConnectivity);
invoke(vtkm::worklet::CellDeepCopy::PassCellStructure{},
cellSetIn,
viewArrayShapes,
vtkm::cont::make_ArrayHandleGroupVecVariable(viewArrayConnectivity, offsets));
shapes.ReleaseResourcesExecution();
offsets.ReleaseResourcesExecution();
connectivity.ReleaseResourcesExecution();
// point the connectivity to the point indices of this partition
vtkm::cont::Algorithm::Transform(
vtkm::cont::ArrayHandleConstant<vtkm::Id>(pointStartIndex, numberOfConnectivity),
viewArrayConnectivity,
viewArrayConnectivity,
vtkm::Sum());
}
};
void TransferCells(const vtkm::cont::UnknownCellSet& cellSetIn,
vtkm::cont::ArrayHandle<vtkm::UInt8>& shapes,
vtkm::cont::ArrayHandle<vtkm::Id>& numIndices,
vtkm::cont::ArrayHandle<vtkm::Id>& connectivity,
vtkm::Id startIndex)
{
vtkm::cont::CastAndCall(
cellSetIn, TransferCellsFunctor{}, shapes, numIndices, connectivity, startIndex);
}
struct TransferArrayFunctor
{
template <typename T, typename S>
VTKM_CONT void operator()(const vtkm::cont::ArrayHandle<T, S>& arrayIn,
vtkm::cont::UnknownArrayHandle& arrayOut,
vtkm::Id startIndex) const
{
vtkm::cont::ArrayHandleView<vtkm::cont::ArrayHandle<T>> viewArrayOut(
arrayOut.AsArrayHandle<vtkm::cont::ArrayHandle<T>>(),
startIndex,
arrayIn.GetNumberOfValues());
vtkm::cont::ArrayCopy(arrayIn, viewArrayOut);
}
};
void TransferArray(const vtkm::cont::UnknownArrayHandle& arrayIn,
vtkm::cont::UnknownArrayHandle& arrayOut,
vtkm::Id startIndex)
{
arrayIn.CastAndCallForTypes<
VTKM_DEFAULT_TYPE_LIST,
vtkm::List<vtkm::cont::StorageTagBasic, vtkm::cont::StorageTagUniformPoints>>(
TransferArrayFunctor{}, arrayOut, startIndex);
}
//-----------------------------------------------------------------------------
VTKM_CONT
vtkm::cont::DataSet MergePartitionedDataSet(
const vtkm::cont::PartitionedDataSet& partitionedDataSet)
@ -116,103 +389,27 @@ vtkm::cont::DataSet MergePartitionedDataSet(
// verify correctnees of data
VTKM_ASSERT(partitionedDataSet.GetNumberOfPartitions() > 0);
vtkm::cont::UnknownArrayHandle coordsOut;
vtkm::cont::ArrayCopy(
partitionedDataSet.GetPartition(0).GetCoordinateSystem().GetDataAsMultiplexer(), coordsOut);
vtkm::cont::ArrayHandle<vtkm::UInt8> shapes;
vtkm::cont::ArrayHandle<vtkm::Id> numIndices;
vtkm::cont::ArrayHandle<vtkm::Id> connectivity;
vtkm::Id numberOfPointsSoFar = 0;
for (vtkm::Id partitionId = 0; partitionId < partitionedDataSet.GetNumberOfPartitions();
partitionId++)
vtkm::Id numPointsTotal;
vtkm::Id numCellsTotal;
CountPointsAndCells(partitionedDataSet, numPointsTotal, numCellsTotal);
vtkm::cont::DataSet outputData;
outputData.SetCellSet(MergeCellSets(partitionedDataSet, numPointsTotal, numCellsTotal));
vtkm::cont::DataSet partition0 = partitionedDataSet.GetPartition(0);
for (vtkm::IdComponent coordId = 0; coordId < partition0.GetNumberOfCoordinateSystems();
++coordId)
{
auto partition = partitionedDataSet.GetPartition(partitionId);
// Transfer points
auto coordsIn = partition.GetCoordinateSystem().GetDataAsMultiplexer();
coordsOut.Allocate(numberOfPointsSoFar + partition.GetNumberOfPoints(), vtkm::CopyFlag::On);
TransferArray(coordsIn, coordsOut, numberOfPointsSoFar);
// Transfer cells
vtkm::cont::UnknownCellSet cellset;
cellset = partition.GetCellSet();
TransferCells(cellset, shapes, numIndices, connectivity, numberOfPointsSoFar);
numberOfPointsSoFar += partition.GetNumberOfPoints();
outputData.AddCoordinateSystem(
MergeCoordinateSystem(partitionedDataSet, coordId, numPointsTotal));
}
// create dataset
vtkm::cont::CellSetExplicit<> cellSet;
vtkm::Id nPts = static_cast<vtkm::Id>(coordsOut.GetNumberOfValues());
vtkm::cont::ArrayHandle<vtkm::Id> offsets;
vtkm::cont::Algorithm::ScanExtended(numIndices, offsets);
cellSet.Fill(nPts, shapes, connectivity, offsets);
vtkm::cont::DataSet derivedDataSet;
derivedDataSet.AddCoordinateSystem(vtkm::cont::CoordinateSystem(
partitionedDataSet.GetPartition(0).GetCoordinateSystem().GetName(), coordsOut));
derivedDataSet.SetCellSet(cellSet);
// Transfer fields
for (vtkm::IdComponent f = 0; f < partitionedDataSet.GetPartition(0).GetNumberOfFields(); f++)
for (vtkm::IdComponent fieldId = 0; fieldId < partition0.GetNumberOfFields(); ++fieldId)
{
std::string name = partitionedDataSet.GetPartition(0).GetField(f).GetName();
vtkm::cont::UnknownArrayHandle outFieldHandle;
vtkm::cont::ArrayCopy(partitionedDataSet.GetPartition(0).GetField(name).GetData(),
outFieldHandle);
if (partitionedDataSet.GetPartition(0).GetField(name).IsFieldCell())
{
outFieldHandle.Allocate(derivedDataSet.GetNumberOfCells());
vtkm::Id numberOfCellValuesSoFar = 0;
for (vtkm::Id partitionId = 0; partitionId < partitionedDataSet.GetNumberOfPartitions();
partitionId++)
{
try
{
auto cellField = partitionedDataSet.GetPartition(partitionId).GetField(name).GetData();
TransferArray(cellField, outFieldHandle, numberOfCellValuesSoFar);
}
catch (const vtkm::cont::Error& error)
{
std::cout << "Partition 0 contains an array that partition " << partitionId
<< " does not contain. The merged Dataset will have random values where values "
"were missing."
<< std::endl;
std::cout << error.GetMessage() << std::endl;
}
numberOfCellValuesSoFar += partitionedDataSet.GetPartition(partitionId).GetNumberOfCells();
}
derivedDataSet.AddCellField(name, outFieldHandle);
}
else
{
outFieldHandle.Allocate(derivedDataSet.GetNumberOfPoints());
vtkm::Id numberOfPointValuesSoFar = 0;
for (vtkm::Id partitionId = 0; partitionId < partitionedDataSet.GetNumberOfPartitions();
partitionId++)
{
try
{
auto pointField = partitionedDataSet.GetPartition(partitionId).GetField(name).GetData();
TransferArray(pointField, outFieldHandle, numberOfPointValuesSoFar);
}
// catch (vtkm::cont::ErrorBadValue& error)
catch (const vtkm::cont::Error& error)
{
std::cout << "Partition 0 contains an array that partition " << partitionId
<< " does not contain. The merged Dataset will have random values where values "
"were missing."
<< std::endl;
std::cout << error.GetMessage() << std::endl;
}
numberOfPointValuesSoFar +=
partitionedDataSet.GetPartition(partitionId).GetNumberOfPoints();
}
derivedDataSet.AddPointField(name, outFieldHandle);
}
outputData.AddField(MergeField(partitionedDataSet, fieldId, numPointsTotal, numCellsTotal));
}
return derivedDataSet;
return outputData;
}
}