//============================================================================ // 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 2014 National Technology & Engineering Solutions of Sandia, LLC (NTESS). // Copyright 2014 UT-Battelle, LLC. // Copyright 2014 Los Alamos National Security. // // Under the terms of Contract DE-NA0003525 with NTESS, // the U.S. Government retains certain rights in this software. // // Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National // Laboratory (LANL), the U.S. Government retains certain rights in // this software. //============================================================================ // 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) //============================================================================== #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // clang-format off VTKM_THIRDPARTY_PRE_INCLUDE #include #include VTKM_THIRDPARTY_POST_INCLUDE // clang-format on #ifdef WITH_MPI #include #endif #include #include #include #include #include #include #include #include using ValueType = vtkm::Float32; using BranchType = vtkm::worklet::contourtree_augmented::process_contourtree_inc::Branch; namespace ctaug_ns = vtkm::worklet::contourtree_augmented; // Simple helper class for parsing the command line options class ParseCL { public: ParseCL() {} void parse(int& argc, char** argv) { mCLOptions.resize(static_cast(argc)); for (std::size_t i = 1; i < static_cast(argc); ++i) { this->mCLOptions[i] = std::string(argv[i]); } } vtkm::Id findOption(const std::string& option) const { auto it = std::find_if(this->mCLOptions.begin(), this->mCLOptions.end(), [option](const std::string& val) -> bool { return val.find(option) == 0; }); if (it == this->mCLOptions.end()) { return -1; } else { return static_cast(it - this->mCLOptions.begin()); } } bool hasOption(const std::string& option) const { return this->findOption(option) >= 0; } std::string getOption(const std::string& option) const { std::size_t index = static_cast(this->findOption(option)); std::string val = this->mCLOptions[index]; auto valPos = val.find("="); if (valPos) { return val.substr(valPos + 1); } return std::string(""); } const std::vector& getOptions() const { return this->mCLOptions; } private: std::vector mCLOptions; }; inline vtkm::Id3 ComputeNumberOfBlocksPerAxis(vtkm::Id3 globalSize, vtkm::Id numberOfBlocks) { vtkm::Id currNumberOfBlocks = numberOfBlocks; vtkm::Id3 blocksPerAxis{ 1, 1, 1 }; while (currNumberOfBlocks > 1) { vtkm::IdComponent splitAxis = 0; for (vtkm::IdComponent d = 1; d < 3; ++d) { if (globalSize[d] > globalSize[splitAxis]) { splitAxis = d; } } if (currNumberOfBlocks % 2 == 0) { blocksPerAxis[splitAxis] *= 2; globalSize[splitAxis] /= 2; currNumberOfBlocks /= 2; } else { blocksPerAxis[splitAxis] *= currNumberOfBlocks; break; } } return blocksPerAxis; } inline std::tuple ComputeBlockExtents(vtkm::Id3 globalSize, vtkm::Id3 blocksPerAxis, vtkm::Id blockNo) { // DEBUG: std::cout << "ComputeBlockExtents("< " << blockIndex << " " << blockOrigin << " " << blockSize << std::endl; return std::make_tuple(blockIndex, blockOrigin, blockSize); } inline vtkm::cont::DataSet CreateSubDataSet(const vtkm::cont::DataSet& ds, vtkm::Id3 blockOrigin, vtkm::Id3 blockSize, const std::string& fieldName) { vtkm::Id3 globalSize; ds.GetCellSet().CastAndCallForTypes( vtkm::worklet::contourtree_augmented::GetPointDimensions(), globalSize); const vtkm::Id nOutValues = blockSize[0] * blockSize[1] * blockSize[2]; const auto inDataArrayHandle = ds.GetPointField(fieldName).GetData(); vtkm::cont::ArrayHandle copyIdsArray; copyIdsArray.Allocate(nOutValues); auto copyIdsPortal = copyIdsArray.WritePortal(); vtkm::Id3 outArrIdx; for (outArrIdx[2] = 0; outArrIdx[2] < blockSize[2]; ++outArrIdx[2]) for (outArrIdx[1] = 0; outArrIdx[1] < blockSize[1]; ++outArrIdx[1]) for (outArrIdx[0] = 0; outArrIdx[0] < blockSize[0]; ++outArrIdx[0]) { vtkm::Id3 inArrIdx = outArrIdx + blockOrigin; vtkm::Id inIdx = (inArrIdx[2] * globalSize[1] + inArrIdx[1]) * globalSize[0] + inArrIdx[0]; vtkm::Id outIdx = (outArrIdx[2] * blockSize[1] + outArrIdx[1]) * blockSize[0] + outArrIdx[0]; VTKM_ASSERT(inIdx >= 0 && inIdx < inDataArrayHandle.GetNumberOfValues()); VTKM_ASSERT(outIdx >= 0 && outIdx < nOutValues); copyIdsPortal.Set(outIdx, inIdx); } // DEBUG: std::cout << copyIdsPortal.GetNumberOfValues() << std::endl; vtkm::cont::Field permutedField; bool success = vtkm::filter::MapFieldPermutation(ds.GetPointField(fieldName), copyIdsArray, permutedField); if (!success) throw vtkm::cont::ErrorBadType("Field copy failed (probably due to invalid type)"); vtkm::cont::DataSetBuilderUniform dsb; if (globalSize[2] <= 1) // 2D Data Set { vtkm::Id2 dimensions{ blockSize[0], blockSize[1] }; vtkm::cont::DataSet dataSet = dsb.Create(dimensions); vtkm::cont::CellSetStructured<2> cellSet; cellSet.SetPointDimensions(dimensions); cellSet.SetGlobalPointDimensions(vtkm::Id2{ globalSize[0], globalSize[1] }); cellSet.SetGlobalPointIndexStart(vtkm::Id2{ blockOrigin[0], blockOrigin[1] }); dataSet.SetCellSet(cellSet); dataSet.AddField(permutedField); return dataSet; } else { vtkm::cont::DataSet dataSet = dsb.Create(blockSize); vtkm::cont::CellSetStructured<3> cellSet; cellSet.SetPointDimensions(blockSize); cellSet.SetGlobalPointDimensions(globalSize); cellSet.SetGlobalPointIndexStart(blockOrigin); dataSet.SetCellSet(cellSet); dataSet.AddField(permutedField); return dataSet; } } // Compute and render an isosurface for a uniform grid example int main(int argc, char* argv[]) { #ifdef WITH_MPI // Setup the MPI environment. MPI_Init(&argc, &argv); auto comm = MPI_COMM_WORLD; // Tell VTK-m which communicator it should use. vtkm::cont::EnvironmentTracker::SetCommunicator(vtkmdiy::mpi::communicator()); // get the rank and size int rank, size; MPI_Comm_rank(comm, &rank); MPI_Comm_size(comm, &size); int numBlocks = size; #endif // initialize vtkm-m (e.g., logging via -v and device via the -d option) vtkm::cont::InitializeOptions vtkm_initialize_options = vtkm::cont::InitializeOptions::RequireDevice; vtkm::cont::InitializeResult vtkm_config = vtkm::cont::Initialize(argc, argv, vtkm_initialize_options); auto device = vtkm_config.Device; #ifdef WITH_MPI VTKM_LOG_IF_S(vtkm::cont::LogLevel::Info, rank == 0, "Running with MPI. #ranks=" << size); #else VTKM_LOG_S(vtkm::cont::LogLevel::Info, "Single node run"); int rank = 0; #endif // Setup timing vtkm::Float64 prevTime = 0; vtkm::Float64 currTime = 0; vtkm::cont::Timer totalTime; totalTime.Start(); //////////////////////////////////////////// // Parse the command line options //////////////////////////////////////////// ParseCL parser; parser.parse(argc, argv); std::string filename = parser.getOptions().back(); unsigned int computeRegularStructure = 1; // 1=fully augmented bool useMarchingCubes = false; bool computeBranchDecomposition = true; bool printContourTree = false; if (parser.hasOption("--augmentTree")) computeRegularStructure = static_cast(std::stoi(parser.getOption("--augmentTree"))); if (parser.hasOption("--mc")) useMarchingCubes = true; if (parser.hasOption("--printCT")) printContourTree = true; if (parser.hasOption("--branchDecomp")) computeBranchDecomposition = std::stoi(parser.getOption("--branchDecomp")); // We need the fully augmented tree to compute the branch decomposition if (computeBranchDecomposition && (computeRegularStructure != 1)) { VTKM_LOG_S(vtkm::cont::LogLevel::Warn, "Regular structure is required for branch decomposition." " Disabling branch decomposition"); computeBranchDecomposition = false; } // Iso value selection parameters // Approach to be used to select contours based on the tree vtkm::Id contourType = 0; // Error away from critical point ValueType eps = 0.00001f; // Number of iso levels to be selected. By default we disable the isovalue selection. vtkm::Id numLevels = 0; // Number of components the tree should be simplified to vtkm::Id numComp = numLevels + 1; // Method to be used to compute the relevant iso values vtkm::Id contourSelectMethod = 0; bool usePersistenceSorter = true; if (parser.hasOption("--levels")) numLevels = std::stoi(parser.getOption("--levels")); if (parser.hasOption("--type")) contourType = std::stoi(parser.getOption("--type")); if (parser.hasOption("--eps")) eps = std::stof(parser.getOption("--eps")); if (parser.hasOption("--method")) contourSelectMethod = std::stoi(parser.getOption("--method")); if (parser.hasOption("--comp")) numComp = std::stoi(parser.getOption("--comp")); if (contourSelectMethod == 0) numComp = numLevels + 1; if (parser.hasOption("--useVolumeSorter")) usePersistenceSorter = false; if ((numLevels > 0) && (!computeBranchDecomposition)) { VTKM_LOG_S(vtkm::cont::LogLevel::Warn, "Iso level selection only available when branch decomposition is enabled. " "Disabling iso value selection"); numLevels = 0; } if (rank == 0 && (argc < 2 || parser.hasOption("--help") || parser.hasOption("-h"))) { std::cout << "ContourTreeAugmented " << std::endl; std::cout << std::endl; std::cout << " Name of the input data file." << std::endl; std::cout << "The file is expected to be ASCII with either: " << std::endl; std::cout << " - xdim ydim integers for 2D or" << std::endl; std::cout << " - xdim ydim zdim integers for 3D" << std::endl; std::cout << "followed by vector data last dimension varying fastest" << std::endl; std::cout << std::endl; std::cout << "----------------------------- VTKM Options -----------------------------" << std::endl; std::cout << vtkm_config.Usage << std::endl; std::cout << std::endl; std::cout << "------------------------- Contour Tree Options -------------------------" << std::endl; std::cout << "Options: (Bool options are give via int, i.e. =0 for False and =1 for True)" << std::endl; std::cout << "--mc Use marching cubes interpolation for contour tree calculation. " "(Default=False)" << std::endl; std::cout << "--augmentTree 1 = compute the fully augmented contour tree (Default)" << std::endl; std::cout << " 2 = compute the boundary augmented contour tree " << std::endl; std::cout << " 0 = no augmentation. NOTE: When using MPI, local ranks use" << std::endl; std::cout << " boundary augmentation to support parallel merge of blocks" << std::endl; std::cout << "--branchDecomp Compute the volume branch decomposition for the contour tree. " "Requires --augmentTree (Default=True)" << std::endl; std::cout << "--printCT Print the contour tree. (Default=False)" << std::endl; std::cout << std::endl; std::cout << "---------------------- Isovalue Selection Options ----------------------" << std::endl; std::cout << "Isovalue selection options: (require --branchDecomp=1 and augmentTree=1)" << std::endl; std::cout << "--levels= Number of iso-contour levels to be used (default=0, i.e., " "disable isovalue computation)" << std::endl; std::cout << "--comp= Number of components the contour tree should be simplified to. " "Only used if method==1. (default=0)" << std::endl; std::cout << "--eps= Floating point offset awary from the critical point. (default=0.00001)" << std::endl; std::cout << "--type= Approach to be used for selection of iso-values. 0=saddle+-eps; " "1=mid point between saddle and extremum, 2=extremum+-eps. (default=0)" << std::endl; std::cout << "--method= Method used for selecting relevant iso-values. (default=0)" << std::endl; std::cout << std::endl; #ifdef WITH_MPI MPI_Finalize(); #endif return EXIT_SUCCESS; } if (rank == 0) { std::stringstream logmessage; logmessage << " ------------ Settings -----------" << std::endl << " filename=" << filename << std::endl << " device=" << device.GetName() << std::endl << " mc=" << useMarchingCubes << std::endl << " augmentTree=" << computeRegularStructure << std::endl << " branchDecomp=" << computeBranchDecomposition << std::endl << #ifdef WITH_MPI " nblocks=" << numBlocks << std::endl << #endif " computeIsovalues=" << (numLevels > 0); VTKM_LOG_S(vtkm::cont::LogLevel::Info, std::endl << logmessage.str()); VTKM_LOG_IF_S(vtkm::cont::LogLevel::Info, numLevels > 0, std::endl << " ------------ Settings Isolevel Selection -----------" << std::endl << " levels=" << numLevels << std::endl << " eps=" << eps << std::endl << " comp" << numComp << std::endl << " type=" << contourType << std::endl << " method=" << contourSelectMethod << std::endl << " mc=" << useMarchingCubes << std::endl << " use" << (usePersistenceSorter ? "PersistenceSorter" : "VolumeSorter")); } currTime = totalTime.GetElapsedTime(); vtkm::Float64 startUpTime = currTime - prevTime; prevTime = currTime; // Redirect stdout to file if we are using MPI with Debugging #ifdef WITH_MPI #ifdef DEBUG_PRINT // From https://www.unix.com/302983597-post2.html char cstr_filename[32]; snprintf(cstr_filename, sizeof(cstr_filename), "cout_%d.log", rank); int out = open(cstr_filename, O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0600); if (-1 == out) { perror("opening cout.log"); return 255; } snprintf(cstr_filename, sizeof(cstr_filename), "cerr_%d.log", rank); int err = open(cstr_filename, O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0600); if (-1 == err) { perror("opening cerr.log"); return 255; } int save_out = dup(fileno(stdout)); int save_err = dup(fileno(stderr)); if (-1 == dup2(out, fileno(stdout))) { perror("cannot redirect stdout"); return 255; } if (-1 == dup2(err, fileno(stderr))) { perror("cannot redirect stderr"); return 255; } #endif #endif /////////////////////////////////////////////// // Read the input data /////////////////////////////////////////////// vtkm::Float64 dataReadTime = 0; vtkm::Float64 buildDatasetTime = 0; std::vector::size_type nDims = 0; vtkm::cont::DataSet inDataSet; std::vector values; std::vector dims; if (filename.compare(filename.length() - 3, 3, "bov") == 0) { vtkm::io::BOVDataSetReader reader(filename); inDataSet = reader.ReadDataSet(); nDims = 3; currTime = totalTime.GetElapsedTime(); dataReadTime = currTime - prevTime; prevTime = currTime; } else // Read ASCII data input { std::ifstream inFile(filename); if (inFile.bad()) return 0; // Read the dimensions of the mesh, i.e,. number of elementes in x, y, and z std::string line; getline(inFile, line); std::istringstream linestream(line); vtkm::Id dimVertices; while (linestream >> dimVertices) { dims.push_back(dimVertices); } // Compute the number of vertices, i.e., xdim * ydim * zdim nDims = static_cast(dims.size()); std::size_t numVertices = static_cast( std::accumulate(dims.begin(), dims.end(), std::size_t(1), std::multiplies())); // Check for fatal input errors // Check the the number of dimensiosn is either 2D or 3D bool invalidNumDimensions = (nDims < 2 || nDims > 3); // Log any errors if found on rank 0 VTKM_LOG_IF_S(vtkm::cont::LogLevel::Error, invalidNumDimensions && (rank == 0), "The input mesh is " << nDims << "D. The input data must be either 2D or 3D."); // If we found any errors in the setttings than finalize MPI and exit the execution if (invalidNumDimensions) { #ifdef WITH_MPI MPI_Finalize(); #endif return EXIT_SUCCESS; } // Read data values.resize(numVertices); for (std::size_t vertex = 0; vertex < numVertices; ++vertex) { inFile >> values[vertex]; } // finish reading the data inFile.close(); currTime = totalTime.GetElapsedTime(); dataReadTime = currTime - prevTime; prevTime = currTime; // swap dims order std::swap(dims[0], dims[1]); // build the input dataset vtkm::cont::DataSetBuilderUniform dsb; // 2D data if (nDims == 2) { vtkm::Id2 vdims; vdims[0] = static_cast(dims[0]); vdims[1] = static_cast(dims[1]); inDataSet = dsb.Create(vdims); } // 3D data else { vtkm::Id3 vdims; vdims[0] = static_cast(dims[0]); vdims[1] = static_cast(dims[1]); vdims[2] = static_cast(dims[2]); inDataSet = dsb.Create(vdims); } inDataSet.AddPointField("values", values); } // END ASCII Read // Print the mesh metadata if (rank == 0) { VTKM_LOG_S(vtkm::cont::LogLevel::Info, std::endl << " ---------------- Input Mesh Properties --------------" << std::endl << " Number of dimensions: " << nDims); } // Check if marching cubes is enabled for non 3D data bool invalidMCOption = (useMarchingCubes && nDims != 3); VTKM_LOG_IF_S(vtkm::cont::LogLevel::Error, invalidMCOption && (rank == 0), "The input mesh is " << nDims << "D. " << "Contour tree using marching cubes is only supported for 3D data."); // If we found any errors in the setttings than finalize MPI and exit the execution if (invalidMCOption) { #ifdef WITH_MPI MPI_Finalize(); #endif return EXIT_SUCCESS; } #ifndef WITH_MPI // construct regular, single-block VTK-M input dataset vtkm::cont::DataSet useDataSet = inDataSet; // Single block dataset #else // Create a multi-block dataset for multi-block DIY-paralle processing // Determine split vtkm::Id3 globalSize = nDims == 3 ? vtkm::Id3(static_cast(dims[0]), static_cast(dims[1]), static_cast(dims[2])) : vtkm::Id3(static_cast(dims[0]), static_cast(dims[1]), static_cast(1)); vtkm::Id3 blocksPerDim = ComputeNumberOfBlocksPerAxis(globalSize, numBlocks); vtkm::Id blocksPerRank = numBlocks / size; vtkm::Id numRanksWithExtraBlock = numBlocks % size; vtkm::Id blocksOnThisRank, startBlockNo; if (rank < numRanksWithExtraBlock) { blocksOnThisRank = blocksPerRank + 1; startBlockNo = (blocksPerRank + 1) * rank; } else { blocksOnThisRank = blocksPerRank; startBlockNo = numRanksWithExtraBlock * (blocksPerRank + 1) + (rank - numRanksWithExtraBlock) * blocksPerRank; } if (blocksOnThisRank != 1) { std::cerr << "Currently only one block per rank supported!"; MPI_Finalize(); return EXIT_FAILURE; } // Created partitioned (split) data set vtkm::cont::PartitionedDataSet useDataSet; vtkm::cont::ArrayHandle localBlockIndices; localBlockIndices.Allocate(blocksPerRank); auto localBlockIndicesPortal = localBlockIndices.WritePortal(); for (vtkm::Id blockNo = 0; blockNo < blocksOnThisRank; ++blockNo) { vtkm::Id3 blockOrigin, blockSize, blockIndex; std::tie(blockIndex, blockOrigin, blockSize) = ComputeBlockExtents(globalSize, blocksPerDim, startBlockNo + blockNo); useDataSet.AppendPartition(CreateSubDataSet(inDataSet, blockOrigin, blockSize, "values")); localBlockIndicesPortal.Set(blockNo, blockIndex); } #endif // WITH_MPI construct input dataset currTime = totalTime.GetElapsedTime(); buildDatasetTime = currTime - prevTime; prevTime = currTime; // Convert the mesh of values into contour tree, pairs of vertex ids vtkm::filter::scalar_topology::ContourTreeAugmented filter(useMarchingCubes, computeRegularStructure); #ifdef WITH_MPI filter.SetBlockIndices(blocksPerDim, localBlockIndices); #endif filter.SetActiveField("values"); // Execute the contour tree analysis. NOTE: If MPI is used the result will be // a vtkm::cont::PartitionedDataSet instead of a vtkm::cont::DataSet auto result = filter.Execute(useDataSet); currTime = totalTime.GetElapsedTime(); vtkm::Float64 computeContourTreeTime = currTime - prevTime; prevTime = currTime; #ifdef WITH_MPI #ifdef DEBUG_PRINT std::cout << std::flush; close(out); std::cerr << std::flush; close(err); dup2(save_out, fileno(stdout)); dup2(save_err, fileno(stderr)); close(save_out); close(save_err); #endif #endif //////////////////////////////////////////// // Compute the branch decomposition //////////////////////////////////////////// if (rank == 0 && computeBranchDecomposition && computeRegularStructure) { // Time branch decompostion vtkm::cont::Timer branchDecompTimer; branchDecompTimer.Start(); // compute the volume for each hyperarc and superarc ctaug_ns::IdArrayType superarcIntrinsicWeight; ctaug_ns::IdArrayType superarcDependentWeight; ctaug_ns::IdArrayType supernodeTransferWeight; ctaug_ns::IdArrayType hyperarcDependentWeight; ctaug_ns::ProcessContourTree::ComputeVolumeWeightsSerial(filter.GetContourTree(), filter.GetNumIterations(), superarcIntrinsicWeight, // (output) superarcDependentWeight, // (output) supernodeTransferWeight, // (output) hyperarcDependentWeight); // (output) // Record the timings for the branch decomposition std::stringstream timingsStream; // Use a string stream to log in one message timingsStream << std::endl; timingsStream << " --------------- Branch Decomposition Timings " << rank << " --------------" << std::endl; timingsStream << " " << std::setw(38) << std::left << "Compute Volume Weights" << ": " << branchDecompTimer.GetElapsedTime() << " seconds" << std::endl; branchDecompTimer.Start(); // compute the branch decomposition by volume ctaug_ns::IdArrayType whichBranch; ctaug_ns::IdArrayType branchMinimum; ctaug_ns::IdArrayType branchMaximum; ctaug_ns::IdArrayType branchSaddle; ctaug_ns::IdArrayType branchParent; ctaug_ns::ProcessContourTree::ComputeVolumeBranchDecompositionSerial(filter.GetContourTree(), superarcDependentWeight, superarcIntrinsicWeight, whichBranch, // (output) branchMinimum, // (output) branchMaximum, // (output) branchSaddle, // (output) branchParent); // (output) // Record and log the branch decompostion timings timingsStream << " " << std::setw(38) << std::left << "Compute Volume Branch Decomposition" << ": " << branchDecompTimer.GetElapsedTime() << " seconds" << std::endl; VTKM_LOG_S(vtkm::cont::LogLevel::Info, timingsStream.str()); //----main branch decompostion end //----Isovalue seleciton start if (numLevels > 0) // if compute isovalues { // Get the data values for computing the explicit branch decomposition vtkm::cont::ArrayHandle dataField; #ifdef WITH_MPI result.GetPartitions()[0].GetField("values").GetData().AsArrayHandle(dataField); bool dataFieldIsSorted = true; #else useDataSet.GetField("values").GetData().AsArrayHandle(dataField); bool dataFieldIsSorted = false; #endif // create explicit representation of the branch decompostion from the array representation BranchType* branchDecompostionRoot = ctaug_ns::ProcessContourTree::ComputeBranchDecomposition( filter.GetContourTree().Superparents, filter.GetContourTree().Supernodes, whichBranch, branchMinimum, branchMaximum, branchSaddle, branchParent, filter.GetSortOrder(), dataField, dataFieldIsSorted); #ifdef DEBUG_PRINT branchDecompostionRoot->PrintBranchDecomposition(std::cout); #endif // Simplify the contour tree of the branch decompostion branchDecompostionRoot->SimplifyToSize(numComp, usePersistenceSorter); // Compute the relevant iso-values std::vector isoValues; switch (contourSelectMethod) { default: case 0: { branchDecompostionRoot->GetRelevantValues(static_cast(contourType), eps, isoValues); } break; case 1: { vtkm::worklet::contourtree_augmented::process_contourtree_inc::PiecewiseLinearFunction< ValueType> plf; branchDecompostionRoot->AccumulateIntervals(static_cast(contourType), eps, plf); isoValues = plf.nLargest(static_cast(numLevels)); } break; } // Print the compute iso values std::stringstream isoStream; // Use a string stream to log in one message isoStream << std::endl; isoStream << " ------------------- Isovalue Suggestions --------------------" << std::endl; std::sort(isoValues.begin(), isoValues.end()); isoStream << " Isovalues: "; for (ValueType val : isoValues) { isoStream << val << " "; } isoStream << std::endl; // Unique isovalues std::vector::iterator it = std::unique(isoValues.begin(), isoValues.end()); isoValues.resize(static_cast(std::distance(isoValues.begin(), it))); isoStream << " Unique Isovalues (" << isoValues.size() << "):"; for (ValueType val : isoValues) { isoStream << val << " "; } VTKM_LOG_S(vtkm::cont::LogLevel::Info, isoStream.str()); } //end if compute isovalue } currTime = totalTime.GetElapsedTime(); vtkm::Float64 computeBranchDecompTime = currTime - prevTime; prevTime = currTime; //vtkm::cont::Field resultField = result.GetField(); //vtkm::cont::ArrayHandle > saddlePeak; //resultField.GetData().AsArrayHandle(saddlePeak); // Dump out contour tree for comparison if (rank == 0 && printContourTree) { std::cout << "Contour Tree" << std::endl; std::cout << "============" << std::endl; ctaug_ns::EdgePairArray saddlePeak; ctaug_ns::ProcessContourTree::CollectSortedSuperarcs( filter.GetContourTree(), filter.GetSortOrder(), saddlePeak); ctaug_ns::PrintEdgePairArrayColumnLayout(saddlePeak, std::cout); } #ifdef WITH_MPI // Force a simple round-robin on the ranks for the summary prints. Its not perfect for MPI but // it works well enough to sort the summaries from the ranks for small-scale debugging. if (rank > 0) { int temp; MPI_Status status; MPI_Recv(&temp, 1, MPI_INT, (rank - 1), 0, comm, &status); } #endif currTime = totalTime.GetElapsedTime(); VTKM_LOG_S(vtkm::cont::LogLevel::Info, std::endl << " -------------------------- Totals " << rank << " -----------------------------" << std::endl << std::setw(42) << std::left << " Start-up" << ": " << startUpTime << " seconds" << std::endl << std::setw(42) << std::left << " Data Read" << ": " << dataReadTime << " seconds" << std::endl << std::setw(42) << std::left << " Build VTKM Dataset" << ": " << buildDatasetTime << " seconds" << std::endl << std::setw(42) << std::left << " Compute Contour Tree" << ": " << computeContourTreeTime << " seconds" << std::endl << std::setw(42) << std::left << " Compute Branch Decomposition" << ": " << computeBranchDecompTime << " seconds" << std::endl << std::setw(42) << std::left << " Total Time" << ": " << currTime << " seconds"); const ctaug_ns::ContourTree& ct = filter.GetContourTree(); VTKM_LOG_S(vtkm::cont::LogLevel::Info, std::endl << " ---------------- Contour Tree Array Sizes ---------------------" << std::endl << ct.PrintArraySizes()); // Print hyperstructure statistics VTKM_LOG_S(vtkm::cont::LogLevel::Info, std::endl << ct.PrintHyperStructureStatistics(false) << std::endl); // Flush ouput streams just to make sure everything has been logged (in particular when using MPI) std::cout << std::flush; std::cerr << std::flush; #ifdef WITH_MPI // Let the next rank know that it is time to print their summary. if (rank < (size - 1)) { int message = 1; MPI_Send(&message, 1, MPI_INT, (rank + 1), 0, comm); } #endif #ifdef WITH_MPI MPI_Finalize(); #endif return EXIT_SUCCESS; }