/*========================================================================= Program: Visualization Toolkit Module: vtkDIYGhostUtilities.txx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm 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. =========================================================================*/ #ifndef vtkDIYGhostUtilities_txx #define vtkDIYGhostUtilities_txx #include "vtkDIYGhostUtilities.h" #include "vtkArrayDispatch.h" #include "vtkBoundingBox.h" #include "vtkCellData.h" #include "vtkCommunicator.h" #include "vtkDIYExplicitAssigner.h" #include "vtkDIYUtilities.h" #include "vtkDataArrayRange.h" #include "vtkDataSetAttributes.h" #include "vtkDataSetSurfaceFilter.h" #include "vtkFieldData.h" #include "vtkIdList.h" #include "vtkImageData.h" #include "vtkLogger.h" #include "vtkMultiProcessController.h" #include "vtkNew.h" #include "vtkPointData.h" #include "vtkPoints.h" #include "vtkPolyData.h" #include "vtkRectilinearGrid.h" #include "vtkSMPTools.h" #include "vtkStructuredData.h" #include "vtkStructuredGrid.h" #include "vtkUnsignedCharArray.h" #include "vtkUnstructuredGrid.h" #include #include #include #include #include #include // clang-format off #include "vtk_diy2.h" #include VTK_DIY2(diy/assigner.hpp) #include VTK_DIY2(diy/link.hpp) #include VTK_DIY2(diy/master.hpp) #include VTK_DIY2(diy/mpi.hpp) #include VTK_DIY2(diy/partners/all-reduce.hpp) #include VTK_DIY2(diy/reduce-operations.hpp) // clang-format on //============================================================================ template <> struct vtkDIYGhostUtilities::DataSetTypeToBlockTypeConverter { typedef ImageDataBlock BlockType; static constexpr bool IsUnstructuredData = false; }; //============================================================================ template <> struct vtkDIYGhostUtilities::DataSetTypeToBlockTypeConverter { typedef RectilinearGridBlock BlockType; static constexpr bool IsUnstructuredData = false; }; //============================================================================ template <> struct vtkDIYGhostUtilities::DataSetTypeToBlockTypeConverter { typedef StructuredGridBlock BlockType; static constexpr bool IsUnstructuredData = false; }; //============================================================================ template <> struct vtkDIYGhostUtilities::DataSetTypeToBlockTypeConverter { typedef UnstructuredGridBlock BlockType; static constexpr bool IsUnstructuredData = true; }; //============================================================================ template <> struct vtkDIYGhostUtilities::DataSetTypeToBlockTypeConverter { typedef PolyDataBlock BlockType; static constexpr bool IsUnstructuredData = true; }; namespace vtkDIYGhostUtilities_detail { //============================================================================ template ::is_integer> struct Limits; //============================================================================ template struct Limits { static constexpr ValueT Epsilon = 0; static constexpr ValueT Min = 0; }; //============================================================================ template struct Limits { static constexpr ValueT Epsilon = std::numeric_limits::epsilon(); static constexpr ValueT Min = std::numeric_limits::min(); }; //---------------------------------------------------------------------------- template ValueT ComputePrecision(ValueT val) { constexpr ValueT Epsilon = Limits::Epsilon; constexpr ValueT Min = Limits::Min; return std::max(val * Epsilon, Min); } //============================================================================ template struct ConvertIdToCoordinatesFromCroppedExtentToFullExtentWorker; //============================================================================ template <> struct ConvertIdToCoordinatesFromCroppedExtentToFullExtentWorker { vtkIdType operator()(vtkIdType id, const vtkDIYGhostUtilities::ExtentType& fullExtent, const vtkDIYGhostUtilities::ExtentType& croppedExtent) { int ijk[3]; vtkStructuredData::ComputeCellStructuredCoordsForExtent(id, croppedExtent.data(), ijk); return vtkStructuredData::ComputeCellIdForExtent(fullExtent.data(), ijk); } }; //============================================================================ template <> struct ConvertIdToCoordinatesFromCroppedExtentToFullExtentWorker { vtkIdType operator()(vtkIdType id, const vtkDIYGhostUtilities::ExtentType& fullExtent, const vtkDIYGhostUtilities::ExtentType& croppedExtent) { int ijk[3]; vtkStructuredData::ComputePointStructuredCoordsForExtent(id, croppedExtent.data(), ijk); return vtkStructuredData::ComputePointIdForExtent(fullExtent.data(), ijk); } }; //============================================================================ template struct GhostFinder { using ExtentType = vtkDIYGhostUtilities::ExtentType; GhostFinder(vtkUnsignedCharArray* ghosts, const ExtentType& fullExtent, const ExtentType& croppedExtent, unsigned char ghostTarget) : Ghosts(ghosts) , FullExtent(fullExtent) , CroppedExtent(croppedExtent) , GhostTarget(ghostTarget) , FoundGhost(false) { } void operator()(vtkIdType startId, vtkIdType endId) { auto ghosts = vtk::DataArrayValueRange<1>(this->Ghosts); using RefType = typename decltype(ghosts)::ReferenceType; for (vtkIdType id = startId; id < endId; ++id) { if (this->FoundGhost) { return; } ConvertIdToCoordinatesFromCroppedExtentToFullExtentWorker worker; vtkIdType remappedId = worker(id, this->FullExtent, this->CroppedExtent); RefType ghost = ghosts[remappedId]; if (ghost & this->GhostTarget) { this->FoundGhost = true; } } } vtkUnsignedCharArray* Ghosts; const ExtentType& FullExtent; const ExtentType& CroppedExtent; unsigned char GhostTarget; std::atomic FoundGhost; }; static constexpr unsigned char GHOST_CELL_BIT = 1; static constexpr unsigned char GHOST_POINT_BIT = 2; //---------------------------------------------------------------------------- template unsigned char CleanGhostsReduceAllForStructuredData( diy::Master& master, diy::Assigner& assigner, diy::RegularAllReducePartners& partners) { using BlockType = typename vtkDIYGhostUtilities::DataSetTypeToBlockTypeConverter::BlockType; using ExtentType = vtkDIYGhostUtilities::ExtentType; unsigned char cleanGhostMask = 0; diy::reduce(master, assigner, partners, [&](BlockType* block, const diy::ReduceProxy& rp, const diy::RegularAllReducePartners&) { // step 1 dequeue and merge for (int i = 0; i < rp.in_link().size(); ++i) { int gid = rp.in_link().target(i).gid; unsigned char currentMask; rp.dequeue(gid, ¤tMask, 1); cleanGhostMask |= currentMask; } unsigned char foundGhost = 0; const ExtentType& extent = block->Information.Extent; StructuredDataSetT* ds = block->Information.Input; const int* dsExtent = ds->GetExtent(); const ExtentType fullExtent{ dsExtent[0], dsExtent[1], dsExtent[2], dsExtent[3], dsExtent[4], dsExtent[5] }; if (vtkUnsignedCharArray* ghostCells = ds->GetCellGhostArray()) { const vtkIdType numberOfCells = vtkStructuredData::GetNumberOfCells(extent.data()); vtkDIYGhostUtilities_detail::GhostFinder worker(ghostCells, fullExtent, extent, vtkDataSetAttributes::HIDDENCELL | vtkDataSetAttributes::DUPLICATECELL); vtkSMPTools::For(0, numberOfCells, worker); foundGhost |= GHOST_CELL_BIT * worker.FoundGhost; } if (vtkUnsignedCharArray* ghostPoints = ds->GetPointGhostArray()) { const vtkIdType numberOfPoints = vtkStructuredData::GetNumberOfPoints(extent.data()); vtkDIYGhostUtilities_detail::GhostFinder worker(ghostPoints, fullExtent, extent, vtkDataSetAttributes::HIDDENPOINT | vtkDataSetAttributes::DUPLICATEPOINT); vtkSMPTools::For(0, numberOfPoints, worker); foundGhost |= GHOST_POINT_BIT * worker.FoundGhost; } // step 2 enqueue for (int i = 0; i < rp.out_link().size(); ++i) { rp.enqueue(rp.out_link().target(i), &foundGhost, 1); } cleanGhostMask |= foundGhost; }); return cleanGhostMask; } //============================================================================ template struct CleanGhostsReduceAllWorkerImpl; //============================================================================ template <> struct CleanGhostsReduceAllWorkerImpl { template unsigned char operator()(diy::Master&, diy::Assigner&, diy::RegularAllReducePartners&) { return 0; } }; //============================================================================ template <> struct CleanGhostsReduceAllWorkerImpl { template unsigned char operator()( diy::Master& master, diy::Assigner& assigner, diy::RegularAllReducePartners& partners) { return vtkDIYGhostUtilities_detail::CleanGhostsReduceAllForStructuredData( master, assigner, partners); } }; //============================================================================ template struct CleanGhostsReduceAllWorker { using DataSetInfo = vtkDIYGhostUtilities::DataSetTypeToBlockTypeConverter; using BlockType = typename DataSetInfo::BlockType; static constexpr bool IsUnstructuredData = DataSetInfo::IsUnstructuredData; unsigned char operator()( diy::Master& master, diy::Assigner& assigner, diy::RegularAllReducePartners& partners) { return this->Impl.template operator()(master, assigner, partners); } CleanGhostsReduceAllWorkerImpl Impl; }; //============================================================================ template vtkSmartPointer RemoveGhostArraysIfNeeded( DataSetT* input, unsigned char ghostCleaningMask) { vtkSmartPointer ds = vtkSmartPointer::New(); ds->ShallowCopy(input); if (!(ghostCleaningMask & GHOST_CELL_BIT)) { ds->GetCellData()->RemoveArray(vtkDataSetAttributes::GhostArrayName()); } if (!(ghostCleaningMask & GHOST_POINT_BIT)) { ds->GetPointData()->RemoveArray(vtkDataSetAttributes::GhostArrayName()); } return ds; } } // namesapce vtkDIYGhostUtilities_detail //---------------------------------------------------------------------------- template void vtkDIYGhostUtilities::ExchangeBoundingBoxes( diy::Master& master, const vtkDIYExplicitAssigner& assigner, std::vector& inputs) { using BlockType = typename DataSetTypeToBlockTypeConverter::BlockType; diy::all_to_all( master, assigner, [&master, &inputs](BlockType* block, const diy::ReduceProxy& srp) { int myBlockId = srp.gid(); int localId = master.lid(myBlockId); auto& input = inputs[localId]; if (srp.round() == 0) { const double* bounds = input->GetBounds(); vtkBoundingBox& bb = block->BoundingBox; bb = vtkBoundingBox(bounds); vtkDIYGhostUtilities::InflateBoundingBoxIfNecessary(input, bb); for (int i = 0; i < srp.out_link().size(); ++i) { const diy::BlockID& blockId = srp.out_link().target(i); if (blockId.gid != myBlockId) { srp.enqueue(blockId, bb.GetMinPoint(), 3); srp.enqueue(blockId, bb.GetMaxPoint(), 3); } } } else { double minPoint[3], maxPoint[3]; for (int i = 0; i < static_cast(srp.in_link().size()); ++i) { const diy::BlockID& blockId = srp.in_link().target(i); if (blockId.gid != myBlockId) { srp.dequeue(blockId, minPoint, 3); srp.dequeue(blockId, maxPoint, 3); block->NeighborBoundingBoxes.emplace(blockId.gid, vtkBoundingBox( minPoint[0], maxPoint[0], minPoint[1], maxPoint[1], minPoint[2], maxPoint[2])); } } } }); } //---------------------------------------------------------------------------- template void vtkDIYGhostUtilities::ExchangeGhosts(diy::Master& master, std::vector& inputs) { using BlockType = typename DataSetTypeToBlockTypeConverter::BlockType; master.foreach ([&master, &inputs](BlockType* block, const diy::Master::ProxyWithLink& cp) { int myBlockId = cp.gid(); int localId = master.lid(myBlockId); auto& input = inputs[localId]; for (int id = 0; id < static_cast(cp.link()->size()); ++id) { const diy::BlockID& blockId = cp.link()->target(id); vtkDIYGhostUtilities::EnqueueGhosts(cp, blockId, input, block); } }); master.exchange(); master.foreach ([](BlockType* block, const diy::Master::ProxyWithLink& cp) { std::vector incoming; cp.incoming(incoming); for (const int& gid : incoming) { // we need this extra check because incoming is not empty when using only one block if (!cp.incoming(gid).empty()) { vtkDIYGhostUtilities::DequeueGhosts(cp, gid, block->BlockStructures.at(gid)); } } }); } //---------------------------------------------------------------------------- template vtkDIYGhostUtilities::LinkMap vtkDIYGhostUtilities::ComputeLinkMapUsingBoundingBoxes( const diy::Master& master) { LinkMap linkMap(master.size()); for (int localId = 0; localId < static_cast(master.size()); ++localId) { Links& links = linkMap[localId]; BlockT* block = master.block(localId); vtkBoundingBox& localbb = block->BoundingBox; BlockMapType& bb = block->NeighborBoundingBoxes; for (auto item : bb) { if (localbb.Intersects(item.second)) { links.insert(item.first); } } } return linkMap; } //---------------------------------------------------------------------------- template void vtkDIYGhostUtilities::CopyInputsAndAllocateGhosts(diy::Master& master, diy::Assigner& assigner, diy::RegularAllReducePartners& partners, std::vector& inputs, std::vector& outputs, int outputGhostLevels) { using BlockType = typename DataSetTypeToBlockTypeConverter::BlockType; vtkDIYGhostUtilities_detail::CleanGhostsReduceAllWorker cleaner; unsigned char ghostCleaningMask = cleaner(master, assigner, partners); for (int localId = 0; localId < static_cast(inputs.size()); ++localId) { DataSetT* input = inputs[localId]; DataSetT* output = outputs[localId]; BlockType* block = master.block(localId); vtkSmartPointer cleanedInput = vtkDIYGhostUtilities_detail::RemoveGhostArraysIfNeeded(input, ghostCleaningMask); // If we are isolated (no connection with other blocks), just shallow-copy if (block->BlockStructures.empty()) { output->ShallowCopy(cleanedInput); } // If we fetch 0 levels of ghosts AND there were no ghost cells in the input, // we can partially shallow copy (we can't shallow copy points as the interfaces // can be written over by other blocks) else if (outputGhostLevels == 0 && !block->Information.InputNeedsGhostsPeeledOff() && !(ghostCleaningMask & vtkDIYGhostUtilities_detail::GHOST_CELL_BIT)) { output->CopyStructure(input); output->GetPointData()->DeepCopy(cleanedInput->GetPointData()); output->GetCellData()->ShallowCopy(cleanedInput->GetCellData()); output->GetFieldData()->ShallowCopy(input->GetFieldData()); } // In the general case, deep copy the input and allocate the geometry // for the new ghost cells. else { vtkDIYGhostUtilities::DeepCopyInputAndAllocateGhosts(block, cleanedInput, output); } } } //---------------------------------------------------------------------------- template void vtkDIYGhostUtilities::AddGhostArrays(diy::Master& master, std::vector& outputs) { using BlockType = typename DataSetTypeToBlockTypeConverter::BlockType; for (int localId = 0; localId < static_cast(outputs.size()); ++localId) { DataSetT* output = outputs[localId]; BlockType* block = master.block(localId); output->GetPointData()->AddArray(block->GhostPointArray); if (block->GhostCellArray) { output->GetCellData()->AddArray(block->GhostCellArray); } } } //---------------------------------------------------------------------------- template void vtkDIYGhostUtilities::InitializeGhostArrays( diy::Master& master, std::vector& outputs, int outputGhostLevels) { using BlockType = typename DataSetTypeToBlockTypeConverter::BlockType; for (int localId = 0; localId < static_cast(outputs.size()); ++localId) { DataSetT* output = outputs[localId]; BlockType* block = master.block(localId); if (outputGhostLevels != 0) { vtkDIYGhostUtilities::InitializeGhostCellArray(block, output); } vtkDIYGhostUtilities::InitializeGhostPointArray(block, output); } } //---------------------------------------------------------------------------- template void vtkDIYGhostUtilities::InitializeGhostCellArray( typename DataSetTypeToBlockTypeConverter::BlockType* block, DataSetT* output) { vtkSmartPointer& ghostCellArray = block->GhostCellArray; ghostCellArray = output->GetGhostArray(vtkDataObject::FIELD_ASSOCIATION_CELLS); if (!ghostCellArray) { ghostCellArray = vtkSmartPointer::New(); ghostCellArray->SetName(vtkDataSetAttributes::GhostArrayName()); ghostCellArray->SetNumberOfComponents(1); ghostCellArray->SetNumberOfValues(output->GetNumberOfCells()); ghostCellArray->Fill(0); } else { vtkDIYGhostUtilities::ReinitializeSelectedBits( ghostCellArray, vtkDataSetAttributes::DUPLICATECELL); } } //---------------------------------------------------------------------------- template void vtkDIYGhostUtilities::InitializeGhostPointArray( typename DataSetTypeToBlockTypeConverter::BlockType* block, DataSetT* output) { vtkSmartPointer& ghostPointArray = block->GhostPointArray; ghostPointArray = output->GetGhostArray(vtkDataObject::FIELD_ASSOCIATION_POINTS); if (!ghostPointArray) { ghostPointArray = vtkSmartPointer::New(); ghostPointArray->SetName(vtkDataSetAttributes::GhostArrayName()); ghostPointArray->SetNumberOfComponents(1); ghostPointArray->SetNumberOfValues(output->GetNumberOfPoints()); ghostPointArray->Fill(0); } else { vtkDIYGhostUtilities::ReinitializeSelectedBits( ghostPointArray, vtkDataSetAttributes::DUPLICATEPOINT); } } //---------------------------------------------------------------------------- /** * Main pipeline generating ghosts. */ template int vtkDIYGhostUtilities::GenerateGhostCells(std::vector& inputs, std::vector& outputs, int outputGhostLevels, vtkMultiProcessController* controller) { static_assert((std::is_base_of::value || std::is_base_of::value || std::is_base_of::value || std::is_base_of::value || std::is_base_of::value), "Input data set type is not supported."); using BlockType = typename DataSetTypeToBlockTypeConverter::BlockType; const int size = static_cast(inputs.size()); if (size != static_cast(outputs.size())) { return 0; } std::string logMessage = size ? std::string("Generating ghosts for ") + std::string(outputs[0]->GetClassName()) : std::string("No ghosts to generate for empty rank"); vtkLogStartScope(TRACE, logMessage.c_str()); vtkDIYGhostUtilities::CloneGeometricStructures(inputs, outputs); vtkLogStartScope(TRACE, "Instantiating diy communicator"); diy::mpi::communicator comm = vtkDIYUtilities::GetCommunicator(controller); vtkLogEndScope("Instantiating diy communicator"); vtkLogStartScope(TRACE, "Instantiating master"); diy::Master master( comm, 1, -1, []() { return static_cast(new BlockType()); }, [](void* b) -> void { delete static_cast(b); }); vtkLogEndScope("Instantiating master"); vtkLogStartScope(TRACE, "Instantiating assigner"); vtkDIYExplicitAssigner assigner(comm, size); vtkLogEndScope("Instantiating assigner"); if (!size) { // In such instance, we can just terminate. We are empty an finished communicating with other // ranks. vtkLogEndScope(logMessage.c_str()); return 1; } vtkLogStartScope(TRACE, "Decomposing master"); diy::RegularDecomposer decomposer( /*dim*/ 1, diy::interval(0, assigner.nblocks() - 1), assigner.nblocks()); decomposer.decompose(comm.rank(), assigner, master); vtkLogEndScope("Decomposing master"); diy::RegularAllReducePartners partners(decomposer, 2); // At this step, we gather data from the inputs and store it inside the local blocks // so we don't have to carry extra parameters later. vtkLogStartScope(TRACE, "Setup block self information."); vtkDIYGhostUtilities::InitializeBlocks(master, inputs); vtkLogEndScope("Setup block self information."); vtkLogStartScope(TRACE, "Exchanging bounding boxes"); vtkDIYGhostUtilities::ExchangeBoundingBoxes(master, assigner, inputs); vtkLogEndScope("Exchanging bounding boxes"); // We compute a temporary link map that weeds out data sets that do not have // overlapping bounding boxes. vtkLogStartScope(TRACE, "Computing temporary link map using bounding boxes."); LinkMap temporaryLinkMap = vtkDIYGhostUtilities::ComputeLinkMapUsingBoundingBoxes(master); vtkLogEndScope("Computing temporary link map using bounding boxes."); // We link our blocks using the temporary link map vtkLogStartScope(TRACE, "Relinking blocks using temporary link map"); vtkDIYUtilities::Link(master, assigner, temporaryLinkMap); vtkLogEndScope("Relinking blocks using temporary link map"); // Here, we exchange structural information between blocks that will be used to // determine if blocks are actually adjacent or not. vtkLogStartScope(TRACE, "Exchanging block structures"); vtkDIYGhostUtilities::ExchangeBlockStructures(master, inputs); vtkLogEndScope("Exchanging block structures"); // The structural information that has been exchanged is used to compute // the final link map, mapping blocks that will actually exchange ghosts. vtkLogStartScope(TRACE, "Creating link map between connected blocks"); LinkMap linkMap = vtkDIYGhostUtilities::ComputeLinkMap(master, inputs, outputGhostLevels); vtkLogEndScope("Creating link map between connected blocks"); vtkLogStartScope(TRACE, "Relinking blocks using link map"); vtkDIYUtilities::Link(master, assigner, linkMap); vtkLogEndScope("Relinking blocks using link map"); vtkLogStartScope(TRACE, "Exchanging ghost data between blocks"); vtkDIYGhostUtilities::ExchangeGhosts(master, inputs); vtkLogEndScope("Exchanging ghost data between blocks"); vtkLogStartScope(TRACE, "Allocating ghosts in outputs"); vtkDIYGhostUtilities::CopyInputsAndAllocateGhosts( master, assigner, partners, inputs, outputs, outputGhostLevels); vtkLogEndScope("Allocating ghosts in outputs"); vtkLogStartScope(TRACE, "Initializing ghost arrays in outputs"); vtkDIYGhostUtilities::InitializeGhostArrays(master, outputs, outputGhostLevels); vtkLogEndScope("Initializing ghost arrays in outputs"); vtkLogStartScope(TRACE, "Filling local ghosts with received data from other blocks"); vtkDIYGhostUtilities::FillGhostArrays(master, outputs, outputGhostLevels); vtkLogEndScope("Filling local ghosts with received data from other blocks"); vtkLogStartScope(TRACE, "Adding ghost arrays to point and / or cell data"); vtkDIYGhostUtilities::AddGhostArrays(master, outputs); vtkLogEndScope("Adding ghost arrays to point and / or cell data"); vtkLogEndScope(logMessage.c_str()); return 1; } #endif