//============================================================================ // 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. //============================================================================ // Copyright (c) 2018, The Regents of the University of California, through // Lawrence Berkeley National Laboratory (subject to receipt of any required approvals // from the U.S. Dept. of Energy). All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // (1) Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // (2) Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // (3) Neither the name of the University of California, Lawrence Berkeley National // Laboratory, U.S. Dept. of Energy nor the names of its contributors may be // used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED // OF THE POSSIBILITY OF SUCH DAMAGE. // //============================================================================= // // This code is an extension of the algorithm presented in the paper: // Parallel Peak Pruning for Scalable SMP Contour Tree Computation. // Hamish Carr, Gunther Weber, Christopher Sewell, and James Ahrens. // Proceedings of the IEEE Symposium on Large Data Analysis and Visualization // (LDAV), October 2016, Baltimore, Maryland. // // The PPP2 algorithm and software were jointly developed by // Hamish Carr (University of Leeds), Gunther H. Weber (LBNL), and // Oliver Ruebel (LBNL) //============================================================================== #ifndef vtk_m_worklet_contourtree_distributed_computedistributedcontourtreefunctor_h #define vtk_m_worklet_contourtree_distributed_computedistributedcontourtreefunctor_h #include #include #include #include // clang-format off VTKM_THIRDPARTY_PRE_INCLUDE #include VTKM_THIRDPARTY_POST_INCLUDE // clang-format on namespace vtkm { namespace worklet { namespace contourtree_distributed { /// Functor used by DIY reduce the merge data blocks in parallel template class ComputeDistributedContourTreeFunctor { public: /// Create the functor /// @param[in] globalSize Global extends of the input mesh (i.e., number of mesh points in each dimension) /// @param[in] timingsLogLevel Set the vtkm::cont:LogLevel to be used to record timings information /// specific to the computation of the hierachical contour tree /// @param[in] treeLogLevel Set the vtkm::cont:LogLevel to be used to record metadata information /// about the various trees computed as part of the hierarchical contour tree compute ComputeDistributedContourTreeFunctor( vtkm::Id3 globalSize, bool useBoundaryExtremaOnly, vtkm::cont::LogLevel timingsLogLevel = vtkm::cont::LogLevel::Perf, vtkm::cont::LogLevel treeLogLevel = vtkm::cont::LogLevel::Info) : GlobalSize(globalSize) , UseBoundaryExtremaOnly(useBoundaryExtremaOnly) , TimingsLogLevel(timingsLogLevel) , TreeLogLevel(treeLogLevel) { } void operator()( vtkm::worklet::contourtree_distributed::DistributedContourTreeBlockData* block, // local Block. const vtkmdiy::ReduceProxy& rp, // communication proxy const vtkmdiy::RegularSwapPartners& // partners of the current block (unused) ) const { // Track timing of main steps vtkm::cont::Timer totalTimer; // Total time for each call totalTimer.Start(); vtkm::cont::Timer timer; // Time individual steps timer.Start(); std::stringstream timingsStream; // Get our rank and DIY id const vtkm::Id rank = vtkm::cont::EnvironmentTracker::GetCommunicator().rank(); const auto selfid = rp.gid(); // Here we do the deque first before the send due to the way the iteration is handled in DIY, i.e., in each iteration // A block needs to first collect the data from its neighours and then send the combined block to its neighbours // for the next iteration. // 1. dequeue the block and compute the new contour tree and contour tree mesh for the block if we have the hight GID std::vector incoming; rp.incoming(incoming); // log the time for getting the data from DIY timingsStream << " " << std::setw(38) << std::left << "DIY Incoming Data" << ": " << timer.GetElapsedTime() << " seconds" << std::endl; timer.Start(); // Compute the joint contour tree for (const int ingid : incoming) { // NOTE/IMPORTANT: In each round we should have only one swap partner (despite for-loop here). // If that assumption does not hold, it will break things. // NOTE/IMPORTANT: This assumption only holds if the number of blocks is a power of two. // Otherwise, we may need to process more than one incoming block if (ingid != selfid) { vtkm::cont::Timer loopTimer; // time the steps of this loop loopTimer.Start(); vtkm::Id3 otherBlockOrigin; rp.dequeue(ingid, otherBlockOrigin); vtkm::Id3 otherBlockSize; rp.dequeue(ingid, otherBlockSize); vtkm::worklet::contourtree_augmented::ContourTreeMesh otherContourTreeMesh; rp.dequeue(ingid, otherContourTreeMesh); timingsStream << " Subphase of Merge Block" << std::endl; timingsStream << " |-->" << std::setw(38) << std::left << "DIY Deque Data" << ": " << loopTimer.GetElapsedTime() << " seconds" << std::endl; loopTimer.Start(); #ifdef DEBUG_PRINT_CTUD VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Local block has extents: " << block->BlockOrigin << " " << block->BlockSize << std::endl << "Combining with block received from ID " << ingid << " with extents: " << otherBlockOrigin << " " << otherBlockSize << std::endl); #endif // Merge the two contour tree meshes block->ContourTreeMeshes.back().MergeWith(otherContourTreeMesh); timingsStream << " |-->" << std::setw(38) << std::left << "Merge Contour Tree Mesh" << ": " << loopTimer.GetElapsedTime() << " seconds" << std::endl; loopTimer.Start(); #ifdef DEBUG_PRINT_CTUD // save the corresponding .gv file for the contour tree mesh std::string contourTreeMeshFileName = std::string("Rank_") + std::to_string(static_cast(rank)) + std::string("_Block_") + std::to_string(static_cast(block->BlockIndex)) + "_Round_" + std::to_string(rp.round()) + "_Partner_" + std::to_string(ingid) + std::string("_Step_0_Combined_Mesh.gv"); std::string contourTreeMeshLabel = std::string("Block ") + std::to_string(static_cast(block->BlockIndex)) + " Round " + std::to_string(rp.round()) + " Partner " + std::to_string(ingid) + std::string(" Step 0 Combined Mesh"); std::string contourTreeMeshString = vtkm::worklet::contourtree_distributed::ContourTreeMeshDotGraphPrint( contourTreeMeshLabel, block->ContourTreeMeshes.back(), worklet::contourtree_distributed::SHOW_CONTOUR_TREE_MESH_ALL); std::ofstream contourTreeMeshFile(contourTreeMeshFileName); contourTreeMeshFile << contourTreeMeshString; timingsStream << " |-->" << std::setw(38) << std::left << "Save Contour Tree Mesh Dot" << ": " << loopTimer.GetElapsedTime() << " seconds" << std::endl; loopTimer.Start(); #endif // Compute the origin and size of the new block vtkm::Id3 currBlockOrigin{ std::min(otherBlockOrigin[0], block->BlockOrigin[0]), std::min(otherBlockOrigin[1], block->BlockOrigin[1]), std::min(otherBlockOrigin[2], block->BlockOrigin[2]), }; vtkm::Id3 currBlockMaxIndex{ // Needed only to compute the block size std::max(otherBlockOrigin[0] + otherBlockSize[0], block->BlockOrigin[0] + block->BlockSize[0]), std::max(otherBlockOrigin[1] + otherBlockSize[1], block->BlockOrigin[1] + block->BlockSize[1]), std::max(otherBlockOrigin[2] + otherBlockSize[2], block->BlockOrigin[2] + block->BlockSize[2]) }; vtkm::Id3 currBlockSize{ currBlockMaxIndex[0] - currBlockOrigin[0], currBlockMaxIndex[1] - currBlockOrigin[1], currBlockMaxIndex[2] - currBlockOrigin[2] }; // Compute the contour tree from our merged mesh vtkm::Id currNumIterations; block->ContourTrees.emplace_back(); // Create new empty contour tree object vtkm::worklet::contourtree_augmented::IdArrayType currSortOrder; vtkm::worklet::ContourTreeAugmented worklet; worklet.TimingsLogLevel = vtkm::cont::LogLevel::Off; // disable the print logging, we'll print this later vtkm::Id3 maxIdx{ currBlockOrigin[0] + currBlockSize[0] - 1, currBlockOrigin[1] + currBlockSize[1] - 1, currBlockOrigin[2] + currBlockSize[2] - 1 }; auto meshBoundaryExecObj = block->ContourTreeMeshes.back().GetMeshBoundaryExecutionObject( this->GlobalSize, currBlockOrigin, maxIdx); worklet.Run(block->ContourTreeMeshes.back() .SortedValues, // Unused param. Provide something to keep the API happy block->ContourTreeMeshes.back(), block->ContourTrees.back(), currSortOrder, currNumIterations, 1, // Fully augmented meshBoundaryExecObj); // Update block extents block->BlockOrigin = currBlockOrigin; block->BlockSize = currBlockSize; timingsStream << " |-->" << std::setw(38) << std::left << "Compute Joint Contour Tree" << ": " << loopTimer.GetElapsedTime() << " seconds" << std::endl; loopTimer.Start(); #ifdef DEBUG_PRINT_CTUD /* // TODO: GET THIS COMPILING. NEED TO LIKELY PUT THIS IN A SEPARATE FUNCTION TO GET THE STORAGE TYPE TEMPLATE PARAMETER // TODO/FIXME: At this time we should only be dealing with contour tree meshes and not possibly other mesh types, // and block does not have a Meshes member. Shouldn't this all be ContourTreeMesh instead? // and the ones for the contour tree regular and superstructures std::string regularStructureFileName = std::string("Rank_") + std::to_string(static_cast(rank)) + std::string("_Block_") + std::to_string(static_cast(block->BlockIndex)) + "_Round_" + std::to_string(rp.round()) + " Partner " + std::to_string(ingid) + std::string("_Step_1_Contour_Tree_Regular_Structure.gv"); std::string regularStructureLabel = std::string("Block ") + std::to_string(static_cast(block->BlockIndex)) + " Round " + std::to_string(rp.round()) + " Partner " + std::to_string(ingid) + std::string(" Step 1 Contour Tree Regular Structure"); std::string regularStructureString = worklet::contourtree_distributed::ContourTreeDotGraphPrint < FieldType, MeshType, vtkm::worklet::contourtree_augmented::IdArrayType()( regularStructureLabel, block->Meshes.back(), block->ContourTrees.back(), worklet::contourtree_distributed::SHOW_REGULAR_STRUCTURE | worklet::contourtree_distributed::SHOW_ALL_IDS); std::ofstream regularStructureFile(regularStructureFileName); regularStructureFile << regularStructureString; std::string superStructureFileName = std::string("Rank_") + std::to_string(static_cast(rank)) + std::string("_Block_") + std::to_string(static_cast(block->BlockIndex)) + "_Round_" + std::to_string(rp.round()) + " Partner " + std::to_string(ingid) + std::string("_Step_2_Contour_Tree_Super_Structure.gv"); std::ofstream superStructureFile(superStructureFileName); superStructureFile << worklet::contourtree_distributed::ContourTreeDotGraphPrint < T, MeshType, vtkm::worklet::contourtree_augmented::IdArrayType()( std::string("Block ") + std::to_string(static_cast(block->BlockIndex)) + " Round " + std::to_string(rp.round()) + " Partner " + std::to_string(ingid) + std::string(" Step 2 Contour Tree Super Structure"), block->Meshes.back(), block->ContourTrees.back(), worklet::contourtree_distributed::SHOW_SUPER_STRUCTURE | worklet::contourtree_distributed::SHOW_HYPER_STRUCTURE | worklet::contourtree_distributed::SHOW_ALL_IDS | worklet::contourtree_distributed::SHOW_ALL_SUPERIDS | worklet::contourtree_distributed::SHOW_ALL_HYPERIDS); */ #endif // Log the contour tree timiing stats (void)rank; // Suppress unused variable warning if logging is disabled. VTKM_LOG_S(this->TimingsLogLevel, std::endl << " ---------------- Contour Tree Worklet Timings ------------------" << std::endl << " Rank : " << rank << std::endl << " DIY Id : " << selfid << std::endl << " Round : " << rp.round() << std::endl << worklet.TimingsLogString); // Log the contour tree size stats VTKM_LOG_S(this->TreeLogLevel, std::endl << " ---------------- Contour Tree Array Sizes ---------------------" << std::endl << " Rank : " << rank << std::endl << " DIY Id : " << selfid << std::endl << " Round : " << rp.round() << std::endl << block->ContourTrees.back().PrintArraySizes()); } // end if (ingid != selfid) } // end for // log the time needed to compute the local contour tree timingsStream << " " << std::setw(38) << std::left << "Merge Block (Compute Joint Tree)" << ": " << timer.GetElapsedTime() << " seconds" << std::endl; timer.Start(); // If we are not in the first round (contour tree mesh for that round was pre-computed // in filter outside functor) and if we are sending to someone else (i.e., not in // last round) then compute contour tree mesh to send and save it. if (rp.round() != 0 && rp.out_link().size() != 0) { vtkm::Id3 maxIdx{ block->BlockOrigin[0] + block->BlockSize[0] - 1, block->BlockOrigin[1] + block->BlockSize[1] - 1, block->BlockOrigin[2] + block->BlockSize[2] - 1 }; // Compute BRACT vtkm::worklet::contourtree_distributed::BoundaryTree boundaryTree; // ... Get the mesh boundary object auto meshBoundaryExecObj = block->ContourTreeMeshes.back().GetMeshBoundaryExecutionObject( this->GlobalSize, block->BlockOrigin, maxIdx); // Make the BRACT and InteriorForest (i.e., residue) block->InteriorForests.emplace_back(); auto boundaryTreeMaker = vtkm::worklet::contourtree_distributed::BoundaryTreeMaker< vtkm::worklet::contourtree_augmented::ContourTreeMesh, vtkm::worklet::contourtree_augmented::MeshBoundaryContourTreeMeshExec>( &(block->ContourTreeMeshes.back()), meshBoundaryExecObj, block->ContourTrees.back(), &boundaryTree, &(block->InteriorForests.back())); // Construct the BRACT and InteriorForest. Since we are working on a ContourTreeMesh we do // not need to provide and IdRelabeler here in order to compute the InteriorForest boundaryTreeMaker.Construct(nullptr, this->UseBoundaryExtremaOnly); // Construct contour tree mesh from BRACT block->ContourTreeMeshes.emplace_back( boundaryTree.VertexIndex, boundaryTree.Superarcs, block->ContourTreeMeshes.back()); #ifdef DEBUG_PRINT_CTUD /* // TODO: GET THIS COMPILING. // TODO/FIXME: Need to get inggid here somehow // save the Boundary Tree as a dot file std::string boundaryTreeFileName = std::string("Rank_") + std::to_string(static_cast(rank)) + std::string("_Block_") + std::to_string(static_cast(block->BlockIndex)) + "_Round_" + std::to_string(rp.round()) + "_Partner_" + std::to_string(ingid) + std::string("_Step_3_Boundary_Tree.gv"); std::ofstream boundaryTreeFile(boundaryTreeFileName); boundaryTreeFile << vtkm::worklet::contourtree_distributed::BoundaryTreeDotGraphPrint (std::string("Block ") + std::to_string(static_cast(block->BlockIndex)) + " Round " + std::to_string(rp.round()) + " Partner " + std::to_string(ingid) + std::string(" Step 3 Boundary Tree"), block->Meshes.back()], block->BoundaryTrees.back()); // and save the Interior Forest as another dot file std::string interiorForestFileName = std::string("Rank_") + std::to_string(static_cast(rank)) + std::string("_Block_") + std::to_string(static_cast(block->BlockIndex)) + "_Round_" + std::to_string(rp.round()) + "_Partner_" + std::to_string(ingid) + std::string("_Step_4_Interior_Forest.gv"); std::ofstream interiorForestFile(interiorForestFileName); interiorForestFileName << InteriorForestDotGraphPrintFile( std::string("Block ") + std::to_string(static_cast(block->BlockIndex)) + " Round " + std::to_string(rp.round()) + " Partner " + std::to_string(ingid) + std::string(" Step 4 Interior Forest"), block->InteriorForests.back(), block->ContourTrees.back(), block->BoundaryTrees.back(), block->Meshes.back()); // save the corresponding .gv file std::string boundaryTreeMeshFileName = std::string("Rank_") + std::to_string(static_cast(rank)) + std::string("_Block_") + std::to_string(static_cast(block->BlockIndex)) + "_Round_" + std::to_string(rp.round()) + "_Partner_" + std::to_string(ingid) + std::string("_Step_5_Boundary_Tree_Mesh.gv"); std::ofstream boundaryTreeMeshFile(boundaryTreeMeshFileName); boundaryTreeMeshFile << vtkm::worklet::contourtree_distributed::ContourTreeMeshDotGraphPrint( std::string("Block ") + std::to_string(static_cast(block->BlockIndex)) + " Round " + std::to_string(rp.round()) + " Partner " + std::to_string(ingid) + std::string(" Step 5 Boundary Tree Mesh"), block->ContourTreeMeshes.back(), worklet::contourtree_distributed::SHOW_CONTOUR_TREE_MESH_ALL); */ #endif // Log the boundary tree size statistics VTKM_LOG_S(this->TreeLogLevel, std::endl << " ---------------- Boundary Tree Array Sizes ---------------------" << std::endl << " Rank : " << rank << std::endl << " DIY Id : " << selfid << std::endl << " Round : " << rp.round() << std::endl << boundaryTree.PrintArraySizes()); // Log the interior forest statistics VTKM_LOG_S(this->TreeLogLevel, std::endl << " ---------------- Interior Forest Array Sizes ---------------------" << std::endl << " Rank : " << rank << std::endl << " DIY Id : " << selfid << std::endl << " Round : " << rp.round() << std::endl << block->InteriorForests.back().PrintArraySizes()); } // end if (rp.round() != 0 && rp.out_link().size() != 0) // log the time to compute the boundary tree, interior forest, and contour tree mesh, i.e, the data we need to send timingsStream << " " << std::setw(38) << std::left << "Compute Trees To Send" << ": " << timer.GetElapsedTime() << " seconds" << std::endl; timer.Start(); // Send our current block (which is either our original block or the one we just combined from the ones we received) to our next neighbour. // Once a rank has send his block (either in its orignal or merged form) it is done with the reduce for (int cc = 0; cc < rp.out_link().size(); ++cc) { auto target = rp.out_link().target(cc); if (target.gid != selfid) { rp.enqueue(target, block->BlockOrigin); rp.enqueue(target, block->BlockSize); rp.enqueue(target, block->ContourTreeMeshes.back()); } } // end for // Log the time for enqueue the data for sending via DIY timingsStream << " " << std::setw(38) << std::left << "DIY Enqueue Data" << ": " << timer.GetElapsedTime() << " seconds" << std::endl; // Log the total this functor call step took timingsStream << " " << std::setw(38) << std::left << "Total Time Functor Step" << ": " << totalTimer.GetElapsedTime() << " seconds" << std::endl; // Record the times we logged VTKM_LOG_S(this->TimingsLogLevel, std::endl << " ---------------- Fan In Functor Step ---------------------" << std::endl << " Rank : " << rank << std::endl << " DIY Id : " << selfid << std::endl << " Round : " << rp.round() << std::endl << timingsStream.str()); } //end ComputeDistributedContourTreeFunctor private: /// Extends of the global mesh vtkm::Id3 GlobalSize; /// Use boundary extrema only (instead of the full boundary) during the fan in bool UseBoundaryExtremaOnly; /// Log level to be used for outputting timing information. Default is vtkm::cont::LogLevel::Perf vtkm::cont::LogLevel TimingsLogLevel = vtkm::cont::LogLevel::Perf; /// Log level to be used for outputting metadata about the trees. Default is vtkm::cont::LogLevel::Info vtkm::cont::LogLevel TreeLogLevel = vtkm::cont::LogLevel::Info; }; } // namespace contourtree_distributed } // namespace worklet } // namespace vtkm #endif