//============================================================================ // Copyright (c) Kitware, Inc. // All rights reserved. // See LICENSE.txt for details. // // This software is distributed WITHOUT ANY WARRANTY; without even // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notice for more information. //============================================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { struct CopyWithOffsetWorklet : public vtkm::worklet::WorkletMapField { vtkm::Id OffsetValue; VTKM_CONT CopyWithOffsetWorklet(const vtkm::Id offset) : OffsetValue(offset) { } typedef void ControlSignature(FieldIn, FieldInOut); typedef void ExecutionSignature(_1, _2); VTKM_EXEC void operator()(const vtkm::Id originalValue, vtkm::Id& outputValue) const { outputValue = originalValue + this->OffsetValue; } }; 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 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& shapes, vtkm::cont::ArrayHandle& numIndices, const vtkm::Id firstNonEmptyPartitionId) { vtkm::cont::Invoker invoke; shapes.Allocate(numCellsTotal); numIndices.Allocate(numCellsTotal); vtkm::Id cellStartIndex = 0; for (vtkm::Id partitionId = firstNonEmptyPartitionId; partitionId < partitionedDataSet.GetNumberOfPartitions(); ++partitionId) { if (partitionedDataSet.GetPartition(partitionId).GetNumberOfPoints() == 0) { continue; } 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 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& offsets, vtkm::Id numIndicesTotal, vtkm::cont::ArrayHandle& indices, const vtkm::Id firstNonEmptyPartitionId) { vtkm::cont::Invoker invoke; indices.Allocate(numIndicesTotal); vtkm::Id pointStartIndex = 0; vtkm::Id cellStartIndex = 0; for (vtkm::Id partitionId = firstNonEmptyPartitionId; partitionId < partitionedDataSet.GetNumberOfPartitions(); ++partitionId) { if (partitionedDataSet.GetPartition(partitionId).GetNumberOfPoints() == 0) { continue; } 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::CellSetSingleType<> MergeCellSetsSingleType( const vtkm::cont::PartitionedDataSet& partitionedDataSet, const vtkm::Id firstNonEmptyPartitionId) { vtkm::Id numCells = 0; vtkm::Id numPoints = 0; vtkm::Id numOfDataSet = partitionedDataSet.GetNumberOfPartitions(); std::vector cellOffsets(numOfDataSet); std::vector pointOffsets(numOfDataSet); //Mering cell set into single type //checking the cell type to make sure how many points per cell vtkm::IdComponent numberOfPointsPerCell = partitionedDataSet.GetPartition(firstNonEmptyPartitionId) .GetCellSet() .GetNumberOfPointsInCell(0); for (vtkm::Id partitionIndex = firstNonEmptyPartitionId; partitionIndex < numOfDataSet; partitionIndex++) { if (partitionedDataSet.GetPartition(partitionIndex).GetNumberOfPoints() == 0) { continue; } cellOffsets[partitionIndex] = numCells; numCells += partitionedDataSet.GetPartition(partitionIndex).GetNumberOfCells(); pointOffsets[partitionIndex] = numPoints; numPoints += partitionedDataSet.GetPartition(partitionIndex).GetNumberOfPoints(); } //We assume all cells have same type, which should have been previously checked. const vtkm::Id mergedConnSize = numCells * numberOfPointsPerCell; // Calculating merged offsets for all domains vtkm::cont::ArrayHandle mergedConn; mergedConn.Allocate(mergedConnSize); for (vtkm::Id partitionIndex = firstNonEmptyPartitionId; partitionIndex < numOfDataSet; partitionIndex++) { if (partitionedDataSet.GetPartition(partitionIndex).GetNumberOfPoints() == 0) { continue; } auto cellSet = partitionedDataSet.GetPartition(partitionIndex).GetCellSet(); // Grabing the connectivity and copy it into the larger connectivity array vtkm::cont::CellSetSingleType<> singleType = cellSet.AsCellSet>(); const vtkm::cont::ArrayHandle connPerDataSet = singleType.GetConnectivityArray( vtkm::TopologyElementTagCell(), vtkm::TopologyElementTagPoint()); vtkm::Id copySize = connPerDataSet.GetNumberOfValues(); VTKM_ASSERT(copySize == cellSet.GetNumberOfCells() * numberOfPointsPerCell); // Mapping connectivity array per data into proper region of merged connectivity array // and also adjust the value in merged connectivity array vtkm::cont::Invoker invoker; invoker(CopyWithOffsetWorklet{ pointOffsets[partitionIndex] }, connPerDataSet, vtkm::cont::make_ArrayHandleView( mergedConn, cellOffsets[partitionIndex] * numberOfPointsPerCell, copySize)); } vtkm::cont::CellSetSingleType<> cellSet; //Filling in the cellSet according to shapeId and number of points per cell. vtkm::UInt8 cellShapeId = partitionedDataSet.GetPartition(firstNonEmptyPartitionId).GetCellSet().GetCellShape(0); cellSet.Fill(numPoints, cellShapeId, numberOfPointsPerCell, mergedConn); return cellSet; } vtkm::cont::CellSetExplicit<> MergeCellSetsExplicit( const vtkm::cont::PartitionedDataSet& partitionedDataSet, vtkm::Id numPointsTotal, vtkm::Id numCellsTotal, const vtkm::Id firstNonEmptyPartitionId) { vtkm::cont::ArrayHandle shapes; vtkm::cont::ArrayHandle numIndices; MergeShapes(partitionedDataSet, numCellsTotal, shapes, numIndices, firstNonEmptyPartitionId); vtkm::cont::ArrayHandle offsets; vtkm::Id numIndicesTotal; vtkm::cont::ConvertNumComponentsToOffsets(numIndices, offsets, numIndicesTotal); numIndices.ReleaseResources(); //Merging connectivity/indicies array vtkm::cont::ArrayHandle indices; MergeIndices(partitionedDataSet, offsets, numIndicesTotal, indices, firstNonEmptyPartitionId); vtkm::cont::CellSetExplicit<> outCells; outCells.Fill(numPointsTotal, shapes, indices, offsets); return outCells; } vtkm::Id GetFirstEmptyPartition(const vtkm::cont::PartitionedDataSet& partitionedDataSet) { vtkm::Id numOfDataSet = partitionedDataSet.GetNumberOfPartitions(); vtkm::Id firstNonEmptyPartitionId = -1; for (vtkm::Id partitionIndex = 0; partitionIndex < numOfDataSet; partitionIndex++) { if (partitionedDataSet.GetPartition(partitionIndex).GetNumberOfPoints() != 0) { firstNonEmptyPartitionId = partitionIndex; break; } } return firstNonEmptyPartitionId; } bool PartitionsAreSingleType(const vtkm::cont::PartitionedDataSet partitionedDataSet, const vtkm::Id firstNonEmptyPartitionId) { vtkm::Id numOfDataSet = partitionedDataSet.GetNumberOfPartitions(); for (vtkm::Id partitionIndex = firstNonEmptyPartitionId; partitionIndex < numOfDataSet; partitionIndex++) { if (partitionedDataSet.GetPartition(partitionIndex).GetNumberOfPoints() == 0) { continue; } auto cellSet = partitionedDataSet.GetPartition(partitionIndex).GetCellSet(); if (!cellSet.IsType>()) { return false; } } //Make sure the cell type of each non-empty partition is same //with tested one, and they also have the same number of points. //We know that all cell sets are of type `CellSetSingleType<>` at this point. //Polygons may have different number of points even with the same shape id. //It is more efficient to `GetCellShape(0)` from `CellSetSingleType` compared with `CellSetExplicit`. auto cellSetFirst = partitionedDataSet.GetPartition(firstNonEmptyPartitionId).GetCellSet(); vtkm::UInt8 cellShapeId = cellSetFirst.GetCellShape(0); vtkm::IdComponent numPointsInCell = cellSetFirst.GetNumberOfPointsInCell(0); for (vtkm::Id partitionIndex = firstNonEmptyPartitionId + 1; partitionIndex < numOfDataSet; partitionIndex++) { auto cellSet = partitionedDataSet.GetPartition(partitionIndex).GetCellSet(); if (cellSet.GetCellShape(0) != cellShapeId || cellSet.GetNumberOfPointsInCell(0) != numPointsInCell) { return false; } } return true; } void CheckCoordsNames(const vtkm::cont::PartitionedDataSet partitionedDataSet, const vtkm::Id firstNonEmptyPartitionId) { vtkm::IdComponent numCoords = partitionedDataSet.GetPartition(firstNonEmptyPartitionId).GetNumberOfCoordinateSystems(); std::vector coordsNames; for (vtkm::IdComponent coordsIndex = 0; coordsIndex < numCoords; coordsIndex++) { coordsNames.push_back(partitionedDataSet.GetPartition(firstNonEmptyPartitionId) .GetCoordinateSystemName(coordsIndex)); } vtkm::Id numOfDataSet = partitionedDataSet.GetNumberOfPartitions(); for (vtkm::Id partitionIndex = firstNonEmptyPartitionId; partitionIndex < numOfDataSet; partitionIndex++) { if (partitionedDataSet.GetPartition(partitionIndex).GetNumberOfPoints() == 0) { //Skip the empty data sets in the partitioned data sets continue; } if (numCoords != partitionedDataSet.GetPartition(partitionIndex).GetNumberOfCoordinateSystems()) { throw vtkm::cont::ErrorExecution("Data sets have different number of coordinate systems"); } for (vtkm::IdComponent coordsIndex = 0; coordsIndex < numCoords; coordsIndex++) { if (!partitionedDataSet.GetPartition(partitionIndex) .HasCoordinateSystem(coordsNames[coordsIndex])) { throw vtkm::cont::ErrorExecution( "Coordinates system name: " + coordsNames[coordsIndex] + " in the first partition does not exist in other partitions"); } } } } void MergeFieldsAndAddIntoDataSet(vtkm::cont::DataSet& outputDataSet, const vtkm::cont::PartitionedDataSet partitionedDataSet, const vtkm::Id numPoints, const vtkm::Id numCells, const vtkm::Float64 invalidValue, const vtkm::Id firstNonEmptyPartitionId) { // Merging selected fields and coordinates // We get fields names in all partitions firstly // The inserted map stores the field name and a index of the first partition that owns that field vtkm::cont::Invoker invoke; auto associationHash = [](vtkm::cont::Field::Association const& association) { return static_cast(association); }; std::unordered_map, decltype(associationHash)> fieldsMap(2, associationHash); vtkm::Id numOfDataSet = partitionedDataSet.GetNumberOfPartitions(); for (vtkm::Id partitionIndex = firstNonEmptyPartitionId; partitionIndex < numOfDataSet; partitionIndex++) { if (partitionedDataSet.GetPartition(partitionIndex).GetNumberOfPoints() == 0) { continue; } vtkm::IdComponent numFields = partitionedDataSet.GetPartition(partitionIndex).GetNumberOfFields(); for (vtkm::IdComponent fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { const vtkm::cont::Field& field = partitionedDataSet.GetPartition(partitionIndex).GetField(fieldIndex); auto association = field.GetAssociation(); bool isSupported = (association == vtkm::cont::Field::Association::Points || association == vtkm::cont::Field::Association::Cells); if (!isSupported) { VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Skipping merge of field '" << field.GetName() << "' because it has an unsupported association."); } //Do not store the field index again if it exists in fieldMap if (fieldsMap[association].find(field.GetName()) != fieldsMap[association].end()) { continue; } fieldsMap[association][field.GetName()] = partitionIndex; } } // Iterate all fields and create merged field arrays for (auto fieldMapIter = fieldsMap.begin(); fieldMapIter != fieldsMap.end(); ++fieldMapIter) { auto fieldAssociation = fieldMapIter->first; auto fieldNamesMap = fieldMapIter->second; for (auto fieldNameIter = fieldNamesMap.begin(); fieldNameIter != fieldNamesMap.end(); ++fieldNameIter) { std::string fieldName = fieldNameIter->first; vtkm::Id partitionOwnsField = fieldNameIter->second; const vtkm::cont::Field& field = partitionedDataSet.GetPartition(partitionOwnsField).GetField(fieldName, fieldAssociation); vtkm::cont::UnknownArrayHandle mergedFieldArray = field.GetData().NewInstanceBasic(); if (fieldAssociation == vtkm::cont::Field::Association::Points) { mergedFieldArray.Allocate(numPoints); } else { //We may add a new association (such as edges or faces) in future VTKM_ASSERT(fieldAssociation == vtkm::cont::Field::Association::Cells); mergedFieldArray.Allocate(numCells); } //Merging each field into the mergedField array auto resolveType = [&](auto& concreteOut) { vtkm::Id offset = 0; for (vtkm::Id partitionIndex = firstNonEmptyPartitionId; partitionIndex < numOfDataSet; ++partitionIndex) { if (partitionedDataSet.GetPartition(partitionIndex).GetNumberOfPoints() == 0) { continue; } if (partitionedDataSet.GetPartition(partitionIndex).HasField(fieldName, fieldAssociation)) { vtkm::cont::UnknownArrayHandle in = partitionedDataSet.GetPartition(partitionIndex) .GetField(fieldName, fieldAssociation) .GetData(); vtkm::Id copySize = in.GetNumberOfValues(); auto viewOut = vtkm::cont::make_ArrayHandleView(concreteOut, offset, copySize); vtkm::cont::ArrayCopy(in, viewOut); offset += copySize; } else { //Creating invalid values for the partition that does not have the field using ComponentType = typename std::decay_t::ValueType::ComponentType; ComponentType castInvalid = vtkm::cont::internal::CastInvalidValue(invalidValue); vtkm::Id copySize = 0; if (fieldAssociation == vtkm::cont::Field::Association::Points) { copySize = partitionedDataSet.GetPartition(partitionIndex).GetNumberOfPoints(); } else { copySize = partitionedDataSet.GetPartition(partitionIndex).GetNumberOfCells(); } for (vtkm::IdComponent component = 0; component < concreteOut.GetNumberOfComponents(); ++component) { //Extracting each component from RecombineVec and copy invalid value into it //Avoid using invoke to call worklet on ArrayHandleRecombineVec (it may cause long compiling issue on CUDA 12.x). concreteOut.GetComponentArray(component).Fill(castInvalid, offset, offset + copySize); } offset += copySize; } } }; mergedFieldArray.CastAndCallWithExtractedArray(resolveType); outputDataSet.AddField(vtkm::cont::Field(fieldName, fieldAssociation, mergedFieldArray)); } } return; } } // anonymous namespace //----------------------------------------------------------------------------- namespace vtkm { namespace cont { VTKM_CONT vtkm::cont::DataSet MergePartitionedDataSet( const vtkm::cont::PartitionedDataSet& partitionedDataSet, vtkm::Float64 invalidValue) { vtkm::cont::DataSet outputData; //The name of coordinates system in the first non-empty partition will be used in merged data set vtkm::Id firstNonEmptyPartitionId = GetFirstEmptyPartition(partitionedDataSet); if (firstNonEmptyPartitionId == -1) { return outputData; } //Checking the name of coordinates system, if all partitions have different name with the firstNonEmptyPartitionId //just throw the exception now CheckCoordsNames(partitionedDataSet, firstNonEmptyPartitionId); //Checking if all partitions have CellSetSingleType with the same cell type bool allPartitionsAreSingleType = PartitionsAreSingleType(partitionedDataSet, firstNonEmptyPartitionId); vtkm::Id numPointsTotal; vtkm::Id numCellsTotal; CountPointsAndCells(partitionedDataSet, numPointsTotal, numCellsTotal); if (allPartitionsAreSingleType) { outputData.SetCellSet(MergeCellSetsSingleType(partitionedDataSet, firstNonEmptyPartitionId)); } else { outputData.SetCellSet(MergeCellSetsExplicit( partitionedDataSet, numPointsTotal, numCellsTotal, firstNonEmptyPartitionId)); } //Merging fields and coordinate systems MergeFieldsAndAddIntoDataSet(outputData, partitionedDataSet, numPointsTotal, numCellsTotal, invalidValue, firstNonEmptyPartitionId); //Labeling fields that belong to the coordinate system. //There might be multiple coordinates systems, assuming all partitions have the same name of the coordinates system vtkm::IdComponent numCoordsNames = partitionedDataSet.GetPartition(firstNonEmptyPartitionId).GetNumberOfCoordinateSystems(); for (vtkm::IdComponent i = 0; i < numCoordsNames; i++) { outputData.AddCoordinateSystem( partitionedDataSet.GetPartition(firstNonEmptyPartitionId).GetCoordinateSystemName(i)); } return outputData; } } }