//============================================================================ // 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 _TREECOMPILER_H_ #define _TREECOMPILER_H_ #include #include #include #include #include namespace vtkm { namespace worklet { namespace contourtree_distributed { // Possibly change the following when comapring to PPP prototype constexpr int PRINT_WIDTH = 12; using dataType = vtkm::FloatDefault; using indexType = vtkm::Id; // small class for storing the contour arcs class Edge { // Edge public: indexType low, high; // constructor - defaults to -1 Edge(vtkm::Id Low = -1, vtkm::Id High = -1) : low(Low) , high(High) { } }; // Edge // comparison operator < inline bool operator<(const Edge& LHS, const Edge& RHS) { // operator < #if 0 if (LHS.low < RHS.low) return true; if (LHS.low > RHS.low) return false; if (LHS.high < RHS.high) return true; if (LHS.high > RHS.high) return false; #endif if (std::min(LHS.low, LHS.high) < std::min(RHS.low, RHS.high)) return true; else if (std::min(LHS.low, LHS.high) > std::min(RHS.low, RHS.high)) return false; if (std::max(LHS.low, LHS.high) < std::max(RHS.low, RHS.high)) return true; else if (std::max(LHS.low, LHS.high) > std::max(RHS.low, RHS.high)) return false; return false; } // operator < // comparison operator == inline bool operator==(const Edge& LHS, const Edge& RHS) { // operator == return (LHS.low == RHS.low && LHS.high == RHS.high) || (LHS.low == RHS.high && LHS.high == RHS.low); } // operator == // a helper class which stores a single supernode inserted onto a superarc class SupernodeOnSuperarc { // class SupernodeOnSuperarc public: // the global ID of the supernode indexType globalID; // the data value stored at the supernode dataType dataValue; // the low and high ends of the superarc it is on (may be itself) indexType lowEnd, highEnd; // constructor SupernodeOnSuperarc(indexType GlobalID = vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT, dataType DataValue = vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT, indexType LowEnd = vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT, indexType HighEnd = vtkm::worklet::contourtree_augmented::NO_SUCH_ELEMENT) : globalID(GlobalID) , dataValue(DataValue) , lowEnd(LowEnd) , highEnd(HighEnd) { // constructor } // constructor }; // class SupernodeOnSuperarc // overloaded comparison operator // primary sort is by superarc (low, high), // then secondary sort on datavalue // tertiary on globalID to implement simulated simplicity inline bool operator<(const SupernodeOnSuperarc& left, const SupernodeOnSuperarc& right) { // < operator // simple lexicographic sort if (left.lowEnd < right.lowEnd) return true; if (left.lowEnd > right.lowEnd) return false; if (left.highEnd < right.highEnd) return true; if (left.highEnd > right.highEnd) return false; if (left.dataValue < right.dataValue) return true; if (left.dataValue > right.dataValue) return false; if (left.globalID < right.globalID) return true; if (left.globalID > right.globalID) return false; // fall-through (shouldn't happen, but) // if they're the same, it's false return false; } // < operator // stream output std::ostream& operator<<(std::ostream& outStream, SupernodeOnSuperarc& node); // stream input std::istream& operator>>(std::istream& inStream, SupernodeOnSuperarc& node); // the class that compiles the contour tree class TreeCompiler { // class TreeCompiler public: // we want a vector of supernodes on superarcs std::vector supernodes; // and a vector of Edges (the output) std::vector superarcs; // routine to add a known hierarchical tree to it // note that this DOES NOT finalise - we don't want too many sorts void AddHierarchicalTree(const vtkm::cont::DataSet& addedTree); // routine to compute the actual superarcs void ComputeSuperarcs(); // routine to print a superarcs array in our format static void PrintSuperarcArray(const std::vector& superarc_array); // routine to print the superarcs void PrintSuperarcs(bool) const; // routine to write out binary file void WriteBinary(FILE* outFile) const; // routine to read in binary file & append to contents void ReadBinary(FILE* inFile); }; // class TreeCompiler // stream output inline std::ostream& operator<<(std::ostream& outStream, SupernodeOnSuperarc& node) { // stream output outStream << node.lowEnd << " " << node.highEnd << " " << node.dataValue << " " << node.globalID << std::endl; return outStream; } // stream output // stream input inline std::istream& operator>>(std::istream& inStream, SupernodeOnSuperarc& node) { // stream input inStream >> node.lowEnd >> node.highEnd >> node.dataValue >> node.globalID; return inStream; } // stream input // routine to add a known hierarchical tree to it // note that this DOES NOT finalise - we don't want too many sorts inline void TreeCompiler::AddHierarchicalTree(const vtkm::cont::DataSet& addedTree) { // TreeCompiler::AddHierarchicalTree() // Copy relevant tree content to STL arrays vtkm::cont::VariantArrayHandle dataValues_array = addedTree.GetField("DataValues").GetData(); std::vector dataValues(dataValues_array.GetNumberOfValues()); auto dataValues_handle = vtkm::cont::make_ArrayHandle(dataValues, vtkm::CopyFlag::Off); vtkm::cont::ArrayCopy(dataValues_array.ResetTypes(vtkm::List{}), dataValues_handle); dataValues_handle.SyncControlArray(); auto regularNodeGlobalIds_array = addedTree.GetField("RegularNodeGlobalIds").GetData(); std::vector regularNodeGlobalIds(regularNodeGlobalIds_array.GetNumberOfValues()); auto regularNodeGlobalIds_handle = vtkm::cont::make_ArrayHandle(regularNodeGlobalIds, vtkm::CopyFlag::Off); vtkm::cont::ArrayCopy(regularNodeGlobalIds_array.ResetTypes(vtkm::List{}), regularNodeGlobalIds_handle); regularNodeGlobalIds_handle .SyncControlArray(); //Forces values to get updated if copy happened on GPU auto superarcs_array = addedTree.GetField("Superarcs").GetData(); std::vector added_tree_superarcs(superarcs_array.GetNumberOfValues()); auto superarcs_handle = vtkm::cont::make_ArrayHandle(added_tree_superarcs, vtkm::CopyFlag::Off); vtkm::cont::ArrayCopy(superarcs_array.ResetTypes(vtkm::List{}), superarcs_handle); superarcs_handle.SyncControlArray(); //Forces values to get updated if copy happened on GPU auto supernodes_array = addedTree.GetField("Supernodes").GetData(); std::vector added_tree_supernodes(supernodes_array.GetNumberOfValues()); auto supernodes_handle = vtkm::cont::make_ArrayHandle(added_tree_supernodes, vtkm::CopyFlag::Off); vtkm::cont::ArrayCopy(supernodes_array.ResetTypes(vtkm::List{}), supernodes_handle); supernodes_handle.SyncControlArray(); //Forces values to get updated if copy happened on GPU auto superparents_array = addedTree.GetField("Superparents").GetData(); std::vector superparents(superparents_array.GetNumberOfValues()); auto superparents_handle = vtkm::cont::make_ArrayHandle(superparents, vtkm::CopyFlag::Off); vtkm::cont::ArrayCopy(superparents_array.ResetTypes(vtkm::List{}), superparents_handle); superparents_handle.SyncControlArray(); //Forces values to get updated if copy happened on GPU // loop through all of the supernodes in the hierarchical tree for (indexType supernode = 0; supernode < static_cast(added_tree_supernodes.size()); supernode++) { // per supernode // retrieve the regular ID for the supernode indexType regularId = added_tree_supernodes[supernode]; indexType globalId = regularNodeGlobalIds[regularId]; dataType dataVal = dataValues[regularId]; // retrieve the supernode at the far end indexType superTo = added_tree_superarcs[supernode]; // now test - if it is NO_SUCH_ELEMENT, there are two possibilities if (vtkm::worklet::contourtree_augmented::NoSuchElement(superTo)) { // no Superto // retrieve the superparent indexType superparent = superparents[regularId]; // the root node will have itself as its superparent if (superparent == supernode) continue; else { // not own superparent - an attachment point // retrieve the superparent's from & to indexType regularFrom = added_tree_supernodes[superparent]; indexType globalFrom = regularNodeGlobalIds[regularFrom]; indexType superParentTo = added_tree_superarcs[superparent]; indexType regularTo = added_tree_supernodes[vtkm::worklet::contourtree_augmented::MaskedIndex(superParentTo)]; indexType globalTo = regularNodeGlobalIds[regularTo]; // test the superTo to see whether we ascend or descend // note that we will never have NO_SUCH_ELEMENT here if (vtkm::worklet::contourtree_augmented::IsAscending(superParentTo)) { // ascending this->supernodes.push_back(SupernodeOnSuperarc(globalId, dataVal, globalFrom, globalTo)); } // ascending else { // descending this->supernodes.push_back(SupernodeOnSuperarc(globalId, dataVal, globalTo, globalFrom)); } // descending } // not own superparent - an attachment point } // no Superto else { // there is a valid superarc // retrieve the "to" and convert to global indexType maskedTo = vtkm::worklet::contourtree_augmented::MaskedIndex(superTo); indexType regularTo = added_tree_supernodes[maskedTo]; indexType globalTo = regularNodeGlobalIds[regularTo]; dataType dataTo = dataValues[regularTo]; // test the superTo to see whether we ascend or descend // note that we will never have NO_SUCH_ELEMENT here // we add both ends if (vtkm::worklet::contourtree_augmented::IsAscending(superTo)) { // ascending this->supernodes.push_back(SupernodeOnSuperarc(globalId, dataVal, globalId, globalTo)); this->supernodes.push_back(SupernodeOnSuperarc(globalTo, dataTo, globalId, globalTo)); } // ascending else { // descending this->supernodes.push_back(SupernodeOnSuperarc(globalId, dataVal, globalTo, globalId)); this->supernodes.push_back(SupernodeOnSuperarc(globalTo, dataTo, globalTo, globalId)); } // descending } // there is a valid superarc } // per supernode } // TreeCompiler::AddHierarchicalTree() // routine to compute the actual superarcs inline void TreeCompiler::ComputeSuperarcs() { // TreeCompiler::ComputeSuperarcs() // first we sort the vector std::sort(supernodes.begin(), supernodes.end()); // we could do a unique test here, but it's easier just to suppress it inside the loop // now we loop through it: note the -1 // this is because we know a priori that the last one is the last supernode on a superarc // and would fail the test inside the loop. By putting it in the loop test, we avoid having // to have an explicit if statement inside the loop for (indexType supernode = 0; supernode < static_cast(supernodes.size() - 1); supernode++) { // loop through supernodes // this is actually painfully simple: if the (lowEnd, highEnd) don't match the next one, // then we're at the end of the group and do nothing. Otherwise, we link to the next one if ((supernodes[supernode].lowEnd != supernodes[supernode + 1].lowEnd) || (supernodes[supernode].highEnd != supernodes[supernode + 1].highEnd)) continue; // if the supernode matches, then we have a repeat, and can suppress if (supernodes[supernode].globalID == supernodes[supernode + 1].globalID) continue; // otherwise, add a superarc to the list superarcs.push_back(Edge(supernodes[supernode].globalID, supernodes[supernode + 1].globalID)); } // loop through supernodes // now sort them std::sort(superarcs.begin(), superarcs.end()); } // TreeCompiler::ComputeSuperarcs() // routine to print the superarcs inline void TreeCompiler::PrintSuperarcArray(const std::vector& superarc_array) { // TreeCompiler::PrintSuperarcArray() for (indexType superarc = 0; superarc < static_cast(superarc_array.size()); superarc++) { // per superarc if (superarc_array[superarc].low < superarc_array[superarc].high) { // order by ID not value std::cout << std::setw(PRINT_WIDTH) << superarc_array[superarc].low << " "; std::cout << std::setw(PRINT_WIDTH) << superarc_array[superarc].high << std::endl; } // order by ID not value else { // order by ID not value std::cout << std::setw(PRINT_WIDTH) << superarc_array[superarc].high << " "; std::cout << std::setw(PRINT_WIDTH) << superarc_array[superarc].low << std::endl; } // order by ID not value } // per superarc } // TreeCompiler::PrintSuperarcArray() inline void TreeCompiler::PrintSuperarcs(bool printHeader = false) const { if (printHeader) { std::cout << "============" << std::endl; std::cout << "Contour Tree" << std::endl; } PrintSuperarcArray(this->superarcs); } // routine to write out binary file inline void TreeCompiler::WriteBinary(FILE* outFile) const { // WriteBinary() // do a bulk write of the entire contents // no error checking, no type checking, no nothing fwrite(&(supernodes[0]), sizeof(SupernodeOnSuperarc), supernodes.size(), outFile); } // WriteBinary() // routine to read in binary file and append inline void TreeCompiler::ReadBinary(FILE* inFile) { // ReadBinary() // use fseek to jump to the end fseek(inFile, 0, SEEK_END); // use fTell to retrieve the size of the file long long nBytes = ftell(inFile); // now rewind rewind(inFile); // compute how many elements are to be read long long nSupernodes = nBytes / sizeof(SupernodeOnSuperarc); // retrieve the current size long long currentSize = supernodes.size(); // resize to add the right number supernodes.resize(currentSize + nSupernodes); // now read directly into the right chunk fread(&(supernodes[currentSize]), sizeof(SupernodeOnSuperarc), nSupernodes, inFile); } // ReadBinary() // stream output - just dumps the supernodeonsuperarcs inline std::ostream& operator<<(std::ostream& outStream, TreeCompiler& tree) { // stream output for (indexType supernode = 0; supernode < static_cast(tree.supernodes.size()); supernode++) outStream << tree.supernodes[supernode]; return outStream; } // stream output // stream input - reads in the supernodeonsuperarcs & appends them inline std::istream& operator>>(std::istream& inStream, TreeCompiler& tree) { // stream input while (!inStream.eof()) { SupernodeOnSuperarc tempNode; inStream >> tempNode; tree.supernodes.push_back(tempNode); } // we will overshoot, so subtract one tree.supernodes.resize(tree.supernodes.size() - 1); return inStream; } // stream input } // namespace contourtree_distributed } // namespace worklet } // namespace vtkm #endif