From 6550fef755d7617047fbf8ba508bc55a60acedda Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 10 Jan 2024 09:15:18 -0800 Subject: [PATCH 01/61] Added new parallel implementations new_scanCrossings and new_computeContour. The current versions are not removed yet, while I check out performance of the new implementations. --- .../detail/MarchingCubesFullParallel.hpp | 202 +++++++++++++----- 1 file changed, 147 insertions(+), 55 deletions(-) diff --git a/src/axom/quest/detail/MarchingCubesFullParallel.hpp b/src/axom/quest/detail/MarchingCubesFullParallel.hpp index 8e7e2e12e6..90d1a85126 100644 --- a/src/axom/quest/detail/MarchingCubesFullParallel.hpp +++ b/src/axom/quest/detail/MarchingCubesFullParallel.hpp @@ -102,8 +102,8 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase void computeContourMesh() override { markCrossings(); - scanCrossings(); - computeContour(); + new_scanCrossings(); + new_computeContour(); } /*! @@ -313,12 +313,82 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_facetIncrs.data() + m_facetIncrs.size() - 1, sizeof(facetIncrs_back)); m_facetCount = firstFacetIds_back + facetIncrs_back; + } + void new_scanCrossings() + { +#if defined(AXOM_USE_RAJA) + #ifdef __INTEL_LLVM_COMPILER + // Intel oneAPI compiler segfaults with OpenMP RAJA scan + using ScanPolicy = + typename axom::execution_space::loop_policy; + #else + using ScanPolicy = typename axom::execution_space::loop_policy; + #endif +#endif + const axom::IndexType parentCellCount = m_caseIds.size(); + auto caseIdsView = m_caseIds.view(); - // Allocate space for surface mesh. - const axom::IndexType cornersCount = DIM * m_facetCount; - m_contourCellParents.resize(m_facetCount); - m_contourCellCorners.resize(m_facetCount); - m_contourNodeCoords.resize(cornersCount); + // + // Initialize crossingFlags to 0 or 1, prefix-sum it, and count the + // crossings. + // + + axom::Array crossingFlags(parentCellCount); + auto crossingFlagsView = crossingFlags.view(); + axom::for_all( + 0, + parentCellCount, + AXOM_LAMBDA(axom::IndexType parentCellId) { + auto numContourCells = num_contour_cells(caseIdsView.flatIndex(parentCellId)); + crossingFlagsView[parentCellId] = bool(numContourCells); + }); + + axom::Array scannedFlags(1 + parentCellCount); + auto scannedFlagsView = scannedFlags.view(); + RAJA::inclusive_scan( + RAJA::make_span(crossingFlags.data(), parentCellCount), + RAJA::make_span(scannedFlags.data() + 1, parentCellCount), + RAJA::operators::plus {}); + + axom::copy(&m_crossingCount, + scannedFlags.data() + scannedFlags.size() - 1, + sizeof(axom::IndexType)); + + // + // Generate crossing-cells index list and corresponding facet counts. + // + + m_crossingParentIds.resize(m_crossingCount); + m_facetIncrs.resize(m_crossingCount); + auto crossingParentIdsView = m_crossingParentIds.view(); + auto facetIncrsView = m_facetIncrs.view(); + + auto loopBody = AXOM_LAMBDA(axom::IndexType parentCellId) { + if(scannedFlagsView[parentCellId] != + scannedFlagsView[1+parentCellId]) { + auto crossingId = scannedFlagsView[parentCellId]; + auto facetIncr = num_contour_cells(caseIdsView.flatIndex(parentCellId)); + crossingParentIdsView[crossingId] = parentCellId; + facetIncrsView[crossingId] = facetIncr; + } + }; + axom::for_all(0, parentCellCount, loopBody); + + // + // Prefix-sum the facets counts to get first facet id for each crossing + // and the total number of facets. + // + + m_firstFacetIds.resize(1 + m_crossingCount); + RAJA::inclusive_scan( + RAJA::make_span(m_facetIncrs.data(), m_crossingCount), + RAJA::make_span(m_firstFacetIds.data() + 1, m_crossingCount), + RAJA::operators::plus {}); + + axom::copy(&m_facetCount, + m_firstFacetIds.data() + m_firstFacetIds.size() - 1, + sizeof(axom::IndexType)); + m_firstFacetIds.resize(m_crossingCount); } void computeContour() @@ -327,38 +397,17 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase // Fill in surface mesh data. // + // Allocate space for surface mesh. + const axom::IndexType cornersCount = DIM * m_facetCount; + m_contourCellParents.resize(m_facetCount); + m_contourCellCorners.resize(m_facetCount); + m_contourNodeCoords.resize(cornersCount); + const axom::IndexType parentCellCount = m_caseIds.size(); const auto facetIncrsView = m_facetIncrs.view(); const auto firstFacetIdsView = m_firstFacetIds.view(); const auto caseIdsView = m_caseIds.view(); - /* - To minimize diverence, sort parent indices by number of facets - they add. This is a disabled experimental option. Enable by - setting sortFacetsIncrs. Requires RAJA. - */ - const bool sortFacetsIncrs = false; - // sortedIndices are parent cell indices, sorted by number - // of facets in them. - axom::Array sortedIndices( - sortFacetsIncrs ? parentCellCount : 0); - auto sortedIndicesView = sortedIndices.view(); - -#if defined(AXOM_USE_RAJA) - if(sortFacetsIncrs) - { - auto sortedFacetIncrs = m_facetIncrs; - axom::for_all( - 0, - parentCellCount, - AXOM_LAMBDA(axom::IndexType pcId) { sortedIndicesView[pcId] = pcId; }); - RAJA::stable_sort_pairs( - RAJA::make_span(sortedFacetIncrs.data(), parentCellCount), - RAJA::make_span(sortedIndices.data(), parentCellCount), - RAJA::operators::greater {}); - } -#endif - auto contourCellParentsView = m_contourCellParents.view(); auto contourCellCornersView = m_contourCellCorners.view(); auto contourNodeCoordsView = m_contourNodeCoords.view(); @@ -367,11 +416,8 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_caseIds.strides(), m_fcnView, m_coordsViews); - auto gen_for_parent_cell = AXOM_LAMBDA(axom::IndexType loopIndex) + auto gen_for_parent_cell = AXOM_LAMBDA(axom::IndexType parentCellId) { - axom::IndexType parentCellId = - sortFacetsIncrs ? sortedIndicesView[loopIndex] : loopIndex; - Point cornerCoords[CELL_CORNER_COUNT]; double cornerValues[CELL_CORNER_COUNT]; ccu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); @@ -404,6 +450,65 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase axom::for_all(0, parentCellCount, gen_for_parent_cell); } + void new_computeContour() + { + // + // Fill in surface mesh data. + // + + // Allocate space for surface mesh. + const axom::IndexType cornersCount = DIM * m_facetCount; + m_contourCellParents.resize(m_facetCount); + m_contourCellCorners.resize(m_facetCount); + m_contourNodeCoords.resize(cornersCount); + + const auto facetIncrsView = m_facetIncrs.view(); + const auto firstFacetIdsView = m_firstFacetIds.view(); + const auto crossingParentIdsView = m_crossingParentIds.view(); + const auto caseIdsView = m_caseIds.view(); + + auto contourCellParentsView = m_contourCellParents.view(); + auto contourCellCornersView = m_contourCellCorners.view(); + auto contourNodeCoordsView = m_contourNodeCoords.view(); + + ComputeContour_Util ccu(m_contourVal, + m_caseIds.strides(), + m_fcnView, + m_coordsViews); + auto gen_for_parent_cell = AXOM_LAMBDA(axom::IndexType crossingId) + { + auto parentCellId = crossingParentIdsView[crossingId]; + auto caseId = caseIdsView.flatIndex(parentCellId); + Point cornerCoords[CELL_CORNER_COUNT]; + double cornerValues[CELL_CORNER_COUNT]; + ccu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); + + auto additionalFacets = facetIncrsView[crossingId]; + auto firstFacetId = firstFacetIdsView[crossingId]; + + for(axom::IndexType fId = 0; fId < additionalFacets; ++fId) + { + axom::IndexType newFacetId = firstFacetId + fId; + axom::IndexType firstCornerId = newFacetId * DIM; + + contourCellParentsView[newFacetId] = parentCellId; + + for(axom::IndexType d = 0; d < DIM; ++d) + { + axom::IndexType newCornerId = firstCornerId + d; + contourCellCornersView[newFacetId][d] = newCornerId; + + int edge = cases_table(caseId, fId * DIM + d); + ccu.linear_interp(edge, + cornerCoords, + cornerValues, + contourNodeCoordsView[newCornerId]); + } + } + }; + + axom::for_all(0, m_crossingCount, gen_for_parent_cell); + } /*! @brief Implementation used by MarchingCubesFullParallel::computeContour(). @@ -771,22 +876,6 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase , m_contourCellParents(0, 0) { } - /*! - @brief Info for a parent cell intersecting the contour surface. - */ - struct CrossingInfo - { - CrossingInfo() { } - CrossingInfo(axom::IndexType parentCellNum_, std::uint16_t caseNum_) - : parentCellNum(parentCellNum_) - , caseNum(caseNum_) - , firstSurfaceCellId(std::numeric_limits::max()) - { } - axom::IndexType parentCellNum; //!< @brief Flat index of parent cell in m_caseIds. - std::uint16_t caseNum; //!< @brief Index in cases2D or cases3D - axom::IndexType firstSurfaceCellId; //!< @brief First index for generated cells. - }; - private: MIdx m_bShape; //!< @brief Blueprint cell data shape. @@ -804,9 +893,12 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase //!@brief Number of parent cells crossing the contour surface. axom::IndexType m_crossingCount = 0; - //!@brief Number of surface mesh facets added by computational mesh cells. + //!@brief Number of surface mesh facets added by crossing mesh cells. axom::Array m_facetIncrs; + //!@brief Parent cell id for each crossing. + axom::Array m_crossingParentIds; + //!@brief First index of facets in computational mesh cells. axom::Array m_firstFacetIds; From 8ba0105f4d8b07e25f077ef614ba8b5ce396f068 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 23 Jan 2024 10:31:23 -0800 Subject: [PATCH 02/61] Rename new methods to make them the regular methods. --- src/axom/quest/MarchingCubes.hpp | 10 ++++++-- .../detail/MarchingCubesFullParallel.hpp | 24 ++++++++++--------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index cf600213cb..966e3954b3 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -149,8 +149,9 @@ class MarchingCubes /*! @brief Put generated contour in a mint::UnstructuredMesh. @param mesh Output contour mesh - @param cellIdField Name of field to store the (axom::IndexType) - parent cell ids. If omitted, the data is not provided. + @param cellIdField Name of field to store the array of + parent cells' multidimensional indices. + If empty, the data is not provided. @param domainIdField Name of field to store the (axom::IndexType) parent domain ids. If omitted, the data is not provided. @@ -310,6 +311,9 @@ class MarchingCubesSingleDomain virtual void setContourValue(double contourVal) = 0; //!@brief Compute the contour mesh. virtual void computeContourMesh() = 0; + + //@{ + //!@name Output methods //!@brief Return number of contour mesh facets generated. virtual axom::IndexType getContourCellCount() const = 0; /*! @@ -321,6 +325,8 @@ class MarchingCubesSingleDomain virtual void populateContourMesh( axom::mint::UnstructuredMesh &mesh, const std::string &cellIdField) const = 0; + //@} + virtual ~ImplBase() { } }; diff --git a/src/axom/quest/detail/MarchingCubesFullParallel.hpp b/src/axom/quest/detail/MarchingCubesFullParallel.hpp index 90d1a85126..57ff2bb892 100644 --- a/src/axom/quest/detail/MarchingCubesFullParallel.hpp +++ b/src/axom/quest/detail/MarchingCubesFullParallel.hpp @@ -102,8 +102,8 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase void computeContourMesh() override { markCrossings(); - new_scanCrossings(); - new_computeContour(); + scanCrossings(); + computeContour(); } /*! @@ -255,7 +255,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase and m_contourCellParents arrays that defines the unstructured contour mesh. */ - void scanCrossings() + void old_scanCrossings() { const axom::IndexType parentCellCount = m_caseIds.size(); auto caseIdsView = m_caseIds.view(); @@ -314,7 +314,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase sizeof(facetIncrs_back)); m_facetCount = firstFacetIds_back + facetIncrs_back; } - void new_scanCrossings() + void scanCrossings() { #if defined(AXOM_USE_RAJA) #ifdef __INTEL_LLVM_COMPILER @@ -391,7 +391,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_firstFacetIds.resize(m_crossingCount); } - void computeContour() + void old_computeContour() { // // Fill in surface mesh data. @@ -450,7 +450,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase axom::for_all(0, parentCellCount, gen_for_parent_cell); } - void new_computeContour() + void computeContour() { // // Fill in surface mesh data. @@ -537,14 +537,14 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase template AXOM_HOST_DEVICE typename std::enable_if::type - get_corner_coords_and_values(IndexType cellNum, + get_corner_coords_and_values(IndexType parentCellId, Point cornerCoords[], double cornerValues[]) const { const auto& x = coordsViews[0]; const auto& y = coordsViews[1]; - const auto c = indexer.toMultiIndex(cellNum); + const auto c = indexer.toMultiIndex(parentCellId); const auto& i = c[0]; const auto& j = c[1]; @@ -562,7 +562,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase } template AXOM_HOST_DEVICE typename std::enable_if::type - get_corner_coords_and_values(IndexType cellNum, + get_corner_coords_and_values(IndexType parentCellId, Point cornerCoords[], double cornerValues[]) const { @@ -570,7 +570,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase const auto& y = coordsViews[1]; const auto& z = coordsViews[2]; - const auto c = indexer.toMultiIndex(cellNum); + const auto c = indexer.toMultiIndex(parentCellId); const auto& i = c[0]; const auto& j = c[1]; const auto& k = c[2]; @@ -814,6 +814,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase mesh.appendNodes((double*)contourNodeCoords.data(), contourNodeCoords.size()); + for(int n = 0; n < addedCellCount; ++n) { MIdx cornerIds = contourCellCorners[n]; @@ -823,8 +824,9 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase { cornerIds[d] += priorNodeCount; } - mesh.appendCell(cornerIds); + mesh.appendCell(cornerIds); // This takes too long! } + axom::IndexType numComponents = -1; axom::IndexType* cellIdPtr = mesh.getFieldPtr(cellIdField, From 95521e38143ae2bbe089c4ce26ac3f6f2b7d61b6 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 25 Jan 2024 12:30:59 -0800 Subject: [PATCH 03/61] Partially working implementatino with stream-lined output data. Remove populateContourMesh for single-domain versions, because all output data now go into a buffer managed by the multidomain version. --- src/axom/quest/ArrayIndexer.hpp | 42 ++++- src/axom/quest/CMakeLists.txt | 2 +- src/axom/quest/MarchingCubes.cpp | 156 ++++++++++++++-- src/axom/quest/MarchingCubes.hpp | 159 +++++++++++++++-- src/axom/quest/MeshViewUtil.hpp | 9 + .../detail/MarchingCubesFullParallel.hpp | 118 +++++++++--- .../detail/MarchingCubesHybridParallel.hpp | 168 ++++++++++++------ .../examples/quest_marching_cubes_example.cpp | 55 ++++-- src/axom/quest/tests/quest_array_indexer.cpp | 14 +- 9 files changed, 599 insertions(+), 124 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index a43ec946d7..84e417e9bf 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -31,6 +31,22 @@ class ArrayIndexer @param [in] order: c is column major; r is row major. */ ArrayIndexer(const axom::StackArray& lengths, char order) + { + initialize(lengths, order); + } + + //!@brief Constructor for arbitrary-stride indexing. + ArrayIndexer(const axom::StackArray& strides) : m_strides(strides) + { + initialize(strides); + } + + /*! + @brief Initialize for row- or column major indexing. + @param [in] shape Shape of the array + @param [in] order: c is column major; r is row major. + */ + inline AXOM_HOST_DEVICE void initialize(const axom::StackArray& shape, char order) { SLIC_ASSERT(order == 'c' || order == 'r'); if(order == 'r') @@ -42,7 +58,7 @@ class ArrayIndexer m_strides[0] = 1; for(int d = 1; d < DIM; ++d) { - m_strides[d] = m_strides[d - 1] * lengths[d - 1]; + m_strides[d] = m_strides[d - 1] * shape[d - 1]; } } else @@ -54,14 +70,16 @@ class ArrayIndexer m_strides[DIM - 1] = 1; for(int d = DIM - 2; d >= 0; --d) { - m_strides[d] = m_strides[d + 1] * lengths[d + 1]; + m_strides[d] = m_strides[d + 1] * shape[d + 1]; } } + SLIC_ASSERT((DIM == 1 && getOrder() == 'r' | 's') || (getOrder() == order)); } - //!@brief Constructor for arbitrary-stride indexing. - ArrayIndexer(const axom::StackArray& strides) : m_strides(strides) + //!@brief Initialize for arbitrary-stride indexing. + inline AXOM_HOST_DEVICE void initialize(const axom::StackArray& strides) { + m_strides = strides; for(int d = 0; d < DIM; ++d) { m_slowestDirs[d] = d; @@ -90,6 +108,22 @@ class ArrayIndexer return m_strides; } + /*! + @brief Get the stride order (row- or column-major, or something else). + + @return 'r' or 'c' for row- or column major, '\0' for neither, + or if DIM == 1, the value of 'r' | 'c'. + */ + inline AXOM_HOST_DEVICE char getOrder() const + { + char order = 'r' | 'c'; + for(int d=0; d < DIM - 1; ++d) + { + order &= m_slowestDirs[d] < m_slowestDirs[d+1] ? 'c' : 'r'; + } + return order; + } + //!@brief Convert multidimensional index to flat index. inline AXOM_HOST_DEVICE T toFlatIndex(const axom::StackArray& multiIndex) const { diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index 130b411e08..9ddc1e2059 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -119,7 +119,7 @@ endif() blt_list_append( TO quest_headers - ELEMENTS MarchingCubes.hpp detail/MarchingCubesHybridParallel.hpp detail/MarchingCubesFullParallel.hpp + ELEMENTS MarchingCubes.hpp detail/MarchingCubesFullParallel.hpp IF CONDUIT_FOUND ) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index db8a0178ca..adc929b120 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -13,7 +13,7 @@ #include "axom/core/execution/execution_space.hpp" #include "axom/quest/MarchingCubes.hpp" -#include "axom/quest/detail/MarchingCubesHybridParallel.hpp" +// #include "axom/quest/detail/MarchingCubesHybridParallel.hpp" #include "axom/quest/detail/MarchingCubesFullParallel.hpp" #include "axom/fmt.hpp" @@ -41,6 +41,7 @@ MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, for(auto& dom : bpMesh.children()) { m_singles.emplace_back(new MarchingCubesSingleDomain(m_runtimePolicy, + m_dataParallelism, dom, m_topologyName, maskField)); @@ -59,6 +60,40 @@ void MarchingCubes::setFunctionField(const std::string& fcnField) void MarchingCubes::computeIsocontour(double contourVal) { +#if 1 + // Mark and scan domains while adding up their + // facet counts to get the total facet counts. + m_facetIndexOffsets.resize(m_singles.size()); + m_facetCount = 0; + for(axom::IndexType d=0; dsetOutputBuffers(facetNodeIdsView, + facetNodeCoordsView, + facetParentIdsView, + m_facetIndexOffsets[d]); + } + + for(axom::IndexType d=0; dcomputeContour(); + } +#else SLIC_ASSERT_MSG(!m_fcnFieldName.empty(), "You must call setFunctionField before computeIsocontour."); @@ -68,6 +103,7 @@ void MarchingCubes::computeIsocontour(double contourVal) single->setDataParallelism(m_dataParallelism); single->computeIsocontour(contourVal); } +#endif } axom::IndexType MarchingCubes::getContourCellCount() const @@ -98,9 +134,8 @@ void MarchingCubes::populateContourMesh( if(!cellIdField.empty() && !mesh.hasField(cellIdField, axom::mint::CELL_CENTERED)) { - mesh.createField(cellIdField, - axom::mint::CELL_CENTERED, - mesh.getDimension()); + // Create cellId field, currently the multidimensional index of the parent cell. + mesh.createField(cellIdField, axom::mint::CELL_CENTERED); } if(!domainIdField.empty() && @@ -115,6 +150,33 @@ void MarchingCubes::populateContourMesh( mesh.reserveCells(contourCellCount); mesh.reserveNodes(contourNodeCount); +#if 1 + if (m_facetCount) { + mesh.appendCells(m_facetNodeIds.data(), m_facetCount); + mesh.appendNodes(m_facetNodeCoords.data(), mesh.getDimension()*m_facetCount); + + axom::IndexType* cellIdPtr = + mesh.getFieldPtr(cellIdField, + axom::mint::CELL_CENTERED); + axom::copy(cellIdPtr, + m_facetParentIds.data(), + m_facetCount*sizeof(axom::IndexType)); + + // TODO: Move domain id stuff into a separate function. + auto* domainIdPtr = + mesh.getFieldPtr(domainIdField, + axom::mint::CELL_CENTERED); + for(int d = 0; d < m_singles.size(); ++d) + { + axom::detail::ArrayOps::fill( + domainIdPtr, + m_facetIndexOffsets[d], + m_singles[d]->getContourCellCount(), + execution_space::allocatorID(), + m_singles[d]->getDomainId(d)); + } + } +#else // Populate mesh from single domains and add domain id if requested. for(int dId = 0; dId < m_singles.size(); ++dId) { @@ -140,15 +202,64 @@ void MarchingCubes::populateContourMesh( userDomainId); } } +#endif SLIC_ASSERT(mesh.getNumberOfNodes() == contourNodeCount); SLIC_ASSERT(mesh.getNumberOfCells() == contourCellCount); } +void MarchingCubes::allocateOutputBuffers() +{ + int allocatorId = -1; + if(m_runtimePolicy == MarchingCubes::RuntimePolicy::seq) + { + allocatorId = axom::execution_space::allocatorID(); + } +#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP + else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::omp) + { + allocatorId = axom::execution_space::allocatorID(); + } +#endif +#ifdef AXOM_RUNTIME_POLICY_USE_CUDA + else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::cuda) + { + allocatorId = axom::execution_space>::allocatorID(); + } +#endif +#ifdef AXOM_RUNTIME_POLICY_USE_HIP + else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::hip) + { + allocatorId = axom::execution_space>::allocatorID(); + } +#endif + else + { + SLIC_ERROR(axom::fmt::format( + "MarchingCubes doesn't recognize runtime policy {}", + m_runtimePolicy)); + } + + if (!m_singles.empty()) + { + int ndim = m_singles[0]->spatialDimension(); + const auto nodeCount = m_facetCount * ndim; + m_facetNodeIds = + axom::Array({m_facetCount, ndim}, allocatorId); + m_facetNodeCoords = + axom::Array({nodeCount, ndim}, allocatorId); + axom::StackArray t1{m_facetCount}; + m_facetParentIds = + axom::Array(t1, allocatorId); + } +} + MarchingCubesSingleDomain::MarchingCubesSingleDomain(RuntimePolicy runtimePolicy, + MarchingCubesDataParallelism dataPar, const conduit::Node& dom, const std::string& topologyName, const std::string& maskField) : m_runtimePolicy(runtimePolicy) + , m_dataParallelism(dataPar) , m_dom(nullptr) , m_ndim(0) , m_topologyName(topologyName) @@ -157,7 +268,32 @@ MarchingCubesSingleDomain::MarchingCubesSingleDomain(RuntimePolicy runtimePolicy , m_maskFieldName(maskField) , m_maskPath(maskField.empty() ? std::string() : "fields/" + maskField) { + // Set domain first, to get m_ndim, which is required to allocate m_impl. setDomain(dom); + + /* + We have 2 implementations. MarchingCubesHybridParallel is faster on the host + and MarchingCubesFullParallel is faster on GPUs. Both work in all cases. + We can choose based on runtime policy or by user choice + */ + if(m_dataParallelism == + axom::quest::MarchingCubesDataParallelism::hybridParallel || + (m_dataParallelism == axom::quest::MarchingCubesDataParallelism::byPolicy && + m_runtimePolicy == RuntimePolicy::seq)) + { + SLIC_WARNING("Not really using hybrid while developing. Using full parallel."); + m_impl = axom::quest::detail::marching_cubes::newMarchingCubesFullParallel( + m_runtimePolicy, + m_ndim); + } + else + { + m_impl = axom::quest::detail::marching_cubes::newMarchingCubesFullParallel( + m_runtimePolicy, + m_ndim); + } + + m_impl->initialize(*m_dom, m_topologyName, m_maskFieldName); return; } @@ -191,16 +327,6 @@ void MarchingCubesSingleDomain::setDomain(const conduit::Node& dom) "MarchingCubes currently requires contiguous coordinates layout."); } -void MarchingCubesSingleDomain::setFunctionField(const std::string& fcnField) -{ - m_fcnFieldName = fcnField; - m_fcnPath = "fields/" + fcnField; - SLIC_ASSERT(m_dom->has_path(m_fcnPath)); - SLIC_ASSERT(m_dom->fetch_existing(m_fcnPath + "/association").as_string() == - "vertex"); - SLIC_ASSERT(m_dom->has_path(m_fcnPath + "/values")); -} - int MarchingCubesSingleDomain::getDomainId(int defaultId) const { int rval = defaultId; @@ -211,6 +337,7 @@ int MarchingCubesSingleDomain::getDomainId(int defaultId) const return rval; } +#if 0 void MarchingCubesSingleDomain::computeIsocontour(double contourVal) { SLIC_ASSERT_MSG(!m_fcnFieldName.empty(), @@ -238,6 +365,7 @@ void MarchingCubesSingleDomain::computeIsocontour(double contourVal) m_impl->setContourValue(contourVal); m_impl->computeContourMesh(); } +#endif } // end namespace quest } // end namespace axom diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 966e3954b3..5e9efd2889 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -82,8 +82,9 @@ class MarchingCubesSingleDomain; * const std::string &functionName, * double contourValue ) * { - * MarchingCubes mc(meshNode, coordsName); - * setFunctionField(functionName); + * MarchingCubes mc(axom::runtime_policy::Policy::seq, + * meshNode, coordsName); + * mc.setFunctionField(functionName); * mc.computeIsocontour(contourValue); * axom::mint::UnstructuredMesh * contourMesh(3, min::CellType::Triangle); @@ -146,6 +147,33 @@ class MarchingCubes //!@brief Get number of nodes in the generated contour mesh. axom::IndexType getContourNodeCount() const; + /*! + @brief Return pointer to facet corner node indices (connectivity) + + The buffer size is the x getContourCellCount(). + Memory space of data depends on runtime policy. + */ + axom::ArrayView getContourFacetCorners() const + { return m_facetNodeIds.view(); } + + /*! + @brief Return pointer to node coordinates. + + The buffer size is x getContourNodeCount(). + Memory space of data depends on runtime policy. + */ + axom::ArrayView getContourNodeCoords() const + { return m_facetNodeCoords.view(); } + + /*! + @brief Return pointer to parent cell indices + + The buffer size is getContourCellCount(). + Memory space of data depends on runtime policy. + */ + axom::ArrayView getContourFacetParents() const + { return m_facetParentIds.view(); } + /*! @brief Put generated contour in a mint::UnstructuredMesh. @param mesh Output contour mesh @@ -192,7 +220,37 @@ class MarchingCubes std::string m_maskFieldName; std::string m_maskPath; + //!@brief First facet index from each parent domain. + axom::Array m_facetIndexOffsets; + + //!@brief Facet count over all parent domains. + axom::IndexType m_facetCount = 0; + + //@{ + //!@name Generated contour mesh, shared with singles. + /*! + @brief Corners (index into m_facetNodeCoords) of generated facets. + @see allocateOutputBuffers(). + */ + axom::Array m_facetNodeIds; + + /*! + @brief Coordinates of generated surface mesh nodes. + @see allocateOutputBuffers(). + */ + axom::Array m_facetNodeCoords; + + /*! + @brief Flat index of parent cell of facets. + @see allocateOutputBuffers(). + */ + axom::Array m_facetParentIds; + //@} + void setMesh(const conduit::Node &bpMesh); + + //!@brief Allocate output buffers corresponding to runtime policy. + void allocateOutputBuffers(); }; /*! @@ -212,6 +270,7 @@ class MarchingCubesSingleDomain * \param [in] runtimePolicy A value from RuntimePolicy. * The simplest policy is RuntimePolicy::seq, which specifies * running sequentially on the CPU. + * \param [in] dataPar Choice of data-parallel implementation. * \param [in] dom Blueprint single-domain mesh containing scalar field. * \param [in] topologyName Name of Blueprint topology to use in \a dom * \param [in] maskField Cell-based std::int32_t mask field. If provided, @@ -230,10 +289,13 @@ class MarchingCubesSingleDomain * transformation and storage of the temporary contiguous layout. */ MarchingCubesSingleDomain(RuntimePolicy runtimePolicy, + MarchingCubesDataParallelism dataPar, const conduit::Node &dom, const std::string &topologyName, const std::string &maskfield); + int spatialDimension() const { return m_ndim; } + void setDataParallelism(MarchingCubesDataParallelism &dataPar) { m_dataParallelism = dataPar; @@ -244,7 +306,39 @@ class MarchingCubesSingleDomain in the input mesh. \param [in] fcnField Name of node-based scalar function values. */ - void setFunctionField(const std::string &fcnField); + void setFunctionField(const std::string &fcnField) + { + m_fcnFieldName = fcnField; + m_fcnPath = "fields/" + fcnField; + SLIC_ASSERT(m_dom->has_path(m_fcnPath)); + SLIC_ASSERT(m_dom->fetch_existing(m_fcnPath + "/association").as_string() == + "vertex"); + SLIC_ASSERT(m_dom->has_path(m_fcnPath + "/values")); + if (m_impl) m_impl->setFunctionField(fcnField); + } + + void setContourValue(double contourVal) + { + m_contourVal = contourVal; + if (m_impl) m_impl->setContourValue(m_contourVal); + } + + // Methods trivially delegated to implementation. + void markCrossings() { m_impl->markCrossings(); } + void scanCrossings() { m_impl->scanCrossings(); } + axom::IndexType getContourCellCount() { return m_impl->getContourCellCount(); } + void setOutputBuffers( + axom::ArrayView& facetNodeIds, + axom::ArrayView& facetNodeCoords, + axom::ArrayView& facetParentIds, + axom::IndexType facetIndexOffset) + { + m_impl->setOutputBuffers( facetNodeIds, + facetNodeCoords, + facetParentIds, + facetIndexOffset ); + } + void computeContour() { m_impl->computeContour(); } /*! @brief Get the Blueprint domain id specified in \a state/domain_id @@ -252,15 +346,17 @@ class MarchingCubesSingleDomain */ int getDomainId(int defaultId) const; +#if 0 /*! * \brief Compute the isocontour. * * \param [in] contourVal isocontour value * * Compute isocontour using the marching cubes algorithm. - * To get the isocontour mesh, use populateContourMesh. */ void computeIsocontour(double contourVal = 0.0); +#endif + //!@brief Get number of cells in the generated contour mesh. axom::IndexType getContourCellCount() const @@ -278,6 +374,7 @@ class MarchingCubesSingleDomain return m_ndim * getContourCellCount(); } +#if 0 /*! @brief Put generated contour surface in a mint::UnstructuredMesh object. @@ -292,30 +389,49 @@ class MarchingCubesSingleDomain { m_impl->populateContourMesh(mesh, cellIdField); } +#endif /*! - @brief Base class for implementations templated on dimension - and execution space. + @brief Base class for implementations templated on dimension DIM + and execution space ExecSpace. + + Implementation details templated on DIM and ExecSpace cannot + be in MarchingCubesSingleDomain so should live in this class. This class allows m_impl to refer to any implementation used at runtime. */ struct ImplBase { - //!@brief Prepare internal data for operating on the given domain. + /*! + @brief Prepare internal data for operating on the given domain. + + Put in here codes that can't be in MarchingCubesSingleDomain + due to template use (DIM and ExecSpace). + */ virtual void initialize(const conduit::Node &dom, const std::string &topologyName, - const std::string &fcnPath, const std::string &maskPath) = 0; - //!@brief Set the contour value + + virtual void setFunctionField(const std::string& fcnFieldName) = 0; virtual void setContourValue(double contourVal) = 0; + + //@{ + //!@name Distinct phases in contour generation. //!@brief Compute the contour mesh. - virtual void computeContourMesh() = 0; + //!@brief Mark parent cells that cross the contour value. + virtual void markCrossings() = 0; + //!@brief Scan operations to determine counts and offsets. + virtual void scanCrossings() = 0; + //!@brief Compute contour data. + virtual void computeContour() = 0; + //@} //@{ //!@name Output methods //!@brief Return number of contour mesh facets generated. virtual axom::IndexType getContourCellCount() const = 0; +#if 0 /*! @brief Populate output mesh object with generated contour. @@ -325,9 +441,28 @@ class MarchingCubesSingleDomain virtual void populateContourMesh( axom::mint::UnstructuredMesh &mesh, const std::string &cellIdField) const = 0; +#endif //@} + void setOutputBuffers( + axom::ArrayView& facetNodeIds, + axom::ArrayView& facetNodeCoords, + axom::ArrayView& facetParentIds, + axom::IndexType facetIndexOffset) + { + m_facetNodeIds = facetNodeIds; + m_facetNodeCoords = facetNodeCoords; + m_facetParentIds = facetParentIds; + m_facetIndexOffset = facetIndexOffset; + } + virtual ~ImplBase() { } + + double m_contourVal = 0.0; + axom::ArrayView m_facetNodeIds; + axom::ArrayView m_facetNodeCoords; + axom::ArrayView m_facetParentIds; + axom::IndexType m_facetIndexOffset = -1; }; private: @@ -354,12 +489,14 @@ class MarchingCubesSingleDomain //!@brief Path to mask in m_dom. const std::string m_maskPath; + double m_contourVal = 0.0; + std::unique_ptr m_impl; /*! * \brief Set the blueprint single-domain mesh. * - * Some data from \a dom may be cached by the constructor. + * Some data from \a dom may be cached. */ void setDomain(const conduit::Node &dom); diff --git a/src/axom/quest/MeshViewUtil.hpp b/src/axom/quest/MeshViewUtil.hpp index cb0eb3cd1c..7f898cfa09 100644 --- a/src/axom/quest/MeshViewUtil.hpp +++ b/src/axom/quest/MeshViewUtil.hpp @@ -214,6 +214,15 @@ class MeshViewUtil //@{ //@name Setting up + //!@brief Default constructor doesn't set up the view utility. + MeshViewUtil() + : m_dom(nullptr) + , m_topology(nullptr) + , m_coordset(nullptr) + , m_cdom(nullptr) + , m_ctopology(nullptr) + , m_ccoordset(nullptr) + {} /*! @brief Construct view of a non-const domain. diff --git a/src/axom/quest/detail/MarchingCubesFullParallel.hpp b/src/axom/quest/detail/MarchingCubesFullParallel.hpp index 57ff2bb892..efdf52f77e 100644 --- a/src/axom/quest/detail/MarchingCubesFullParallel.hpp +++ b/src/axom/quest/detail/MarchingCubesFullParallel.hpp @@ -55,7 +55,6 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase @param dom Blueprint structured mesh domain @param topologyName Name of mesh topology (see blueprint mesh documentation) - @param fcnFieldName Name of nodal function is in dom @param maskFieldName Name of integer cell mask function is in dom Set up views to domain data and allocate other data to work on the @@ -66,22 +65,20 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase */ AXOM_HOST void initialize(const conduit::Node& dom, const std::string& topologyName, - const std::string& fcnFieldName, const std::string& maskFieldName = {}) override { SLIC_ASSERT(conduit::blueprint::mesh::topology::dims(dom.fetch_existing( axom::fmt::format("topologies/{}", topologyName))) == DIM); - clear(); + // clear(); - axom::quest::MeshViewUtil mvu(dom, topologyName); + m_mvu = axom::quest::MeshViewUtil(dom, topologyName); - m_bShape = mvu.getCellShape(); - m_coordsViews = mvu.getConstCoordsViews(false); - m_fcnView = mvu.template getConstFieldView(fcnFieldName, false); + m_bShape = m_mvu.getCellShape(); + m_coordsViews = m_mvu.getConstCoordsViews(false); if(!maskFieldName.empty()) { - m_maskView = mvu.template getConstFieldView(maskFieldName, false); + m_maskView = m_mvu.template getConstFieldView(maskFieldName, false); } /* @@ -93,26 +90,36 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_caseIds.fill(0); } - //!@brief Set a value to find the contour for. + /*! + @brief Set the scale field name + @param fcnFieldName Name of nodal function is in dom + */ + void setFunctionField(const std::string& fcnFieldName) override + { + m_fcnView = m_mvu.template getConstFieldView(fcnFieldName, false); + } + void setContourValue(double contourVal) override { m_contourVal = contourVal; } +#if 0 void computeContourMesh() override { markCrossings(); scanCrossings(); computeContour(); } +#endif /*! @brief Implementation of virtual markCrossings. Virtual methods cannot be templated, so this implementation - delegates to a name templated on DIM. + delegates to an implementation templated on DIM. */ - void markCrossings() { markCrossings_dim(); } + void markCrossings() override { markCrossings_dim(); } //!@brief Populate m_caseIds with crossing indices. template @@ -314,7 +321,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase sizeof(facetIncrs_back)); m_facetCount = firstFacetIds_back + facetIncrs_back; } - void scanCrossings() + void scanCrossings() override { #if defined(AXOM_USE_RAJA) #ifdef __INTEL_LLVM_COMPILER @@ -332,6 +339,10 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase // Initialize crossingFlags to 0 or 1, prefix-sum it, and count the // crossings. // + // All 1D array data alligns with m_caseId, which is column-major + // (the default for Array class), leading to *column-major* parent + // cell ids, regardless of the ordering of the input mesh data. + // axom::Array crossingFlags(parentCellCount); auto crossingFlagsView = crossingFlags.view(); @@ -391,6 +402,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_firstFacetIds.resize(m_crossingCount); } +#if 0 void old_computeContour() { // @@ -450,12 +462,60 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase axom::for_all(0, parentCellCount, gen_for_parent_cell); } - void computeContour() +#endif + void computeContour() override { - // - // Fill in surface mesh data. - // +#if 1 + const auto facetIncrsView = m_facetIncrs.view(); + const auto firstFacetIdsView = m_firstFacetIds.view(); + const auto crossingParentIdsView = m_crossingParentIds.view(); + const auto caseIdsView = m_caseIds.view(); + + // Internal contour mesh data to populate + axom::ArrayView facetNodeIdsView = m_facetNodeIds; + axom::ArrayView facetNodeCoordsView = m_facetNodeCoords; + axom::ArrayView facetParentIdsView = m_facetParentIds; + const axom::IndexType facetIndexOffset = m_facetIndexOffset; + + ComputeContour_Util ccu(m_contourVal, + m_caseIds.strides(), + m_fcnView, + m_coordsViews); + + auto gen_for_parent_cell = AXOM_LAMBDA(axom::IndexType crossingId) + { + auto parentCellId = crossingParentIdsView[crossingId]; + auto caseId = caseIdsView.flatIndex(parentCellId); + Point cornerCoords[CELL_CORNER_COUNT]; + double cornerValues[CELL_CORNER_COUNT]; + ccu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); + + auto additionalFacets = facetIncrsView[crossingId]; + auto firstFacetId = facetIndexOffset + firstFacetIdsView[crossingId]; + + for(axom::IndexType fId = 0; fId < additionalFacets; ++fId) + { + axom::IndexType newFacetId = firstFacetId + fId; + axom::IndexType firstCornerId = newFacetId * DIM; + + facetParentIdsView[newFacetId] = parentCellId; + + for(axom::IndexType d = 0; d < DIM; ++d) + { + axom::IndexType newCornerId = firstCornerId + d; + facetNodeIdsView[newFacetId][d] = newCornerId; + + int edge = cases_table(caseId, fId * DIM + d); + ccu.linear_interp(edge, + cornerCoords, + cornerValues, + &facetNodeCoordsView(newCornerId, 0)); + } + } + }; + axom::for_all(0, m_crossingCount, gen_for_parent_cell); +#else // Allocate space for surface mesh. const axom::IndexType cornersCount = DIM * m_facetCount; m_contourCellParents.resize(m_facetCount); @@ -508,6 +568,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase }; axom::for_all(0, m_crossingCount, gen_for_parent_cell); +#endif } /*! @@ -602,7 +663,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase int edgeIdx, const Point cornerCoords[4], const double nodeValues[4], - Point& crossingPt) const + double* /* Point& */ crossingPt) const { // STEP 0: get the edge node indices // 2 nodes define the edge. n1 and n2 are the indices of @@ -623,13 +684,13 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase if(axom::utilities::isNearlyEqual(contourVal, f1) || axom::utilities::isNearlyEqual(f1, f2)) { - crossingPt = p1; + crossingPt[0] = p1[0]; crossingPt[1] = p1[1]; // crossingPt = p1; return; } if(axom::utilities::isNearlyEqual(contourVal, f2)) { - crossingPt = p2; + crossingPt[0] = p2[0]; crossingPt[1] = p2[1]; // crossingPt = p2; return; } @@ -649,7 +710,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase int edgeIdx, const Point cornerCoords[8], const double nodeValues[8], - Point& crossingPt) const + double* /* Point& */ crossingPt) const { // STEP 0: get the edge node indices // 2 nodes define the edge. n1 and n2 are the indices of @@ -676,13 +737,13 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase if(axom::utilities::isNearlyEqual(contourVal, f1) || axom::utilities::isNearlyEqual(f1, f2)) { - crossingPt = p1; + crossingPt[0] = p1[0]; crossingPt[1] = p1[1]; crossingPt[2] = p1[2]; // crossingPt = p1; return; } if(axom::utilities::isNearlyEqual(contourVal, f2)) { - crossingPt = p2; + crossingPt[0] = p2[0]; crossingPt[1] = p2[1]; crossingPt[2] = p2[2]; // crossingPt = p2; return; } @@ -745,6 +806,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase return cases3D[iCase][iEdge]; } +#if 0 /*! @brief Output contour mesh to a mint::UnstructuredMesh object. */ @@ -843,6 +905,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase } } } +#endif //!@brief Compute the case index into cases2D or cases3D. AXOM_HOST_DEVICE inline int compute_crossing_case(const double* f) const @@ -859,6 +922,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase return index; } +#if 0 //!@brief Clear data so you can rerun with a different contour value. void clear() { @@ -868,17 +932,21 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_crossingCount = 0; m_facetCount = 0; } +#endif /*! @brief Constructor. */ MarchingCubesFullParallel() +#if 0 : m_contourNodeCoords(0, 0) , m_contourCellCorners(0, 0) , m_contourCellParents(0, 0) +#endif { } private: + axom::quest::MeshViewUtil m_mvu; MIdx m_bShape; //!< @brief Blueprint cell data shape. // Views of parent domain data. @@ -911,6 +979,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase //!@brief Number of corners (nodes) on each parent cell. static constexpr std::uint8_t CELL_CORNER_COUNT = (DIM == 3) ? 8 : 4; +#if 0 //!@name Internal representation of generated contour mesh. //@{ //!@brief Coordinates of generated surface mesh nodes. @@ -922,10 +991,15 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase //!@brief Flat index of computational cell crossing the contour cell. axom::Array m_contourCellParents; //@} +#endif double m_contourVal = 0.0; }; +/*! + @brief Allocate a MarchingCubesFullParallel object, template-specialized + for caller-specified runtime policy and physical dimension. +*/ static std::unique_ptr newMarchingCubesFullParallel(MarchingCubes::RuntimePolicy runtimePolicy, int dim) { diff --git a/src/axom/quest/detail/MarchingCubesHybridParallel.hpp b/src/axom/quest/detail/MarchingCubesHybridParallel.hpp index 8515c39c14..3ab2a97d91 100644 --- a/src/axom/quest/detail/MarchingCubesHybridParallel.hpp +++ b/src/axom/quest/detail/MarchingCubesHybridParallel.hpp @@ -76,22 +76,20 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase */ AXOM_HOST void initialize(const conduit::Node& dom, const std::string& topologyName, - const std::string& fcnFieldName, const std::string& maskFieldName = {}) override { SLIC_ASSERT(conduit::blueprint::mesh::topology::dims(dom.fetch_existing( axom::fmt::format("topologies/{}", topologyName))) == DIM); - clear(); + // clear(); - axom::quest::MeshViewUtil mvu(dom, topologyName); + m_mvu = axom::quest::MeshViewUtil(dom, topologyName); - m_bShape = mvu.getCellShape(); - m_coordsViews = mvu.getConstCoordsViews(false); - m_fcnView = mvu.template getConstFieldView(fcnFieldName, false); + m_bShape = m_mvu.getCellShape(); + m_coordsViews = m_mvu.getConstCoordsViews(false); if(!maskFieldName.empty()) { - m_maskView = mvu.template getConstFieldView(maskFieldName, false); + m_maskView = m_mvu.template getConstFieldView(maskFieldName, false); } /* @@ -103,18 +101,28 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase m_caseIds.fill(0); } - //!@brief Set a value to find the contour for. + /*! + @brief Set the scale field name + @param fcnFieldName Name of nodal function is in dom + */ + void setFunctionField(const std::string& fcnFieldName) override + { + m_fcnView = m_mvu.template getConstFieldView(fcnFieldName, false); + } + void setContourValue(double contourVal) override { m_contourVal = contourVal; } +#if 0 void computeContourMesh() override { markCrossings(); scanCrossings(); computeContour(); } +#endif /*! @brief Implementation of virtual markCrossings. @@ -122,7 +130,7 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase Virtual methods cannot be templated, so this implementation delegates to a name templated on DIM. */ - void markCrossings() { markCrossings_dim(); } + void markCrossings() override { markCrossings_dim(); } //!@brief Populate m_caseIds with crossing indices. template @@ -267,7 +275,7 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase We sum up the number of contour surface cells from the crossings, allocate space, then populate it. */ - void scanCrossings() + void scanCrossings() override { const axom::IndexType parentCellCount = m_caseIds.size(); auto caseIdsView = m_caseIds.view(); @@ -578,8 +586,60 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase } }; // ComputeContour_Util - void computeContour() + void computeContour() override { +#if 1 + auto crossingsView = m_crossings.view(); + + // Internal contour mesh data to populate + axom::ArrayView facetNodeIdsView = m_facetNodeIds; + axom::ArrayView facetNodeCoordsView = m_facetNodeCoords; + axom::ArrayView facetParentIdsView = m_facetParentIds; + const axom::IndexType facetIndexOffset = m_facetIndexOffset; + + ComputeContour_Util ccu(m_contourVal, + m_caseIds.strides(), + m_fcnView, + m_coordsViews); + + auto loopBody = AXOM_LAMBDA(axom::IndexType iCrossing) + { + const auto& crossingInfo = crossingsView[iCrossing]; + const IndexType crossingCellCount = num_contour_cells(crossingInfo.caseNum); + SLIC_ASSERT(crossingCellCount > 0); + + // Parent cell data for interpolating new node coordinates. + Point cornerCoords[CELL_CORNER_COUNT]; + double cornerValues[CELL_CORNER_COUNT]; + ccu.get_corner_coords_and_values(crossingInfo.parentCellNum, + cornerCoords, + cornerValues); + + /* + Create the new cell and its DIM nodes. New nodes are on + parent cell edges where the edge intersects the isocontour. + linear_interp for the exact coordinates. + */ + for(int iCell = 0; iCell < crossingCellCount; ++iCell) + { + IndexType newFacetId = facetIndexOffset + crossingInfo.firstSurfaceCellId + iCell; + facetParentIdsView[newFacetId] = crossingInfo.parentCellNum; + for(int d = 0; d < DIM; ++d) + { + IndexType newNodeId = newFacetId * DIM + d; + axom::StackArray idx{newFacetId, d}; + facetNodeIdsView[idx] = newNodeId; + + const int edge = cases_table(crossingInfo.caseNum, iCell * DIM + d); + ccu.linear_interp(edge, + cornerCoords, + cornerValues, + facetNodeCoordsView[newNodeId]); + } + } + }; + axom::for_all(0, m_crossingCount, loopBody); +#else auto crossingsView = m_crossings.view(); /* @@ -587,13 +647,13 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase reallocation. */ const axom::IndexType contourNodeCount = DIM * m_contourCellCount; - m_contourNodeCoords.resize(contourNodeCount); - m_contourCellCorners.resize(m_contourCellCount); - m_contourCellParents.resize(m_contourCellCount); + m_facetNodeCoords.resize(contourNodeCount); + m_facetNodeIds.resize(m_contourCellCount); + m_facetParentIds.resize(m_contourCellCount); - auto nodeCoordsView = m_contourNodeCoords.view(); - auto cellCornersView = m_contourCellCorners.view(); - auto cellParentsView = m_contourCellParents.view(); + auto nodeCoordsView = m_facetNodeCoords.view(); + auto cellCornersView = m_facetNodeIds.view(); + auto cellParentsView = m_facetParentIds.view(); ComputeContour_Util ccu(m_contourVal, m_caseIds.strides(), @@ -640,6 +700,7 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase } }; axom::for_all(0, m_crossingCount, loopBody); +#endif } // These 4 functions provide access to the look-up table @@ -690,6 +751,7 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase return cases3D[iCase][iEdge]; } +#if 0 /*! @brief Output contour mesh to a mint::UnstructuredMesh object. */ @@ -708,27 +770,27 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase { populateContourMesh(mesh, cellIdField, - m_contourNodeCoords, - m_contourCellCorners, - m_contourCellParents); + m_facetNodeCoords, + m_facetNodeIds, + m_facetParentIds); } else { - axom::Array contourNodeCoords( - m_contourNodeCoords, + axom::Array facetNodeCoords( + m_facetNodeCoords, hostAllocatorID); - axom::Array contourCellCorners( - m_contourCellCorners, + axom::Array facetNodeIds( + m_facetNodeIds, hostAllocatorID); - axom::Array contourCellParents( - m_contourCellParents, + axom::Array facetParentIds( + m_facetParentIds, hostAllocatorID); populateContourMesh(mesh, cellIdField, - contourNodeCoords, - contourCellCorners, - contourCellParents); + facetNodeCoords, + facetNodeIds, + facetParentIds); } } @@ -736,10 +798,11 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase void populateContourMesh( axom::mint::UnstructuredMesh& mesh, const std::string& cellIdField, - const axom::Array contourNodeCoords, - const axom::Array contourCellCorners, - const axom::Array contourCellParents) const + const axom::Array facetNodeCoords, + const axom::Array facetNodeIds, + const axom::Array facetParentIds) const { + // AXOM_PERF_MARK_FUNCTION("MarchingCubesHybridParallel::populateContourMesh"); if(!cellIdField.empty() && !mesh.hasField(cellIdField, axom::mint::CELL_CENTERED)) { @@ -748,8 +811,8 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase DIM); } - const axom::IndexType addedCellCount = contourCellCorners.size(); - const axom::IndexType addedNodeCount = contourNodeCoords.size(); + const axom::IndexType addedCellCount = facetNodeIds.size(); + const axom::IndexType addedNodeCount = facetNodeCoords.size(); if(addedCellCount != 0) { const axom::IndexType priorCellCount = mesh.getNumberOfCells(); @@ -757,11 +820,11 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase mesh.reserveNodes(priorNodeCount + addedNodeCount); mesh.reserveCells(priorCellCount + addedCellCount); - mesh.appendNodes((double*)contourNodeCoords.data(), - contourNodeCoords.size()); + mesh.appendNodes((double*)facetNodeCoords.data(), + facetNodeCoords.size()); for(int n = 0; n < addedCellCount; ++n) { - MIdx cornerIds = contourCellCorners[n]; + MIdx cornerIds = facetNodeIds[n]; // Bump corner indices by priorNodeCount to avoid indices // used by other parents domains. for(int d = 0; d < DIM; ++d) @@ -782,10 +845,11 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase axom::ArrayIndexer si(m_caseIds.shape(), 'c'); for(axom::IndexType i = 0; i < addedCellCount; ++i) { - cellIdView[priorCellCount + i] = si.toMultiIndex(contourCellParents[i]); + cellIdView[priorCellCount + i] = si.toMultiIndex(facetParentIds[i]); } } } +#endif //!@brief Compute the case index into cases2D or cases3D. AXOM_HOST_DEVICE inline int compute_crossing_case(const double* f) const @@ -802,24 +866,23 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase return index; } +#if 0 //!@brief Clear data so you can rerun with a different contour value. void clear() { - m_contourNodeCoords.clear(); - m_contourCellCorners.clear(); - m_contourCellParents.clear(); + m_facetNodeCoords.clear(); + m_facetNodeIds.clear(); + m_facetParentIds.clear(); m_crossingCount = 0; m_contourCellCount = 0; } +#endif /*! @brief Constructor. */ MarchingCubesHybridParallel() : m_crossings(0, 0) - , m_contourNodeCoords(0, 0) - , m_contourCellCorners(0, 0) - , m_contourCellParents(0, 0) { } /*! @@ -839,6 +902,7 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase }; private: + axom::quest::MeshViewUtil m_mvu; MIdx m_bShape; //!< @brief Blueprint cell data shape. // Views of parent domain data. @@ -868,21 +932,13 @@ class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase //!@brief Number of corners (nodes) on each parent cell. static constexpr std::uint8_t CELL_CORNER_COUNT = (DIM == 3) ? 8 : 4; - //!@name Internal representation of generated contour mesh. - //@{ - //!@brief Coordinates of generated surface mesh nodes. - axom::Array m_contourNodeCoords; - - //!@brief Corners (index into m_contourNodeCoords) of generated contour cells. - axom::Array m_contourCellCorners; - - //!@brief Flat index of computational cell crossing the contour cell. - axom::Array m_contourCellParents; - //@} - double m_contourVal = 0.0; }; +/*! + @brief Allocate a MarchingCubesHybridParallel object, template-specialized + for caller-specified runtime policy and physical dimension. +*/ static std::unique_ptr newMarchingCubesHybridParallel(MarchingCubes::RuntimePolicy runtimePolicy, int dim) { diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 90517f296d..e489adc8d8 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -840,7 +840,7 @@ struct ContourTestBase extractTimer.start(); mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); extractTimer.stop(); - // printTimingStats(extractTimer, name() + " extract"); + printTimingStats(extractTimer, name() + " extract"); int localErrCount = 0; if(params.checkResults) @@ -1076,6 +1076,23 @@ struct ContourTestBase return view; } + axom::ArrayView get_parent_cell_id_view( + axom::mint::UnstructuredMesh& contourMesh) const + { + axom::IndexType numIdxComponents = -1; + axom::IndexType* ptr = + contourMesh.getFieldPtr(m_parentCellIdField, + axom::mint::CELL_CENTERED, + numIdxComponents); + + SLIC_ASSERT(numIdxComponents == 1); + + axom::ArrayView view( + (axom::IndexType*)ptr, + contourMesh.getNumberOfCells()); + return view; + } + /** Check that generated cells fall within their parents. */ @@ -1086,7 +1103,7 @@ struct ContourTestBase int errCount = 0; const axom::IndexType cellCount = contourMesh.getNumberOfCells(); - auto parentCellIdxView = get_parent_cell_idx_view(contourMesh); + auto parentCellIdView = get_parent_cell_id_view(contourMesh); auto domainIdView = getDomainIdView(contourMesh); const axom::IndexType domainCount = computationalMesh.domainCount(); @@ -1111,6 +1128,14 @@ struct ContourTestBase domainIdToContiguousId[domainId] = n; } + axom::Array> indexers(domainCount); + for(int d=0; d domShape; + computationalMesh.domainLengths(d, domShape); + indexers[d].initialize(domShape, 'c'); + } + for(axom::IndexType contourCellNum = 0; contourCellNum < cellCount; ++contourCellNum) { @@ -1119,8 +1144,10 @@ struct ContourTestBase typename axom::quest::MeshViewUtil::ConstCoordsViewsType& coordsViews = allCoordsViews[contiguousIndex]; + axom::IndexType parentCellId = parentCellIdView[contourCellNum]; + axom::StackArray parentCellIdx = - parentCellIdxView[contourCellNum]; + indexers[contiguousIndex].toMultiIndex(parentCellId); axom::StackArray upperIdx = parentCellIdx; addToStackArray(upperIdx, 1); @@ -1175,7 +1202,7 @@ struct ContourTestBase int errCount = 0; const axom::IndexType cellCount = contourMesh.getNumberOfCells(); - auto parentCellIdxView = get_parent_cell_idx_view(contourMesh); + auto parentCellIdView = get_parent_cell_id_view(contourMesh); auto domainIdView = getDomainIdView(contourMesh); const axom::IndexType domainCount = computationalMesh.domainCount(); @@ -1187,17 +1214,16 @@ struct ContourTestBase */ axom::Array> fcnViews( domainCount); - axom::Array> hasContours(domainCount); + axom::Array> hasContours(domainCount); for(axom::IndexType domId = 0; domId < domainCount; ++domId) { const auto& dom = computationalMesh.domain(domId); axom::quest::MeshViewUtil mvu(dom, "mesh"); - const axom::StackArray domLengths = - mvu.getRealExtents("element"); + const axom::IndexType cellCount = mvu.getCellCount(); - axom::Array& hasContour = hasContours[domId]; - hasContour.resize(domLengths, false); + axom::Array& hasContour = hasContours[domId]; + hasContour.resize(cellCount, false); fcnViews[domId] = mvu.template getConstFieldView(functionName(), false); @@ -1220,9 +1246,8 @@ struct ContourTestBase { axom::IndexType domainId = domainIdView[contourCellNum]; axom::IndexType contiguousId = domainIdToContiguousId[domainId]; - const axom::StackArray& parentCellIdx = - parentCellIdxView[contourCellNum]; - hasContours[contiguousId][parentCellIdx] = true; + const axom::IndexType parentCellId = parentCellIdView[contourCellNum]; + hasContours[contiguousId][parentCellId] = true; } // Verify that cells marked by hasContours touches the contour @@ -1238,12 +1263,12 @@ struct ContourTestBase const auto& fcnView = fcnViews[domId]; - axom::ArrayIndexer rowMajor(domLengths, 'r'); + axom::ArrayIndexer colMajor(domLengths, 'c'); const axom::IndexType cellCount = product(domLengths); for(axom::IndexType cellId = 0; cellId < cellCount; ++cellId) { axom::StackArray cellIdx = - rowMajor.toMultiIndex(cellId); + colMajor.toMultiIndex(cellId); // Compute min and max function value in the cell. double minFcnValue = std::numeric_limits::max(); @@ -1272,7 +1297,7 @@ struct ContourTestBase { const bool touchesContour = (minFcnValue <= params.contourVal && maxFcnValue >= params.contourVal); - const bool hasCont = hasContours[domId][cellIdx]; + const bool hasCont = hasContours[domId][cellId]; if(touchesContour != hasCont) { ++errCount; diff --git a/src/axom/quest/tests/quest_array_indexer.cpp b/src/axom/quest/tests/quest_array_indexer.cpp index 01bc68fc1d..4a56846436 100644 --- a/src/axom/quest/tests/quest_array_indexer.cpp +++ b/src/axom/quest/tests/quest_array_indexer.cpp @@ -20,6 +20,7 @@ TEST(quest_array_indexer, quest_strides_and_permutatations) axom::StackArray lengths {2}; axom::ArrayIndexer colIndexer(lengths, 'c'); + EXPECT_EQ(colIndexer.getOrder(), 'c' | 'r'); axom::StackArray colSlowestDirs {0}; axom::StackArray colStrides {1}; for(int d = 0; d < DIM; ++d) @@ -29,6 +30,7 @@ TEST(quest_array_indexer, quest_strides_and_permutatations) } axom::ArrayIndexer rowIndexer(lengths, 'r'); + EXPECT_EQ(rowIndexer.getOrder(), 'c' | 'r'); axom::StackArray rowSlowestDirs {0}; axom::StackArray rowStrides {1}; for(int d = 0; d < DIM; ++d) @@ -43,6 +45,7 @@ TEST(quest_array_indexer, quest_strides_and_permutatations) axom::StackArray lengths {3, 2}; axom::ArrayIndexer colIndexer(lengths, 'c'); + EXPECT_EQ(colIndexer.getOrder(), 'c'); axom::StackArray colSlowestDirs {0, 1}; axom::StackArray colStrides {2, 1}; for(int d = 0; d < DIM; ++d) @@ -52,6 +55,7 @@ TEST(quest_array_indexer, quest_strides_and_permutatations) } axom::ArrayIndexer rowIndexer(lengths, 'r'); + EXPECT_EQ(rowIndexer.getOrder(), 'r'); axom::StackArray rowSlowestDirs {1, 0}; axom::StackArray rowStrides {1, 3}; for(int d = 0; d < DIM; ++d) @@ -66,6 +70,7 @@ TEST(quest_array_indexer, quest_strides_and_permutatations) axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer colIndexer(lengths, 'c'); + EXPECT_EQ(colIndexer.getOrder(), 'c'); axom::StackArray colSlowestDirs {0, 1, 2}; axom::StackArray colStrides {6, 2, 1}; for(int d = 0; d < DIM; ++d) @@ -75,6 +80,7 @@ TEST(quest_array_indexer, quest_strides_and_permutatations) } axom::ArrayIndexer rowIndexer(lengths, 'r'); + EXPECT_EQ(rowIndexer.getOrder(), 'r'); axom::StackArray rowSlowestDirs {2, 1, 0}; axom::StackArray rowStrides {1, 5, 15}; for(int d = 0; d < DIM; ++d) @@ -93,6 +99,7 @@ TEST(quest_array_indexer, quest_col_major_offset) constexpr int DIM = 1; axom::StackArray lengths {3}; axom::ArrayIndexer ai(lengths, 'c'); + EXPECT_EQ(ai.getOrder(), 'c' | 'r'); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -107,6 +114,7 @@ TEST(quest_array_indexer, quest_col_major_offset) constexpr int DIM = 2; axom::StackArray lengths {3, 2}; axom::ArrayIndexer ai(lengths, 'c'); + EXPECT_EQ(ai.getOrder(), 'c'); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -123,6 +131,7 @@ TEST(quest_array_indexer, quest_col_major_offset) // 3D axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer ai(lengths, 'c'); + EXPECT_EQ(ai.getOrder(), 'c'); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -148,6 +157,7 @@ TEST(quest_array_indexer, quest_row_major_offset) constexpr int DIM = 1; axom::StackArray lengths {3}; axom::ArrayIndexer ai(lengths, 'r'); + EXPECT_EQ(ai.getOrder(), 'r' | 'c'); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -162,6 +172,7 @@ TEST(quest_array_indexer, quest_row_major_offset) constexpr int DIM = 2; axom::StackArray lengths {3, 2}; axom::ArrayIndexer ai(lengths, 'r'); + EXPECT_EQ(ai.getOrder(), 'r'); axom::IndexType offset = 0; for(int j = 0; j < lengths[1]; ++j) { @@ -179,6 +190,7 @@ TEST(quest_array_indexer, quest_row_major_offset) constexpr int DIM = 3; axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer ai(lengths, 'r'); + EXPECT_EQ(ai.getOrder(), 'r'); axom::IndexType offset = 0; for(int k = 0; k < lengths[2]; ++k) { @@ -385,7 +397,7 @@ TEST(quest_array_indexer, quest_array_match) { for(axom::IndexType j = 0; j < lengths[1]; ++j) { - for(axom::IndexType k = 0; j < lengths[2]; ++j) + for(axom::IndexType k = 0; k < lengths[2]; ++k) { axom::StackArray indices {i, j, k}; EXPECT_EQ(&a(i, j, k), &a.flatIndex(ai.toFlatIndex(indices))); From d27a52daf25994a9e9a772721df2297d61d91161 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 25 Jan 2024 14:24:40 -0800 Subject: [PATCH 04/61] Remove code that has been superceded. --- src/axom/quest/MarchingCubes.cpp | 70 ---- src/axom/quest/MarchingCubes.hpp | 40 --- .../detail/MarchingCubesFullParallel.hpp | 337 +----------------- 3 files changed, 7 insertions(+), 440 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index adc929b120..55294bc630 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -60,7 +60,6 @@ void MarchingCubes::setFunctionField(const std::string& fcnField) void MarchingCubes::computeIsocontour(double contourVal) { -#if 1 // Mark and scan domains while adding up their // facet counts to get the total facet counts. m_facetIndexOffsets.resize(m_singles.size()); @@ -93,17 +92,6 @@ void MarchingCubes::computeIsocontour(double contourVal) { m_singles[d]->computeContour(); } -#else - SLIC_ASSERT_MSG(!m_fcnFieldName.empty(), - "You must call setFunctionField before computeIsocontour."); - - for(int dId = 0; dId < m_singles.size(); ++dId) - { - std::unique_ptr& single = m_singles[dId]; - single->setDataParallelism(m_dataParallelism); - single->computeIsocontour(contourVal); - } -#endif } axom::IndexType MarchingCubes::getContourCellCount() const @@ -150,7 +138,6 @@ void MarchingCubes::populateContourMesh( mesh.reserveCells(contourCellCount); mesh.reserveNodes(contourNodeCount); -#if 1 if (m_facetCount) { mesh.appendCells(m_facetNodeIds.data(), m_facetCount); mesh.appendNodes(m_facetNodeCoords.data(), mesh.getDimension()*m_facetCount); @@ -176,33 +163,6 @@ void MarchingCubes::populateContourMesh( m_singles[d]->getDomainId(d)); } } -#else - // Populate mesh from single domains and add domain id if requested. - for(int dId = 0; dId < m_singles.size(); ++dId) - { - std::unique_ptr& single = m_singles[dId]; - - auto nPrev = mesh.getNumberOfCells(); - single->populateContourMesh(mesh, cellIdField); - auto nNew = mesh.getNumberOfCells(); - - if(nNew > nPrev && !domainIdField.empty()) - { - auto* domainIdPtr = - mesh.getFieldPtr(domainIdField, - axom::mint::CELL_CENTERED); - - int userDomainId = single->getDomainId(dId); - - axom::detail::ArrayOps::fill( - domainIdPtr, - nPrev, - nNew - nPrev, - execution_space::allocatorID(), - userDomainId); - } - } -#endif SLIC_ASSERT(mesh.getNumberOfNodes() == contourNodeCount); SLIC_ASSERT(mesh.getNumberOfCells() == contourCellCount); } @@ -337,35 +297,5 @@ int MarchingCubesSingleDomain::getDomainId(int defaultId) const return rval; } -#if 0 -void MarchingCubesSingleDomain::computeIsocontour(double contourVal) -{ - SLIC_ASSERT_MSG(!m_fcnFieldName.empty(), - "You must call setFunctionField before computeIsocontour."); - - // We have 2 implementations. MarchingCubesHybridParallel is faster on the host - // and MarchingCubesFullParallel is faster on GPUs. Both work in all cases. - // We can choose based on runtime policy or by user choice - if(m_dataParallelism == - axom::quest::MarchingCubesDataParallelism::hybridParallel || - (m_dataParallelism == axom::quest::MarchingCubesDataParallelism::byPolicy && - m_runtimePolicy == RuntimePolicy::seq)) - { - m_impl = axom::quest::detail::marching_cubes::newMarchingCubesHybridParallel( - m_runtimePolicy, - m_ndim); - } - else - { - m_impl = axom::quest::detail::marching_cubes::newMarchingCubesFullParallel( - m_runtimePolicy, - m_ndim); - } - m_impl->initialize(*m_dom, m_topologyName, m_fcnFieldName, m_maskFieldName); - m_impl->setContourValue(contourVal); - m_impl->computeContourMesh(); -} -#endif - } // end namespace quest } // end namespace axom diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 5e9efd2889..21ac900dec 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -346,18 +346,6 @@ class MarchingCubesSingleDomain */ int getDomainId(int defaultId) const; -#if 0 - /*! - * \brief Compute the isocontour. - * - * \param [in] contourVal isocontour value - * - * Compute isocontour using the marching cubes algorithm. - */ - void computeIsocontour(double contourVal = 0.0); -#endif - - //!@brief Get number of cells in the generated contour mesh. axom::IndexType getContourCellCount() const { @@ -374,23 +362,6 @@ class MarchingCubesSingleDomain return m_ndim * getContourCellCount(); } -#if 0 - /*! - @brief Put generated contour surface in a mint::UnstructuredMesh - object. - - @param mesh Output mesh - @param cellIdField Name of field to store the prent cell ids. - If omitted, the data is not copied. - */ - void populateContourMesh( - axom::mint::UnstructuredMesh &mesh, - const std::string &cellIdField = {}) const - { - m_impl->populateContourMesh(mesh, cellIdField); - } -#endif - /*! @brief Base class for implementations templated on dimension DIM and execution space ExecSpace. @@ -431,17 +402,6 @@ class MarchingCubesSingleDomain //!@name Output methods //!@brief Return number of contour mesh facets generated. virtual axom::IndexType getContourCellCount() const = 0; -#if 0 - /*! - @brief Populate output mesh object with generated contour. - - Note: Output format is in flux. We will likely output - a blueprint object in the future. - */ - virtual void populateContourMesh( - axom::mint::UnstructuredMesh &mesh, - const std::string &cellIdField) const = 0; -#endif //@} void setOutputBuffers( diff --git a/src/axom/quest/detail/MarchingCubesFullParallel.hpp b/src/axom/quest/detail/MarchingCubesFullParallel.hpp index efdf52f77e..96c57f50d8 100644 --- a/src/axom/quest/detail/MarchingCubesFullParallel.hpp +++ b/src/axom/quest/detail/MarchingCubesFullParallel.hpp @@ -13,7 +13,6 @@ #include "axom/core/execution/execution_space.hpp" #include "axom/quest/ArrayIndexer.hpp" -#include "axom/quest/detail/marching_cubes_lookup.hpp" #include "axom/quest/MeshViewUtil.hpp" #include "axom/primal/geometry/Point.hpp" #include "axom/primal/constants.hpp" @@ -104,15 +103,6 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_contourVal = contourVal; } -#if 0 - void computeContourMesh() override - { - markCrossings(); - scanCrossings(); - computeContour(); - } -#endif - /*! @brief Implementation of virtual markCrossings. @@ -256,71 +246,6 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase } } }; // MarkCrossings_Util - - /*! - @brief Populate the 1D m_contourNodeCoords, m_contourCellCorners - and m_contourCellParents arrays that defines the unstructured - contour mesh. - */ - void old_scanCrossings() - { - const axom::IndexType parentCellCount = m_caseIds.size(); - auto caseIdsView = m_caseIds.view(); - - // Compute number of surface facets added by each parent cell. - m_facetIncrs.resize(parentCellCount); - const auto facetIncrsView = m_facetIncrs.view(); - - axom::for_all( - 0, - parentCellCount, - AXOM_LAMBDA(axom::IndexType parentCellId) { - facetIncrsView.flatIndex(parentCellId) = - num_contour_cells(caseIdsView.flatIndex(parentCellId)); - }); - - // Compute index of first facet added by each parent cell - // (whether the cell generates any facet!). - m_firstFacetIds.resize(parentCellCount); - const axom::ArrayView firstFacetIdsView = - m_firstFacetIds.view(); -#if defined(AXOM_USE_RAJA) - #ifdef __INTEL_LLVM_COMPILER - // Intel oneAPI compiler segfaults with OpenMP RAJA scan - using ScanPolicy = - typename axom::execution_space::loop_policy; - #else - using ScanPolicy = typename axom::execution_space::loop_policy; - #endif - - RAJA::exclusive_scan( - RAJA::make_span(facetIncrsView.data(), parentCellCount), - RAJA::make_span(firstFacetIdsView.data(), parentCellCount), - RAJA::operators::plus {}); - - // m_facetIncrs and m_firstFacetIds, combined with m_caseIds, - // are all we need to compute the surface mesh. -#else - firstFacetIdsView[0] = 0; - for(axom::IndexType pcId = 1; pcId < parentCellCount; ++pcId) - { - firstFacetIdsView[pcId] = - firstFacetIdsView[pcId - 1] + facetIncrsView[pcId - 1]; - } -#endif - - // Use last facet info to compute number of facets in domain. - // In case data is on device, copy to host before computing. - axom::IndexType firstFacetIds_back = 0; - FacetIdType facetIncrs_back = 0; - axom::copy(&firstFacetIds_back, - m_firstFacetIds.data() + m_firstFacetIds.size() - 1, - sizeof(firstFacetIds_back)); - axom::copy(&facetIncrs_back, - m_facetIncrs.data() + m_facetIncrs.size() - 1, - sizeof(facetIncrs_back)); - m_facetCount = firstFacetIds_back + facetIncrs_back; - } void scanCrossings() override { #if defined(AXOM_USE_RAJA) @@ -402,70 +327,8 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_firstFacetIds.resize(m_crossingCount); } -#if 0 - void old_computeContour() - { - // - // Fill in surface mesh data. - // - - // Allocate space for surface mesh. - const axom::IndexType cornersCount = DIM * m_facetCount; - m_contourCellParents.resize(m_facetCount); - m_contourCellCorners.resize(m_facetCount); - m_contourNodeCoords.resize(cornersCount); - - const axom::IndexType parentCellCount = m_caseIds.size(); - const auto facetIncrsView = m_facetIncrs.view(); - const auto firstFacetIdsView = m_firstFacetIds.view(); - const auto caseIdsView = m_caseIds.view(); - - auto contourCellParentsView = m_contourCellParents.view(); - auto contourCellCornersView = m_contourCellCorners.view(); - auto contourNodeCoordsView = m_contourNodeCoords.view(); - - ComputeContour_Util ccu(m_contourVal, - m_caseIds.strides(), - m_fcnView, - m_coordsViews); - auto gen_for_parent_cell = AXOM_LAMBDA(axom::IndexType parentCellId) - { - Point cornerCoords[CELL_CORNER_COUNT]; - double cornerValues[CELL_CORNER_COUNT]; - ccu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); - - auto additionalFacets = facetIncrsView[parentCellId]; - auto firstFacetId = firstFacetIdsView[parentCellId]; - - auto caseId = caseIdsView.flatIndex(parentCellId); - - for(axom::IndexType fId = 0; fId < additionalFacets; ++fId) - { - axom::IndexType newFacetId = firstFacetId + fId; - axom::IndexType firstCornerId = newFacetId * DIM; - - contourCellParentsView[newFacetId] = parentCellId; - - for(axom::IndexType d = 0; d < DIM; ++d) - { - axom::IndexType newCornerId = firstCornerId + d; - contourCellCornersView[newFacetId][d] = newCornerId; - - int edge = cases_table(caseId, fId * DIM + d); - ccu.linear_interp(edge, - cornerCoords, - cornerValues, - contourNodeCoordsView[newCornerId]); - } - } - }; - - axom::for_all(0, parentCellCount, gen_for_parent_cell); - } -#endif void computeContour() override { -#if 1 const auto facetIncrsView = m_facetIncrs.view(); const auto firstFacetIdsView = m_firstFacetIds.view(); const auto crossingParentIdsView = m_crossingParentIds.view(); @@ -515,60 +378,6 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase }; axom::for_all(0, m_crossingCount, gen_for_parent_cell); -#else - // Allocate space for surface mesh. - const axom::IndexType cornersCount = DIM * m_facetCount; - m_contourCellParents.resize(m_facetCount); - m_contourCellCorners.resize(m_facetCount); - m_contourNodeCoords.resize(cornersCount); - - const auto facetIncrsView = m_facetIncrs.view(); - const auto firstFacetIdsView = m_firstFacetIds.view(); - const auto crossingParentIdsView = m_crossingParentIds.view(); - const auto caseIdsView = m_caseIds.view(); - - auto contourCellParentsView = m_contourCellParents.view(); - auto contourCellCornersView = m_contourCellCorners.view(); - auto contourNodeCoordsView = m_contourNodeCoords.view(); - - ComputeContour_Util ccu(m_contourVal, - m_caseIds.strides(), - m_fcnView, - m_coordsViews); - auto gen_for_parent_cell = AXOM_LAMBDA(axom::IndexType crossingId) - { - auto parentCellId = crossingParentIdsView[crossingId]; - auto caseId = caseIdsView.flatIndex(parentCellId); - Point cornerCoords[CELL_CORNER_COUNT]; - double cornerValues[CELL_CORNER_COUNT]; - ccu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); - - auto additionalFacets = facetIncrsView[crossingId]; - auto firstFacetId = firstFacetIdsView[crossingId]; - - for(axom::IndexType fId = 0; fId < additionalFacets; ++fId) - { - axom::IndexType newFacetId = firstFacetId + fId; - axom::IndexType firstCornerId = newFacetId * DIM; - - contourCellParentsView[newFacetId] = parentCellId; - - for(axom::IndexType d = 0; d < DIM; ++d) - { - axom::IndexType newCornerId = firstCornerId + d; - contourCellCornersView[newFacetId][d] = newCornerId; - - int edge = cases_table(caseId, fId * DIM + d); - ccu.linear_interp(edge, - cornerCoords, - cornerValues, - contourNodeCoordsView[newCornerId]); - } - } - }; - - axom::for_all(0, m_crossingCount, gen_for_parent_cell); -#endif } /*! @@ -806,107 +615,6 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase return cases3D[iCase][iEdge]; } -#if 0 - /*! - @brief Output contour mesh to a mint::UnstructuredMesh object. - */ - void populateContourMesh( - axom::mint::UnstructuredMesh& mesh, - const std::string& cellIdField) const override - { - auto internalAllocatorID = axom::execution_space::allocatorID(); - auto hostAllocatorID = axom::execution_space::allocatorID(); - - /* - mint uses host memory. If internal memory is on the host, use - it. Otherwise, make a temporary copy of it on the host. - */ - if(internalAllocatorID == hostAllocatorID) - { - populateContourMesh(mesh, - cellIdField, - m_contourNodeCoords, - m_contourCellCorners, - m_contourCellParents); - } - else - { - axom::Array contourNodeCoords( - m_contourNodeCoords, - hostAllocatorID); - axom::Array contourCellCorners( - m_contourCellCorners, - hostAllocatorID); - axom::Array contourCellParents( - m_contourCellParents, - hostAllocatorID); - - populateContourMesh(mesh, - cellIdField, - contourNodeCoords, - contourCellCorners, - contourCellParents); - } - } - - //!@brief Output contour mesh to a mint::UnstructuredMesh object. - void populateContourMesh( - axom::mint::UnstructuredMesh& mesh, - const std::string& cellIdField, - const axom::Array contourNodeCoords, - const axom::Array contourCellCorners, - const axom::Array contourCellParents) const - { - if(!cellIdField.empty() && - !mesh.hasField(cellIdField, axom::mint::CELL_CENTERED)) - { - mesh.createField(cellIdField, - axom::mint::CELL_CENTERED, - DIM); - } - - const axom::IndexType addedCellCount = contourCellCorners.size(); - const axom::IndexType addedNodeCount = contourNodeCoords.size(); - if(addedCellCount != 0) - { - const axom::IndexType priorCellCount = mesh.getNumberOfCells(); - const axom::IndexType priorNodeCount = mesh.getNumberOfNodes(); - mesh.reserveNodes(priorNodeCount + addedNodeCount); - mesh.reserveCells(priorCellCount + addedCellCount); - - mesh.appendNodes((double*)contourNodeCoords.data(), - contourNodeCoords.size()); - - for(int n = 0; n < addedCellCount; ++n) - { - MIdx cornerIds = contourCellCorners[n]; - // Bump corner indices by priorNodeCount to avoid indices - // used by other parents domains. - for(int d = 0; d < DIM; ++d) - { - cornerIds[d] += priorNodeCount; - } - mesh.appendCell(cornerIds); // This takes too long! - } - - axom::IndexType numComponents = -1; - axom::IndexType* cellIdPtr = - mesh.getFieldPtr(cellIdField, - axom::mint::CELL_CENTERED, - numComponents); - SLIC_ASSERT(numComponents == DIM); - axom::ArrayView> cellIdView( - (axom::StackArray*)cellIdPtr, - priorCellCount + addedCellCount); - axom::ArrayIndexer si(m_caseIds.shape(), 'c'); - for(axom::IndexType i = 0; i < addedCellCount; ++i) - { - cellIdView[priorCellCount + i] = si.toMultiIndex(contourCellParents[i]); - } - } - } -#endif - //!@brief Compute the case index into cases2D or cases3D. AXOM_HOST_DEVICE inline int compute_crossing_case(const double* f) const { @@ -922,27 +630,10 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase return index; } -#if 0 - //!@brief Clear data so you can rerun with a different contour value. - void clear() - { - m_contourNodeCoords.clear(); - m_contourCellCorners.clear(); - m_contourCellParents.clear(); - m_crossingCount = 0; - m_facetCount = 0; - } -#endif - /*! @brief Constructor. */ MarchingCubesFullParallel() -#if 0 - : m_contourNodeCoords(0, 0) - , m_contourCellCorners(0, 0) - , m_contourCellParents(0, 0) -#endif { } private: @@ -963,36 +654,22 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase //!@brief Number of parent cells crossing the contour surface. axom::IndexType m_crossingCount = 0; - //!@brief Number of surface mesh facets added by crossing mesh cells. + //!@brief Number of contour surface cells from all crossings. + axom::IndexType m_facetCount = 0; + axom::IndexType getContourCellCount() const override { return m_facetCount; } + + //!@brief Number of surface mesh facets added by each crossing. axom::Array m_facetIncrs; - //!@brief Parent cell id for each crossing. + //!@brief Parent cell id (flat index into m_caseIds) for each crossing. axom::Array m_crossingParentIds; - //!@brief First index of facets in computational mesh cells. + //!@brief First index of facets for each crossing. axom::Array m_firstFacetIds; - //!@brief Number of contour surface cells from crossings. - axom::IndexType m_facetCount = 0; - axom::IndexType getContourCellCount() const override { return m_facetCount; } - //!@brief Number of corners (nodes) on each parent cell. static constexpr std::uint8_t CELL_CORNER_COUNT = (DIM == 3) ? 8 : 4; -#if 0 - //!@name Internal representation of generated contour mesh. - //@{ - //!@brief Coordinates of generated surface mesh nodes. - axom::Array m_contourNodeCoords; - - //!@brief Corners (index into m_contourNodeCoords) of generated contour cells. - axom::Array m_contourCellCorners; - - //!@brief Flat index of computational cell crossing the contour cell. - axom::Array m_contourCellParents; - //@} -#endif - double m_contourVal = 0.0; }; From ba3d3f844f4c0382873b00935f06fe2b218c6e0a Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 25 Jan 2024 17:23:43 -0800 Subject: [PATCH 05/61] Add in hybrid parallel implementation. The hybrid implementation just uses an alternate scanCrossing method. The MarchingCubesFullParallel is not both full and hybrid and will be renamed. --- src/axom/quest/MarchingCubes.cpp | 27 +--- src/axom/quest/MarchingCubes.hpp | 25 +-- .../detail/MarchingCubesFullParallel.hpp | 150 ++++++++++++++++-- .../examples/quest_marching_cubes_example.cpp | 2 +- 4 files changed, 155 insertions(+), 49 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 55294bc630..4de0cf165d 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -22,10 +22,12 @@ namespace axom namespace quest { MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, + MarchingCubesDataParallelism dataParallelism, const conduit::Node& bpMesh, const std::string& topologyName, const std::string& maskField) : m_runtimePolicy(runtimePolicy) + , m_dataParallelism(dataParallelism) , m_singles() , m_topologyName(topologyName) , m_fcnFieldName() @@ -231,29 +233,12 @@ MarchingCubesSingleDomain::MarchingCubesSingleDomain(RuntimePolicy runtimePolicy // Set domain first, to get m_ndim, which is required to allocate m_impl. setDomain(dom); - /* - We have 2 implementations. MarchingCubesHybridParallel is faster on the host - and MarchingCubesFullParallel is faster on GPUs. Both work in all cases. - We can choose based on runtime policy or by user choice - */ - if(m_dataParallelism == - axom::quest::MarchingCubesDataParallelism::hybridParallel || - (m_dataParallelism == axom::quest::MarchingCubesDataParallelism::byPolicy && - m_runtimePolicy == RuntimePolicy::seq)) - { - SLIC_WARNING("Not really using hybrid while developing. Using full parallel."); - m_impl = axom::quest::detail::marching_cubes::newMarchingCubesFullParallel( - m_runtimePolicy, - m_ndim); - } - else - { - m_impl = axom::quest::detail::marching_cubes::newMarchingCubesFullParallel( - m_runtimePolicy, - m_ndim); - } + m_impl = axom::quest::detail::marching_cubes::newMarchingCubesFullParallel( + m_runtimePolicy, + m_ndim); m_impl->initialize(*m_dom, m_topologyName, m_maskFieldName); + m_impl->setDataParallelism(m_dataParallelism); return; } diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 21ac900dec..b4ccb03975 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -107,6 +107,7 @@ class MarchingCubes * \param [in] runtimePolicy A value from RuntimePolicy. * The simplest policy is RuntimePolicy::seq, which specifies * running sequentially on the CPU. + * \param [in] dataParallelism Data parallel implementation choice. * \param [in] bpMesh Blueprint multi-domain mesh containing scalar field. * \param [in] topologyName Name of Blueprint topology to use in \a bpMesh. * \param [in] maskField Cell-based std::int32_t mask field. If provided, @@ -125,6 +126,7 @@ class MarchingCubes * transformation and storage of the temporary contiguous layout. */ MarchingCubes(RuntimePolicy runtimePolicy, + MarchingCubesDataParallelism dataParallelism, const conduit::Node &bpMesh, const std::string &topologyName, const std::string &maskField = {}); @@ -195,16 +197,6 @@ class MarchingCubes const std::string &cellIdField = {}, const std::string &domainIdField = {}); - /*! - @brief Set choice of data-parallel implementation. - - By default, choice is MarchingCubesDataParallelism::byPolicy. - */ - void setDataParallelism(MarchingCubesDataParallelism dataPar) - { - m_dataParallelism = dataPar; - } - private: RuntimePolicy m_runtimePolicy; @@ -247,8 +239,6 @@ class MarchingCubes axom::Array m_facetParentIds; //@} - void setMesh(const conduit::Node &bpMesh); - //!@brief Allocate output buffers corresponding to runtime policy. void allocateOutputBuffers(); }; @@ -296,11 +286,6 @@ class MarchingCubesSingleDomain int spatialDimension() const { return m_ndim; } - void setDataParallelism(MarchingCubesDataParallelism &dataPar) - { - m_dataParallelism = dataPar; - } - /*! @brief Specify the field containing the nodal scalar function in the input mesh. @@ -387,6 +372,9 @@ class MarchingCubesSingleDomain virtual void setFunctionField(const std::string& fcnFieldName) = 0; virtual void setContourValue(double contourVal) = 0; + void setDataParallelism(MarchingCubesDataParallelism dataPar) + { m_dataParallelism = dataPar; } + //@{ //!@name Distinct phases in contour generation. //!@brief Compute the contour mesh. @@ -418,6 +406,9 @@ class MarchingCubesSingleDomain virtual ~ImplBase() { } + MarchingCubesDataParallelism m_dataParallelism = + MarchingCubesDataParallelism::byPolicy; + double m_contourVal = 0.0; axom::ArrayView m_facetNodeIds; axom::ArrayView m_facetNodeCoords; diff --git a/src/axom/quest/detail/MarchingCubesFullParallel.hpp b/src/axom/quest/detail/MarchingCubesFullParallel.hpp index 96c57f50d8..c6a4f2b800 100644 --- a/src/axom/quest/detail/MarchingCubesFullParallel.hpp +++ b/src/axom/quest/detail/MarchingCubesFullParallel.hpp @@ -39,7 +39,7 @@ namespace marching_cubes See MarchingCubesImpl for the difference between that class and MarchingCubesFullParallel. */ -template +template class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase { public: @@ -48,6 +48,8 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase using FacetIdType = int; using LoopPolicy = typename execution_space::loop_policy; using ReducePolicy = typename execution_space::reduce_policy; + using SequentialLoopPolicy = + typename execution_space::loop_policy; static constexpr auto MemorySpace = execution_space::memory_space; /*! @brief Initialize data to a blueprint domain. @@ -246,7 +248,28 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase } } }; // MarkCrossings_Util + void scanCrossings() override + { + constexpr MarchingCubesDataParallelism autoPolicy = + std::is_same::value ? MarchingCubesDataParallelism::hybridParallel : + std::is_same::value ? MarchingCubesDataParallelism::hybridParallel : + MarchingCubesDataParallelism::fullParallel; + + if(m_dataParallelism == + axom::quest::MarchingCubesDataParallelism::hybridParallel || + (m_dataParallelism == axom::quest::MarchingCubesDataParallelism::byPolicy && + autoPolicy == MarchingCubesDataParallelism::hybridParallel)) + { + scanCrossings_hybridParallel(); + } + else + { + scanCrossings_fullParallel(); + } + } + + void scanCrossings_fullParallel() { #if defined(AXOM_USE_RAJA) #ifdef __INTEL_LLVM_COMPILER @@ -296,6 +319,8 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_crossingParentIds.resize(m_crossingCount); m_facetIncrs.resize(m_crossingCount); + m_firstFacetIds.resize(1 + m_crossingCount); + auto crossingParentIdsView = m_crossingParentIds.view(); auto facetIncrsView = m_facetIncrs.view(); @@ -315,7 +340,6 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase // and the total number of facets. // - m_firstFacetIds.resize(1 + m_crossingCount); RAJA::inclusive_scan( RAJA::make_span(m_facetIncrs.data(), m_crossingCount), RAJA::make_span(m_firstFacetIds.data() + 1, m_crossingCount), @@ -327,6 +351,112 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase m_firstFacetIds.resize(m_crossingCount); } + void scanCrossings_hybridParallel() + { + // + // Compute number of crossings in m_caseIds + // + const axom::IndexType parentCellCount = m_caseIds.size(); + auto caseIdsView = m_caseIds.view(); +#if defined(AXOM_USE_RAJA) + RAJA::ReduceSum vsum(0); + RAJA::forall( + RAJA::RangeSegment(0, parentCellCount), + AXOM_LAMBDA(RAJA::Index_type n) { + vsum += bool(num_contour_cells(caseIdsView.flatIndex(n))); + }); + m_crossingCount = static_cast(vsum.get()); +#else + axom::IndexType vsum = 0; + for(axom::IndexType n = 0; n < parentCellCount; ++n) + { + vsum += bool(num_contour_cells(caseIdsView.flatIndex(n))); + } + m_crossingCount = vsum; +#endif + + // + // Allocate space for crossing info + // + m_crossingParentIds.resize(m_crossingCount); + m_facetIncrs.resize(m_crossingCount); + m_firstFacetIds.resize(1 + m_crossingCount); + + auto crossingParentIdsView = m_crossingParentIds.view(); + auto facetIncrsView = m_facetIncrs.view(); + + axom::IndexType* crossingId = axom::allocate( + 1, + axom::detail::getAllocatorID()); + + auto loopBody = AXOM_LAMBDA(axom::IndexType n) + { + auto caseId = caseIdsView.flatIndex(n); + auto ccc = num_contour_cells(caseId); + if(ccc != 0) + { + facetIncrsView[*crossingId] = ccc; + crossingParentIdsView[*crossingId] = n; + ++(*crossingId); + } + }; + +#if defined(AXOM_USE_RAJA) + /* + loopBody isn't data-parallel and shouldn't be parallelized. + This contrived RAJA::forall forces it to run sequentially. + */ + RAJA::forall( + RAJA::RangeSegment(0, 1), + [=] AXOM_HOST_DEVICE(int /* i */) { + *crossingId = 0; + for(axom::IndexType n = 0; n < parentCellCount; ++n) + { + loopBody(n); + } + }); +#else + *crossingId = 0; + for(axom::IndexType n = 0; n < parentCellCount; ++n) + { + loopBody(n); + } + SLIC_ASSERT(*crossingId == m_crossingCount); +#endif + + axom::deallocate(crossingId); + + // axom::Array prefixSum(m_crossingCount, m_crossingCount); + const auto firstFacetIdsView = m_firstFacetIds.view(); + +#if defined(AXOM_USE_RAJA) + // Intel oneAPI compiler segfaults with OpenMP RAJA scan + #ifdef __INTEL_LLVM_COMPILER + using ScanPolicy = + typename axom::execution_space::loop_policy; + #else + using ScanPolicy = typename axom::execution_space::loop_policy; + #endif + RAJA::inclusive_scan( + RAJA::make_span(facetIncrsView.data(), m_crossingCount), + RAJA::make_span(firstFacetIdsView.data() + 1, m_crossingCount), + RAJA::operators::plus {}); +#else + if(m_crossingCount > 0) + { + firstFacetIdsView[0] = 0; + for(axom::IndexType i = 1; i < 1 + m_crossingCount; ++i) + { + firstFacetIdsView[i] = firstFacetIdsView[i - 1] + facetIncrsView[i - 1]; + } + } +#endif + axom::copy(&m_facetCount, + m_firstFacetIds.data() + m_firstFacetIds.size() - 1, + sizeof(axom::IndexType)); + m_firstFacetIds.resize(m_crossingCount); + } + void computeContour() override { const auto facetIncrsView = m_facetIncrs.view(); @@ -687,35 +817,35 @@ newMarchingCubesFullParallel(MarchingCubes::RuntimePolicy runtimePolicy, int dim if(runtimePolicy == MarchingCubes::RuntimePolicy::seq) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesFullParallel<2, axom::SEQ_EXEC>) + new MarchingCubesFullParallel<2, axom::SEQ_EXEC, axom::SEQ_EXEC>) : std::unique_ptr( - new MarchingCubesFullParallel<3, axom::SEQ_EXEC>); + new MarchingCubesFullParallel<3, axom::SEQ_EXEC, axom::SEQ_EXEC>); } #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP else if(runtimePolicy == MarchingCubes::RuntimePolicy::omp) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesFullParallel<2, axom::OMP_EXEC>) + new MarchingCubesFullParallel<2, axom::OMP_EXEC, axom::SEQ_EXEC>) : std::unique_ptr( - new MarchingCubesFullParallel<3, axom::OMP_EXEC>); + new MarchingCubesFullParallel<3, axom::OMP_EXEC, axom::SEQ_EXEC>); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_CUDA else if(runtimePolicy == MarchingCubes::RuntimePolicy::cuda) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesFullParallel<2, axom::CUDA_EXEC<256>>) + new MarchingCubesFullParallel<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>) : std::unique_ptr( - new MarchingCubesFullParallel<3, axom::CUDA_EXEC<256>>); + new MarchingCubesFullParallel<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_HIP else if(runtimePolicy == MarchingCubes::RuntimePolicy::hip) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesFullParallel<2, axom::HIP_EXEC<256>>) + new MarchingCubesFullParallel<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>) : std::unique_ptr( - new MarchingCubesFullParallel<3, axom::HIP_EXEC<256>>); + new MarchingCubesFullParallel<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>); } #endif else diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index e489adc8d8..0f22d2324f 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -790,7 +790,6 @@ struct ContourTestBase MPI_Barrier(MPI_COMM_WORLD); #endif computeTimer.start(); - mc.setDataParallelism(params.dataParallelism); mc.computeIsocontour(params.contourVal); computeTimer.stop(); printTimingStats(computeTimer, name() + " contour"); @@ -1572,6 +1571,7 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) { // Create marching cubes algorithm object and set some parameters quest::MarchingCubes mc(params.policy, + params.dataParallelism, computationalMesh.asConduitNode(), "mesh"); From a6c86b3775c7e549007c484e8d951e90d6660f20 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 25 Jan 2024 17:46:41 -0800 Subject: [PATCH 06/61] Rehame MarchingCubesFullParallel -> MarchingCubesImpl and remove MarchingCubesHybridParallel. --- src/axom/quest/CMakeLists.txt | 2 +- src/axom/quest/MarchingCubes.cpp | 4 +- .../detail/MarchingCubesHybridParallel.hpp | 999 ------------------ ...FullParallel.hpp => MarchingCubesImpl.hpp} | 32 +- 4 files changed, 19 insertions(+), 1018 deletions(-) delete mode 100644 src/axom/quest/detail/MarchingCubesHybridParallel.hpp rename src/axom/quest/detail/{MarchingCubesFullParallel.hpp => MarchingCubesImpl.hpp} (95%) diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index 9ddc1e2059..9f3597326f 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -119,7 +119,7 @@ endif() blt_list_append( TO quest_headers - ELEMENTS MarchingCubes.hpp detail/MarchingCubesFullParallel.hpp + ELEMENTS MarchingCubes.hpp detail/MarchingCubesImpl.hpp IF CONDUIT_FOUND ) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 4de0cf165d..1733531ac1 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -14,7 +14,7 @@ #include "axom/core/execution/execution_space.hpp" #include "axom/quest/MarchingCubes.hpp" // #include "axom/quest/detail/MarchingCubesHybridParallel.hpp" -#include "axom/quest/detail/MarchingCubesFullParallel.hpp" +#include "axom/quest/detail/MarchingCubesImpl.hpp" #include "axom/fmt.hpp" namespace axom @@ -233,7 +233,7 @@ MarchingCubesSingleDomain::MarchingCubesSingleDomain(RuntimePolicy runtimePolicy // Set domain first, to get m_ndim, which is required to allocate m_impl. setDomain(dom); - m_impl = axom::quest::detail::marching_cubes::newMarchingCubesFullParallel( + m_impl = axom::quest::detail::marching_cubes::newMarchingCubesImpl( m_runtimePolicy, m_ndim); diff --git a/src/axom/quest/detail/MarchingCubesHybridParallel.hpp b/src/axom/quest/detail/MarchingCubesHybridParallel.hpp deleted file mode 100644 index 3ab2a97d91..0000000000 --- a/src/axom/quest/detail/MarchingCubesHybridParallel.hpp +++ /dev/null @@ -1,999 +0,0 @@ -// Copyright (c) 2017-2023, Lawrence Livermore National Security, LLC and -// other Axom Project Developers. See the top-level LICENSE file for details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -#include "axom/config.hpp" - -// Implementation requires Conduit. -#ifndef AXOM_USE_CONDUIT - #error "MarchingCubesHybridParallel.hpp requires conduit" -#endif -#include "conduit_blueprint.hpp" - -#include "axom/core/execution/execution_space.hpp" -#include "axom/quest/ArrayIndexer.hpp" -#include "axom/quest/detail/marching_cubes_lookup.hpp" -#include "axom/quest/MeshViewUtil.hpp" -#include "axom/primal/geometry/Point.hpp" -#include "axom/primal/constants.hpp" -#include "axom/mint/execution/internal/structured_exec.hpp" -#include "axom/fmt.hpp" - -namespace axom -{ -namespace quest -{ -namespace detail -{ -namespace marching_cubes -{ -/*! - @brief Computations for MarchingCubesSingleDomain - - Spatial dimension templating is here, to keep out of higher level - classes MarchCubes and MarchingCubesSingleDomain. - - ExecSpace is the general execution space, like axom::SEQ_EXEC and - axom::CUDA_EXEC<256>. SequentialExecSpace is used for loops that - cannot be parallelized but must access data allocated for ExecSpace. - Use something like axom::SEQ_EXEC or axom::CUDA_EXEC<1>. - - The difference between MarchingCubesHybridParallel and MarchingCubesFullParallel - is how they compute indices for the unstructured surface mesh. - - MarchingCubesHybridParallel uses sequential loop, and skips over parents - cells that don't touch the surface contour. It processes less data - but is not data-parallel. - - MarchingCubesFullParallel that checks all parents cells. - It process more data but is data-parallel. - We've observed that MarchingCubesHybridParallel is faster for seq policy - and MarchingCubesHybridParallel is faster on the GPU. -*/ -template -class MarchingCubesHybridParallel : public MarchingCubesSingleDomain::ImplBase -{ -public: - using Point = axom::primal::Point; - using MIdx = axom::StackArray; - using LoopPolicy = typename execution_space::loop_policy; - using ReducePolicy = typename execution_space::reduce_policy; - using SequentialLoopPolicy = - typename execution_space::loop_policy; - static constexpr auto MemorySpace = execution_space::memory_space; - /*! - @brief Initialize data to a blueprint domain. - @param dom Blueprint structured mesh domain - @param topologyName Name of mesh topology (see blueprint - mesh documentation) - @param fcnFieldName Name of nodal function in dom - @param maskFieldName Name of integer cell mask function is in dom - - Set up views to domain data and allocate other data to work on the - given domain. - - The above data from the domain MUST be in a memory space - compatible with ExecSpace. - */ - AXOM_HOST void initialize(const conduit::Node& dom, - const std::string& topologyName, - const std::string& maskFieldName = {}) override - { - SLIC_ASSERT(conduit::blueprint::mesh::topology::dims(dom.fetch_existing( - axom::fmt::format("topologies/{}", topologyName))) == DIM); - - // clear(); - - m_mvu = axom::quest::MeshViewUtil(dom, topologyName); - - m_bShape = m_mvu.getCellShape(); - m_coordsViews = m_mvu.getConstCoordsViews(false); - if(!maskFieldName.empty()) - { - m_maskView = m_mvu.template getConstFieldView(maskFieldName, false); - } - - /* - TODO: To get good cache performance, we should make m_caseIds - row-major if fcn is that way, and vice versa. However, Array - only support column-major, so we're stuck with that for now. - */ - m_caseIds = axom::Array(m_bShape); - m_caseIds.fill(0); - } - - /*! - @brief Set the scale field name - @param fcnFieldName Name of nodal function is in dom - */ - void setFunctionField(const std::string& fcnFieldName) override - { - m_fcnView = m_mvu.template getConstFieldView(fcnFieldName, false); - } - - void setContourValue(double contourVal) override - { - m_contourVal = contourVal; - } - -#if 0 - void computeContourMesh() override - { - markCrossings(); - scanCrossings(); - computeContour(); - } -#endif - - /*! - @brief Implementation of virtual markCrossings. - - Virtual methods cannot be templated, so this implementation - delegates to a name templated on DIM. - */ - void markCrossings() override { markCrossings_dim(); } - - //!@brief Populate m_caseIds with crossing indices. - template - typename std::enable_if::type markCrossings_dim() - { - MarkCrossings_Util mcu(m_caseIds, m_fcnView, m_maskView, m_contourVal); - -#if defined(AXOM_USE_RAJA) - RAJA::RangeSegment jRange(0, m_bShape[1]); - RAJA::RangeSegment iRange(0, m_bShape[0]); - using EXEC_POL = - typename axom::mint::internal::structured_exec::loop2d_policy; - RAJA::kernel( - RAJA::make_tuple(iRange, jRange), - AXOM_LAMBDA(axom::IndexType i, axom::IndexType j) { - mcu.computeCaseId(i, j); - }); -#else - for(int j = 0; j < m_bShape[1]; ++j) - { - for(int i = 0; i < m_bShape[0]; ++i) - { - mcu.computeCaseId(i, j); - } - } -#endif - } - - //!@brief Populate m_caseIds with crossing indices. - template - typename std::enable_if::type markCrossings_dim() - { - MarkCrossings_Util mcu(m_caseIds, m_fcnView, m_maskView, m_contourVal); - -#if defined(AXOM_USE_RAJA) - RAJA::RangeSegment kRange(0, m_bShape[2]); - RAJA::RangeSegment jRange(0, m_bShape[1]); - RAJA::RangeSegment iRange(0, m_bShape[0]); - using EXEC_POL = - typename axom::mint::internal::structured_exec::loop3d_policy; - RAJA::kernel( - RAJA::make_tuple(iRange, jRange, kRange), - AXOM_LAMBDA(axom::IndexType i, axom::IndexType j, axom::IndexType k) { - mcu.computeCaseId(i, j, k); - }); -#else - for(int k = 0; k < m_bShape[2]; ++k) - { - for(int j = 0; j < m_bShape[1]; ++j) - { - for(int i = 0; i < m_bShape[0]; ++i) - { - mcu.computeCaseId(i, j, k); - } - } - } -#endif - } - - /*! - @brief Implementation used by MarchingCubesHybridParallel::markCrossings_dim() - containing just the objects needed for that part, to be made available - on devices. - */ - struct MarkCrossings_Util - { - axom::ArrayView caseIdsView; - axom::ArrayView fcnView; - axom::ArrayView maskView; - double contourVal; - MarkCrossings_Util(axom::Array& caseIds, - axom::ArrayView& fcnView_, - axom::ArrayView& maskView_, - double contourVal_) - : caseIdsView(caseIds.view()) - , fcnView(fcnView_) - , maskView(maskView_) - , contourVal(contourVal_) - { } - - //!@brief Compute the case index into cases2D or cases3D. - AXOM_HOST_DEVICE inline int computeCrossingCase(const double* f) const - { - int index = 0; - for(int n = 0; n < CELL_CORNER_COUNT; ++n) - { - if(f[n] >= contourVal) - { - const int bit = (1 << n); - index |= bit; - } - } - return index; - } - - template - AXOM_HOST_DEVICE inline typename std::enable_if::type - computeCaseId(axom::IndexType i, axom::IndexType j) const - { - const bool useZone = maskView.empty() || bool(maskView(i, j)); - if(useZone) - { - // clang-format off - double nodalValues[CELL_CORNER_COUNT] = - {fcnView(i , j ), - fcnView(i + 1, j ), - fcnView(i + 1, j + 1), - fcnView(i , j + 1)}; - // clang-format on - caseIdsView(i, j) = computeCrossingCase(nodalValues); - } - } - - //!@brief Populate m_caseIds with crossing indices. - template - AXOM_HOST_DEVICE inline typename std::enable_if::type - computeCaseId(axom::IndexType i, axom::IndexType j, axom::IndexType k) const - { - const bool useZone = maskView.empty() || bool(maskView(i, j, k)); - if(useZone) - { - // clang-format off - double nodalValues[CELL_CORNER_COUNT] = - {fcnView(i + 1, j , k ), - fcnView(i + 1, j + 1, k ), - fcnView(i , j + 1, k ), - fcnView(i , j , k ), - fcnView(i + 1, j , k + 1), - fcnView(i + 1, j + 1, k + 1), - fcnView(i , j + 1, k + 1), - fcnView(i , j , k + 1)}; - // clang-format on - caseIdsView(i, j, k) = computeCrossingCase(nodalValues); - } - } - }; // MarkCrossings_Util - - /*! - @brief Populate the 1D m_crossings array, one entry for each - parent cell that crosses the contour. - - We sum up the number of contour surface cells from the crossings, - allocate space, then populate it. - */ - void scanCrossings() override - { - const axom::IndexType parentCellCount = m_caseIds.size(); - auto caseIdsView = m_caseIds.view(); -#if defined(AXOM_USE_RAJA) - RAJA::ReduceSum vsum(0); - RAJA::forall( - RAJA::RangeSegment(0, parentCellCount), - AXOM_LAMBDA(RAJA::Index_type n) { - vsum += bool(num_contour_cells(caseIdsView.flatIndex(n))); - }); - m_crossingCount = static_cast(vsum.get()); -#else - axom::IndexType vsum = 0; - for(axom::IndexType n = 0; n < parentCellCount; ++n) - { - vsum += bool(num_contour_cells(caseIdsView.flatIndex(n))); - } - m_crossingCount = vsum; -#endif - - m_crossings.resize(m_crossingCount, {0, 0}); - axom::ArrayView crossingsView = - m_crossings.view(); - - axom::Array addCells(m_crossingCount, m_crossingCount); - const axom::ArrayView addCellsView = addCells.view(); - - axom::IndexType* crossingId = axom::allocate( - 1, - axom::detail::getAllocatorID()); - - auto loopBody = AXOM_LAMBDA(axom::IndexType n) - { - auto caseId = caseIdsView.flatIndex(n); - auto ccc = num_contour_cells(caseId); - if(ccc != 0) - { - addCellsView[*crossingId] = ccc; - crossingsView[*crossingId].caseNum = caseId; - crossingsView[*crossingId].parentCellNum = n; - ++(*crossingId); - } - }; - -#if defined(AXOM_USE_RAJA) - /* - The m_crossings filling loop isn't data-parallel and shouldn't - be parallelized. This contrived RAJA::forall forces it to run - sequentially. - */ - RAJA::forall( - RAJA::RangeSegment(0, 1), - [=] AXOM_HOST_DEVICE(int /* i */) { - *crossingId = 0; - for(axom::IndexType n = 0; n < parentCellCount; ++n) - { - loopBody(n); - } - }); -#else - *crossingId = 0; - for(axom::IndexType n = 0; n < parentCellCount; ++n) - { - loopBody(n); - } - SLIC_ASSERT(*crossingId == m_crossingCount); -#endif - - axom::deallocate(crossingId); - - axom::Array prefixSum(m_crossingCount, - m_crossingCount); - const auto prefixSumView = prefixSum.view(); - - auto copyFirstSurfaceCellId = AXOM_LAMBDA(axom::IndexType n) - { - crossingsView[n].firstSurfaceCellId = prefixSumView[n]; - }; -#if defined(AXOM_USE_RAJA) - // Intel oneAPI compiler segfaults with OpenMP RAJA scan - #ifdef __INTEL_LLVM_COMPILER - using ScanPolicy = - typename axom::execution_space::loop_policy; - #else - using ScanPolicy = typename axom::execution_space::loop_policy; - #endif - RAJA::exclusive_scan( - RAJA::make_span(addCellsView.data(), m_crossingCount), - RAJA::make_span(prefixSumView.data(), m_crossingCount), - RAJA::operators::plus {}); - RAJA::forall(RAJA::RangeSegment(0, m_crossingCount), - copyFirstSurfaceCellId); -#else - if(m_crossingCount > 0) - { - prefixSumView[0] = 0; - for(axom::IndexType i = 1; i < m_crossingCount; ++i) - { - prefixSumView[i] = prefixSumView[i - 1] + addCellsView[i - 1]; - } - for(axom::IndexType i = 0; i < m_crossingCount; ++i) - { - copyFirstSurfaceCellId(i); - } - } -#endif - - // Data from the last crossing tells us how many contour cells there are. - if(m_crossings.empty()) - { - m_contourCellCount = 0; - } - else - { - CrossingInfo back; - axom::copy(&back, - m_crossings.data() + m_crossings.size() - 1, - sizeof(CrossingInfo)); - m_contourCellCount = - back.firstSurfaceCellId + num_contour_cells(back.caseNum); - } - } - - /*! - @brief Implementation used by MarchingCubesHybridParallel::computeContour(). - containing just the objects needed for that part, to be made available - on devices. - */ - struct ComputeContour_Util - { - double contourVal; - MIdx bStrides; - axom::ArrayIndexer indexer; - axom::ArrayView fcnView; - axom::StackArray, DIM> coordsViews; - ComputeContour_Util( - double contourVal_, - const MIdx& bStrides_, - const axom::ArrayView& fcnView_, - const axom::StackArray, DIM> - coordsViews_) - : contourVal(contourVal_) - , bStrides(bStrides_) - , indexer(bStrides_) - , fcnView(fcnView_) - , coordsViews(coordsViews_) - { } - - template - AXOM_HOST_DEVICE typename std::enable_if::type - get_corner_coords_and_values(IndexType cellNum, - Point cornerCoords[], - double cornerValues[]) const - { - const auto& x = coordsViews[0]; - const auto& y = coordsViews[1]; - - const auto c = indexer.toMultiIndex(cellNum); - const auto& i = c[0]; - const auto& j = c[1]; - - // clang-format off - cornerCoords[0] = { x(i , j ), y(i , j ) }; - cornerCoords[1] = { x(i+1, j ), y(i+1, j ) }; - cornerCoords[2] = { x(i+1, j+1), y(i+1, j+1) }; - cornerCoords[3] = { x(i , j+1), y(i , j+1) }; - - cornerValues[0] = fcnView(i , j ); - cornerValues[1] = fcnView(i+1, j ); - cornerValues[2] = fcnView(i+1, j+1); - cornerValues[3] = fcnView(i , j+1); - // clang-format on - } - template - AXOM_HOST_DEVICE typename std::enable_if::type - get_corner_coords_and_values(IndexType cellNum, - Point cornerCoords[], - double cornerValues[]) const - { - const auto& x = coordsViews[0]; - const auto& y = coordsViews[1]; - const auto& z = coordsViews[2]; - - const auto c = indexer.toMultiIndex(cellNum); - const auto& i = c[0]; - const auto& j = c[1]; - const auto& k = c[2]; - - // clang-format off - cornerCoords[0] = { x(i+1, j , k ), y(i+1, j , k ), z(i+1, j , k ) }; - cornerCoords[1] = { x(i+1, j+1, k ), y(i+1, j+1, k ), z(i+1, j+1, k ) }; - cornerCoords[2] = { x(i , j+1, k ), y(i , j+1, k ), z(i , j+1, k ) }; - cornerCoords[3] = { x(i , j , k ), y(i , j , k ), z(i , j , k ) }; - cornerCoords[4] = { x(i+1, j , k+1), y(i+1, j , k+1), z(i+1, j , k+1) }; - cornerCoords[5] = { x(i+1, j+1, k+1), y(i+1, j+1, k+1), z(i+1, j+1, k+1) }; - cornerCoords[6] = { x(i , j+1, k+1), y(i , j+1, k+1), z(i , j+1, k+1) }; - cornerCoords[7] = { x(i , j , k+1), y(i , j , k+1), z(i , j , k+1) }; - - cornerValues[0] = fcnView(i+1, j , k ); - cornerValues[1] = fcnView(i+1, j+1, k ); - cornerValues[2] = fcnView(i , j+1, k ); - cornerValues[3] = fcnView(i , j , k ); - cornerValues[4] = fcnView(i+1, j , k+1); - cornerValues[5] = fcnView(i+1, j+1, k+1); - cornerValues[6] = fcnView(i , j+1, k+1); - cornerValues[7] = fcnView(i , j , k+1); - // clang-format on - } - - //!@brief Interpolate for the contour location crossing a parent edge. - template - AXOM_HOST_DEVICE typename std::enable_if::type linear_interp( - int edgeIdx, - const Point cornerCoords[4], - const double nodeValues[4], - Point& crossingPt) const - { - // STEP 0: get the edge node indices - // 2 nodes define the edge. n1 and n2 are the indices of - // the nodes w.r.t. the square or cubic zone. There is a - // agreed-on ordering of these indices in the arrays xx, yy, - // zz, nodeValues, crossingPt. - int n1 = edgeIdx; - int n2 = (edgeIdx == 3) ? 0 : edgeIdx + 1; - - // STEP 1: get the fields and coordinates from the two points - const double f1 = nodeValues[n1]; - const double f2 = nodeValues[n2]; - - const Point& p1 = cornerCoords[n1]; - const Point& p2 = cornerCoords[n2]; - - // STEP 2: check whether the interpolated point is at one of the two corners. - if(axom::utilities::isNearlyEqual(contourVal, f1) || - axom::utilities::isNearlyEqual(f1, f2)) - { - crossingPt = p1; - return; - } - - if(axom::utilities::isNearlyEqual(contourVal, f2)) - { - crossingPt = p2; - return; - } - - // STEP 3: point is in between the edge points, interpolate its position - constexpr double ptiny = axom::primal::PRIMAL_TINY; - const double df = f2 - f1 + ptiny; //add ptiny to avoid division by zero - const double w = (contourVal - f1) / df; - for(int d = 0; d < DIM; ++d) - { - crossingPt[d] = p1[d] + w * (p2[d] - p1[d]); - } - } - - //!@brief Interpolate for the contour location crossing a parent edge. - template - AXOM_HOST_DEVICE typename std::enable_if::type linear_interp( - int edgeIdx, - const Point cornerCoords[8], - const double nodeValues[8], - Point& crossingPt) const - { - // STEP 0: get the edge node indices - // 2 nodes define the edge. n1 and n2 are the indices of - // the nodes w.r.t. the square or cubic zone. There is a - // agreed-on ordering of these indices in the arrays - // cornerCoords, nodeValues, hex_edge_table. - const int hex_edge_table[] = { - 0, 1, 1, 2, 2, 3, 3, 0, // base - 4, 5, 5, 6, 6, 7, 7, 4, // top - 0, 4, 1, 5, 2, 6, 3, 7 // vertical - }; - - int n1 = hex_edge_table[edgeIdx * 2]; - int n2 = hex_edge_table[edgeIdx * 2 + 1]; - - // STEP 1: get the fields and coordinates from the two points - const double f1 = nodeValues[n1]; - const double f2 = nodeValues[n2]; - - const Point& p1 = cornerCoords[n1]; - const Point& p2 = cornerCoords[n2]; - - // STEP 2: check whether the interpolated point is at one of the two corners. - if(axom::utilities::isNearlyEqual(contourVal, f1) || - axom::utilities::isNearlyEqual(f1, f2)) - { - crossingPt = p1; - return; - } - - if(axom::utilities::isNearlyEqual(contourVal, f2)) - { - crossingPt = p2; - return; - } - - // STEP 3: point is not at corner; interpolate its position - constexpr double ptiny = axom::primal::PRIMAL_TINY; - const double df = f2 - f1 + ptiny; //add ptiny to avoid division by zero - const double w = (contourVal - f1) / df; - for(int d = 0; d < DIM; ++d) - { - crossingPt[d] = p1[d] + w * (p2[d] - p1[d]); - } - } - }; // ComputeContour_Util - - void computeContour() override - { -#if 1 - auto crossingsView = m_crossings.view(); - - // Internal contour mesh data to populate - axom::ArrayView facetNodeIdsView = m_facetNodeIds; - axom::ArrayView facetNodeCoordsView = m_facetNodeCoords; - axom::ArrayView facetParentIdsView = m_facetParentIds; - const axom::IndexType facetIndexOffset = m_facetIndexOffset; - - ComputeContour_Util ccu(m_contourVal, - m_caseIds.strides(), - m_fcnView, - m_coordsViews); - - auto loopBody = AXOM_LAMBDA(axom::IndexType iCrossing) - { - const auto& crossingInfo = crossingsView[iCrossing]; - const IndexType crossingCellCount = num_contour_cells(crossingInfo.caseNum); - SLIC_ASSERT(crossingCellCount > 0); - - // Parent cell data for interpolating new node coordinates. - Point cornerCoords[CELL_CORNER_COUNT]; - double cornerValues[CELL_CORNER_COUNT]; - ccu.get_corner_coords_and_values(crossingInfo.parentCellNum, - cornerCoords, - cornerValues); - - /* - Create the new cell and its DIM nodes. New nodes are on - parent cell edges where the edge intersects the isocontour. - linear_interp for the exact coordinates. - */ - for(int iCell = 0; iCell < crossingCellCount; ++iCell) - { - IndexType newFacetId = facetIndexOffset + crossingInfo.firstSurfaceCellId + iCell; - facetParentIdsView[newFacetId] = crossingInfo.parentCellNum; - for(int d = 0; d < DIM; ++d) - { - IndexType newNodeId = newFacetId * DIM + d; - axom::StackArray idx{newFacetId, d}; - facetNodeIdsView[idx] = newNodeId; - - const int edge = cases_table(crossingInfo.caseNum, iCell * DIM + d); - ccu.linear_interp(edge, - cornerCoords, - cornerValues, - facetNodeCoordsView[newNodeId]); - } - } - }; - axom::for_all(0, m_crossingCount, loopBody); -#else - auto crossingsView = m_crossings.view(); - - /* - Reserve contour mesh data space so we can add data without - reallocation. - */ - const axom::IndexType contourNodeCount = DIM * m_contourCellCount; - m_facetNodeCoords.resize(contourNodeCount); - m_facetNodeIds.resize(m_contourCellCount); - m_facetParentIds.resize(m_contourCellCount); - - auto nodeCoordsView = m_facetNodeCoords.view(); - auto cellCornersView = m_facetNodeIds.view(); - auto cellParentsView = m_facetParentIds.view(); - - ComputeContour_Util ccu(m_contourVal, - m_caseIds.strides(), - m_fcnView, - m_coordsViews); - - auto loopBody = AXOM_LAMBDA(axom::IndexType iCrossing) - { - const auto& crossingInfo = crossingsView[iCrossing]; - const IndexType crossingCellCount = num_contour_cells(crossingInfo.caseNum); - SLIC_ASSERT(crossingCellCount > 0); - - // Parent cell data for interpolating new node coordinates. - Point cornerCoords[CELL_CORNER_COUNT]; - double cornerValues[CELL_CORNER_COUNT]; - ccu.get_corner_coords_and_values(crossingInfo.parentCellNum, - cornerCoords, - cornerValues); - - /* - Create the new cell and its DIM nodes. New nodes are on - parent cell edges where the edge intersects the isocontour. - linear_interp for the exact coordinates. - - TODO: The varying crossingCellCount value may inhibit device - performance. Try grouping m_crossings items that have the - same values for crossingCellCount. - */ - for(int iCell = 0; iCell < crossingCellCount; ++iCell) - { - IndexType contourCellId = crossingInfo.firstSurfaceCellId + iCell; - cellParentsView[contourCellId] = crossingInfo.parentCellNum; - for(int d = 0; d < DIM; ++d) - { - IndexType contourNodeId = contourCellId * DIM + d; - cellCornersView[contourCellId][d] = contourNodeId; - - const int edge = cases_table(crossingInfo.caseNum, iCell * DIM + d); - ccu.linear_interp(edge, - cornerCoords, - cornerValues, - nodeCoordsView[contourNodeId]); - } - } - }; - axom::for_all(0, m_crossingCount, loopBody); -#endif - } - - // These 4 functions provide access to the look-up table - // whether on host or device. Is there a more elegant way - // to put static 1D and 2D arrays on both host and device? BTNG. - - template - AXOM_HOST_DEVICE inline typename std::enable_if::type - num_contour_cells(int iCase) const - { -#define _MC_LOOKUP_NUM_SEGMENTS -#include "marching_cubes_lookup.hpp" -#undef _MC_LOOKUP_NUM_SEGMENTS - SLIC_ASSERT(iCase >= 0 && iCase < 16); - return num_segments[iCase]; - } - - template - AXOM_HOST_DEVICE inline typename std::enable_if::type - cases_table(int iCase, int iEdge) const - { -#define _MC_LOOKUP_CASES2D -#include "marching_cubes_lookup.hpp" -#undef _MC_LOOKUP_CASES2D - SLIC_ASSERT(iCase >= 0 && iCase < 16); - return cases2D[iCase][iEdge]; - } - - template - AXOM_HOST_DEVICE inline typename std::enable_if::type - num_contour_cells(int iCase) const - { -#define _MC_LOOKUP_NUM_TRIANGLES -#include "marching_cubes_lookup.hpp" -#undef _MC_LOOKUP_NUM_TRIANGLES - SLIC_ASSERT(iCase >= 0 && iCase < 256); - return num_triangles[iCase]; - } - - template - AXOM_HOST_DEVICE inline typename std::enable_if::type - cases_table(int iCase, int iEdge) const - { -#define _MC_LOOKUP_CASES3D -#include "marching_cubes_lookup.hpp" -#undef _MC_LOOKUP_CASES3D - SLIC_ASSERT(iCase >= 0 && iCase < 256); - return cases3D[iCase][iEdge]; - } - -#if 0 - /*! - @brief Output contour mesh to a mint::UnstructuredMesh object. - */ - void populateContourMesh( - axom::mint::UnstructuredMesh& mesh, - const std::string& cellIdField) const override - { - auto internalAllocatorID = axom::execution_space::allocatorID(); - auto hostAllocatorID = axom::execution_space::allocatorID(); - - /* - mint uses host memory. If internal memory is on the host, use - it. Otherwise, make a temporary copy of it on the host. - */ - if(internalAllocatorID == hostAllocatorID) - { - populateContourMesh(mesh, - cellIdField, - m_facetNodeCoords, - m_facetNodeIds, - m_facetParentIds); - } - else - { - axom::Array facetNodeCoords( - m_facetNodeCoords, - hostAllocatorID); - axom::Array facetNodeIds( - m_facetNodeIds, - hostAllocatorID); - axom::Array facetParentIds( - m_facetParentIds, - hostAllocatorID); - - populateContourMesh(mesh, - cellIdField, - facetNodeCoords, - facetNodeIds, - facetParentIds); - } - } - - //!@brief Output contour mesh to a mint::UnstructuredMesh object. - void populateContourMesh( - axom::mint::UnstructuredMesh& mesh, - const std::string& cellIdField, - const axom::Array facetNodeCoords, - const axom::Array facetNodeIds, - const axom::Array facetParentIds) const - { - // AXOM_PERF_MARK_FUNCTION("MarchingCubesHybridParallel::populateContourMesh"); - if(!cellIdField.empty() && - !mesh.hasField(cellIdField, axom::mint::CELL_CENTERED)) - { - mesh.createField(cellIdField, - axom::mint::CELL_CENTERED, - DIM); - } - - const axom::IndexType addedCellCount = facetNodeIds.size(); - const axom::IndexType addedNodeCount = facetNodeCoords.size(); - if(addedCellCount != 0) - { - const axom::IndexType priorCellCount = mesh.getNumberOfCells(); - const axom::IndexType priorNodeCount = mesh.getNumberOfNodes(); - mesh.reserveNodes(priorNodeCount + addedNodeCount); - mesh.reserveCells(priorCellCount + addedCellCount); - - mesh.appendNodes((double*)facetNodeCoords.data(), - facetNodeCoords.size()); - for(int n = 0; n < addedCellCount; ++n) - { - MIdx cornerIds = facetNodeIds[n]; - // Bump corner indices by priorNodeCount to avoid indices - // used by other parents domains. - for(int d = 0; d < DIM; ++d) - { - cornerIds[d] += priorNodeCount; - } - mesh.appendCell(cornerIds); - } - axom::IndexType numComponents = -1; - axom::IndexType* cellIdPtr = - mesh.getFieldPtr(cellIdField, - axom::mint::CELL_CENTERED, - numComponents); - SLIC_ASSERT(numComponents == DIM); - axom::ArrayView> cellIdView( - (axom::StackArray*)cellIdPtr, - priorCellCount + addedCellCount); - axom::ArrayIndexer si(m_caseIds.shape(), 'c'); - for(axom::IndexType i = 0; i < addedCellCount; ++i) - { - cellIdView[priorCellCount + i] = si.toMultiIndex(facetParentIds[i]); - } - } - } -#endif - - //!@brief Compute the case index into cases2D or cases3D. - AXOM_HOST_DEVICE inline int compute_crossing_case(const double* f) const - { - int index = 0; - for(int n = 0; n < CELL_CORNER_COUNT; ++n) - { - if(f[n] >= m_contourVal) - { - const int bit = (1 << n); - index |= bit; - } - } - return index; - } - -#if 0 - //!@brief Clear data so you can rerun with a different contour value. - void clear() - { - m_facetNodeCoords.clear(); - m_facetNodeIds.clear(); - m_facetParentIds.clear(); - m_crossingCount = 0; - m_contourCellCount = 0; - } -#endif - - /*! - @brief Constructor. - */ - MarchingCubesHybridParallel() - : m_crossings(0, 0) - { } - - /*! - @brief Info for a parent cell intersecting the contour surface. - */ - struct CrossingInfo - { - CrossingInfo() { } - CrossingInfo(axom::IndexType parentCellNum_, std::uint16_t caseNum_) - : parentCellNum(parentCellNum_) - , caseNum(caseNum_) - , firstSurfaceCellId(std::numeric_limits::max()) - { } - axom::IndexType parentCellNum; //!< @brief Flat index of parent cell in m_caseIds. - std::uint16_t caseNum; //!< @brief Index in cases2D or cases3D - axom::IndexType firstSurfaceCellId; //!< @brief First index for generated cells. - }; - -private: - axom::quest::MeshViewUtil m_mvu; - MIdx m_bShape; //!< @brief Blueprint cell data shape. - - // Views of parent domain data. - // DIM coordinate components, each on a DIM-dimensional mesh. - using CoordViews = - axom::StackArray, DIM>; - CoordViews m_coordsViews; - axom::ArrayView m_fcnView; - axom::ArrayView m_maskView; - - //!@brief Crossing case for each computational mesh cell. - axom::Array m_caseIds; - - //!@brief Info on every parent cell that crosses the contour surface. - axom::Array m_crossings; - - //!@brief Number of parent cells crossing the contour surface. - axom::IndexType m_crossingCount = 0; - - //!@brief Number of contour surface cells from crossings. - axom::IndexType m_contourCellCount = 0; - axom::IndexType getContourCellCount() const override - { - return m_contourCellCount; - } - - //!@brief Number of corners (nodes) on each parent cell. - static constexpr std::uint8_t CELL_CORNER_COUNT = (DIM == 3) ? 8 : 4; - - double m_contourVal = 0.0; -}; - -/*! - @brief Allocate a MarchingCubesHybridParallel object, template-specialized - for caller-specified runtime policy and physical dimension. -*/ -static std::unique_ptr -newMarchingCubesHybridParallel(MarchingCubes::RuntimePolicy runtimePolicy, int dim) -{ - using ImplBase = axom::quest::MarchingCubesSingleDomain::ImplBase; - - SLIC_ASSERT(dim >= 2 && dim <= 3); - std::unique_ptr impl; - if(runtimePolicy == MarchingCubes::RuntimePolicy::seq) - { - impl = dim == 2 - ? std::unique_ptr( - new MarchingCubesHybridParallel<2, axom::SEQ_EXEC, axom::SEQ_EXEC>) - : std::unique_ptr( - new MarchingCubesHybridParallel<3, axom::SEQ_EXEC, axom::SEQ_EXEC>); - } -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP - else if(runtimePolicy == MarchingCubes::RuntimePolicy::omp) - { - impl = dim == 2 - ? std::unique_ptr( - new MarchingCubesHybridParallel<2, axom::OMP_EXEC, axom::SEQ_EXEC>) - : std::unique_ptr( - new MarchingCubesHybridParallel<3, axom::OMP_EXEC, axom::SEQ_EXEC>); - } -#endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA - else if(runtimePolicy == MarchingCubes::RuntimePolicy::cuda) - { - impl = dim == 2 - ? std::unique_ptr( - new MarchingCubesHybridParallel<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>) - : std::unique_ptr( - new MarchingCubesHybridParallel<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>); - } -#endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP - else if(runtimePolicy == MarchingCubes::RuntimePolicy::hip) - { - impl = dim == 2 - ? std::unique_ptr( - new MarchingCubesHybridParallel<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>) - : std::unique_ptr( - new MarchingCubesHybridParallel<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>); - } -#endif - else - { - SLIC_ERROR(axom::fmt::format( - "MarchingCubesSingleDomain has no implementation for runtime policy {}", - runtimePolicy)); - } - return impl; -} - -} // end namespace marching_cubes -} // end namespace detail -} // end namespace quest -} // end namespace axom diff --git a/src/axom/quest/detail/MarchingCubesFullParallel.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp similarity index 95% rename from src/axom/quest/detail/MarchingCubesFullParallel.hpp rename to src/axom/quest/detail/MarchingCubesImpl.hpp index c6a4f2b800..f2c6117540 100644 --- a/src/axom/quest/detail/MarchingCubesFullParallel.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -7,7 +7,7 @@ // Implementation requires Conduit. #ifndef AXOM_USE_CONDUIT - #error "MarchingCubesFullParallel.hpp requires conduit" + #error "MarchingCubesImpl.hpp requires conduit" #endif #include "conduit_blueprint.hpp" @@ -37,10 +37,10 @@ namespace marching_cubes axom::CUDA_EXEC<256>. See MarchingCubesImpl for the difference between that class and - MarchingCubesFullParallel. + MarchingCubesImpl. */ template -class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase +class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { public: using Point = axom::primal::Point; @@ -172,7 +172,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase } /*! - @brief Implementation used by MarchingCubesFullParallel::markCrossings_dim() + @brief Implementation used by MarchingCubesImpl::markCrossings_dim() containing just the objects needed for that part, to be made available on devices. */ @@ -511,7 +511,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase } /*! - @brief Implementation used by MarchingCubesFullParallel::computeContour(). + @brief Implementation used by MarchingCubesImpl::computeContour(). containing just the objects needed for that part, to be made available on devices. */ @@ -763,7 +763,7 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase /*! @brief Constructor. */ - MarchingCubesFullParallel() + MarchingCubesImpl() { } private: @@ -804,11 +804,11 @@ class MarchingCubesFullParallel : public MarchingCubesSingleDomain::ImplBase }; /*! - @brief Allocate a MarchingCubesFullParallel object, template-specialized + @brief Allocate a MarchingCubesImpl object, template-specialized for caller-specified runtime policy and physical dimension. */ static std::unique_ptr -newMarchingCubesFullParallel(MarchingCubes::RuntimePolicy runtimePolicy, int dim) +newMarchingCubesImpl(MarchingCubes::RuntimePolicy runtimePolicy, int dim) { using ImplBase = axom::quest::MarchingCubesSingleDomain::ImplBase; @@ -817,35 +817,35 @@ newMarchingCubesFullParallel(MarchingCubes::RuntimePolicy runtimePolicy, int dim if(runtimePolicy == MarchingCubes::RuntimePolicy::seq) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesFullParallel<2, axom::SEQ_EXEC, axom::SEQ_EXEC>) + new MarchingCubesImpl<2, axom::SEQ_EXEC, axom::SEQ_EXEC>) : std::unique_ptr( - new MarchingCubesFullParallel<3, axom::SEQ_EXEC, axom::SEQ_EXEC>); + new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>); } #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP else if(runtimePolicy == MarchingCubes::RuntimePolicy::omp) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesFullParallel<2, axom::OMP_EXEC, axom::SEQ_EXEC>) + new MarchingCubesImpl<2, axom::OMP_EXEC, axom::SEQ_EXEC>) : std::unique_ptr( - new MarchingCubesFullParallel<3, axom::OMP_EXEC, axom::SEQ_EXEC>); + new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_CUDA else if(runtimePolicy == MarchingCubes::RuntimePolicy::cuda) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesFullParallel<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>) + new MarchingCubesImpl<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>) : std::unique_ptr( - new MarchingCubesFullParallel<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>); + new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_HIP else if(runtimePolicy == MarchingCubes::RuntimePolicy::hip) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesFullParallel<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>) + new MarchingCubesImpl<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>) : std::unique_ptr( - new MarchingCubesFullParallel<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>); + new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>); } #endif else From eb9b87d52aeb59a5666920006d7c88d82f940b6e Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 26 Jan 2024 12:51:24 -0800 Subject: [PATCH 07/61] Fix some issues with where memory is and getting output data. --- src/axom/quest/ArrayIndexer.hpp | 7 +- src/axom/quest/DistributedClosestPoint.hpp | 2 +- src/axom/quest/MarchingCubes.cpp | 131 ++++++++++-------- src/axom/quest/MarchingCubes.hpp | 119 ++++++++++++---- src/axom/quest/detail/MarchingCubesImpl.hpp | 33 +++-- .../examples/quest_marching_cubes_example.cpp | 103 ++++++-------- 6 files changed, 242 insertions(+), 153 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index 84e417e9bf..0b627511c3 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -73,7 +73,7 @@ class ArrayIndexer m_strides[d] = m_strides[d + 1] * shape[d + 1]; } } - SLIC_ASSERT((DIM == 1 && getOrder() == 'r' | 's') || (getOrder() == order)); + SLIC_ASSERT((DIM == 1 && getOrder() == ('r' | 's')) || (getOrder() == order)); } //!@brief Initialize for arbitrary-stride indexing. @@ -90,7 +90,10 @@ class ArrayIndexer { if(m_strides[m_slowestDirs[s]] < m_strides[m_slowestDirs[d]]) { - std::swap(m_slowestDirs[s], m_slowestDirs[d]); + // Swap values. + auto tmp = m_slowestDirs[s]; + m_slowestDirs[s] = m_slowestDirs[d]; + m_slowestDirs[d] = tmp; } } } diff --git a/src/axom/quest/DistributedClosestPoint.hpp b/src/axom/quest/DistributedClosestPoint.hpp index 35b4009a01..0d6d070223 100644 --- a/src/axom/quest/DistributedClosestPoint.hpp +++ b/src/axom/quest/DistributedClosestPoint.hpp @@ -78,7 +78,7 @@ class DistributedClosestPoint /*! @brief Sets the allocator ID. If not explitly set, the allocator ID is the default is the id - associated with the runtimer policy. + associated with the runtime policy. */ void setAllocatorID(int allocatorID); diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 1733531ac1..8a86747c27 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -21,12 +21,15 @@ namespace axom { namespace quest { + MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, + int allocatorID, MarchingCubesDataParallelism dataParallelism, const conduit::Node& bpMesh, const std::string& topologyName, const std::string& maskField) : m_runtimePolicy(runtimePolicy) + , m_allocatorID(allocatorID) , m_dataParallelism(dataParallelism) , m_singles() , m_topologyName(topologyName) @@ -43,6 +46,7 @@ MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, for(auto& dom : bpMesh.children()) { m_singles.emplace_back(new MarchingCubesSingleDomain(m_runtimePolicy, + m_allocatorID, m_dataParallelism, dom, m_topologyName, @@ -116,10 +120,33 @@ axom::IndexType MarchingCubes::getContourNodeCount() const return contourNodeCount; } +/* + Domain ids are provided as a new Array instead of ArrayView because + we don't store it internally. +*/ +axom::Array MarchingCubes::getContourFacetDomainIds( + int allocatorID) const +{ + // Put parent domain ids into a new Array. + const axom::IndexType len = getContourCellCount(); + axom::Array rval(len, len, allocatorID != axom::INVALID_ALLOCATOR_ID ? + allocatorID : m_allocatorID); + for(int d = 0; d < m_singles.size(); ++d) + { + axom::detail::ArrayOps::fill( + rval.data(), + m_facetIndexOffsets[d], + m_singles[d]->getContourCellCount(), + m_allocatorID, + m_singles[d]->getDomainId(d)); + } + return rval; +} + void MarchingCubes::populateContourMesh( axom::mint::UnstructuredMesh& mesh, const std::string& cellIdField, - const std::string& domainIdField) + const std::string& domainIdField) const { if(!cellIdField.empty() && !mesh.hasField(cellIdField, axom::mint::CELL_CENTERED)) @@ -141,86 +168,75 @@ void MarchingCubes::populateContourMesh( mesh.reserveNodes(contourNodeCount); if (m_facetCount) { - mesh.appendCells(m_facetNodeIds.data(), m_facetCount); - mesh.appendNodes(m_facetNodeCoords.data(), mesh.getDimension()*m_facetCount); - - axom::IndexType* cellIdPtr = - mesh.getFieldPtr(cellIdField, - axom::mint::CELL_CENTERED); - axom::copy(cellIdPtr, - m_facetParentIds.data(), - m_facetCount*sizeof(axom::IndexType)); - - // TODO: Move domain id stuff into a separate function. - auto* domainIdPtr = - mesh.getFieldPtr(domainIdField, - axom::mint::CELL_CENTERED); - for(int d = 0; d < m_singles.size(); ++d) + // Put nodes and cells into the mesh. + // If data is not in host memory, copy to temporary host memory first. + axom::MemorySpace internalMemorySpace = axom::detail::getAllocatorSpace(m_allocatorID); + bool copyToHost = internalMemorySpace != axom::MemorySpace::Dynamic +#ifdef AXOM_USE_UMPIRE + && internalMemorySpace != axom::MemorySpace::Host +#endif + ; + if(copyToHost) { + const int hostAllocatorId = axom::detail::getAllocatorID(); + axom::ArraytmpfacetNodeCoords(m_facetNodeCoords, hostAllocatorId); + axom::ArraytmpfacetNodeIds(m_facetNodeIds, hostAllocatorId); + mesh.appendNodes(tmpfacetNodeCoords.data(), mesh.getDimension()*m_facetCount); + mesh.appendCells(tmpfacetNodeIds.data(), m_facetCount); + } + else + { + mesh.appendNodes(m_facetNodeCoords.data(), mesh.getDimension()*m_facetCount); + mesh.appendCells(m_facetNodeIds.data(), m_facetCount); + } + + if(!cellIdField.empty()) + { + // Put parent cell ids into the mesh. + axom::IndexType* cellIdPtr = + mesh.getFieldPtr(cellIdField, + axom::mint::CELL_CENTERED); + axom::copy(cellIdPtr, + m_facetParentIds.data(), + m_facetCount*sizeof(axom::IndexType)); + } + + if(!domainIdField.empty()) { - axom::detail::ArrayOps::fill( - domainIdPtr, - m_facetIndexOffsets[d], - m_singles[d]->getContourCellCount(), - execution_space::allocatorID(), - m_singles[d]->getDomainId(d)); + // Put parent domain ids into the mesh. + auto* domainIdPtr = + mesh.getFieldPtr(domainIdField, + axom::mint::CELL_CENTERED); + auto tmpContourFacetDomainIds = getContourFacetDomainIds( + axom::execution_space::allocatorID()); + axom::copy(domainIdPtr, tmpContourFacetDomainIds.data(), m_facetCount*sizeof(axom::IndexType)); } } - SLIC_ASSERT(mesh.getNumberOfNodes() == contourNodeCount); - SLIC_ASSERT(mesh.getNumberOfCells() == contourCellCount); } void MarchingCubes::allocateOutputBuffers() { - int allocatorId = -1; - if(m_runtimePolicy == MarchingCubes::RuntimePolicy::seq) - { - allocatorId = axom::execution_space::allocatorID(); - } -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP - else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::omp) - { - allocatorId = axom::execution_space::allocatorID(); - } -#endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA - else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::cuda) - { - allocatorId = axom::execution_space>::allocatorID(); - } -#endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP - else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::hip) - { - allocatorId = axom::execution_space>::allocatorID(); - } -#endif - else - { - SLIC_ERROR(axom::fmt::format( - "MarchingCubes doesn't recognize runtime policy {}", - m_runtimePolicy)); - } - if (!m_singles.empty()) { int ndim = m_singles[0]->spatialDimension(); const auto nodeCount = m_facetCount * ndim; m_facetNodeIds = - axom::Array({m_facetCount, ndim}, allocatorId); + axom::Array({m_facetCount, ndim}, m_allocatorID); m_facetNodeCoords = - axom::Array({nodeCount, ndim}, allocatorId); + axom::Array({nodeCount, ndim}, m_allocatorID); axom::StackArray t1{m_facetCount}; m_facetParentIds = - axom::Array(t1, allocatorId); + axom::Array(t1, m_allocatorID); } } MarchingCubesSingleDomain::MarchingCubesSingleDomain(RuntimePolicy runtimePolicy, + int allocatorID, MarchingCubesDataParallelism dataPar, const conduit::Node& dom, const std::string& topologyName, const std::string& maskField) : m_runtimePolicy(runtimePolicy) + , m_allocatorID(allocatorID) , m_dataParallelism(dataPar) , m_dom(nullptr) , m_ndim(0) @@ -235,6 +251,7 @@ MarchingCubesSingleDomain::MarchingCubesSingleDomain(RuntimePolicy runtimePolicy m_impl = axom::quest::detail::marching_cubes::newMarchingCubesImpl( m_runtimePolicy, + m_allocatorID, m_ndim); m_impl->initialize(*m_dom, m_topologyName, m_maskFieldName); diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index b4ccb03975..284fa8f421 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -95,6 +95,13 @@ class MarchingCubesSingleDomain; * To avoid confusion between the two meshes, we refer to the input * mesh with the scalar function as "parent" and the generated mesh * as the "contour". + * + * The output contour mesh format can be a mint::UnstructuredMesh or + * Array data. IDs of parent cell and domain that generated the + * individual contour facets are provided. Blueprint allows users to + * specify ids for the domains. If "state/domain_id" exists in the + * domains, it is used as the domain id. Otherwise, the domain's + * interation index within the multidomain mesh is used. */ class MarchingCubes { @@ -107,6 +114,8 @@ class MarchingCubes * \param [in] runtimePolicy A value from RuntimePolicy. * The simplest policy is RuntimePolicy::seq, which specifies * running sequentially on the CPU. + * \param [in] allocatorID Data allocator ID. Choose something compatible + * with \c runtimePolicy. See \c esecution_space. * \param [in] dataParallelism Data parallel implementation choice. * \param [in] bpMesh Blueprint multi-domain mesh containing scalar field. * \param [in] topologyName Name of Blueprint topology to use in \a bpMesh. @@ -124,8 +133,14 @@ class MarchingCubes * conduit::blueprint::is_contiguous(). In the future, this * requirement may be relaxed, possibly at the cost of a * transformation and storage of the temporary contiguous layout. + * + * Blueprint allows users to specify ids for the domains. If + * "state/domain_id" exists in the domains, it is used as the domain + * id. Otherwise, the domain's interation index within the + * multidomain mesh is used. */ MarchingCubes(RuntimePolicy runtimePolicy, + int allocatorId, MarchingCubesDataParallelism dataParallelism, const conduit::Node &bpMesh, const std::string &topologyName, @@ -149,56 +164,103 @@ class MarchingCubes //!@brief Get number of nodes in the generated contour mesh. axom::IndexType getContourNodeCount() const; + //@{ + //!@name Output methods + /*! + @brief Put generated contour in a mint::UnstructuredMesh. + @param mesh Output contour mesh + @param cellIdField Name of field to store the array of + parent cells' multidimensional indices. + If empty, the data is not provided. + @param domainIdField Name of field to store the (axom::IndexType) + parent domain ids. If omitted, the data is not provided. + + If the fields aren't in the mesh, they will be created. + + Important: mint::UnstructuredMesh only supports host memory, so + regardless of the allocator ID, this method always deep-copies + data to host memory. To access the data without deep-copying, see + the other output methods. + */ + void populateContourMesh( + axom::mint::UnstructuredMesh &mesh, + const std::string &cellIdField = {}, + const std::string &domainIdField = {}) const; + /*! - @brief Return pointer to facet corner node indices (connectivity) + @brief Return view of facet corner node indices (connectivity) Array. + + The array shape is (getContourCellCount(), ), where + the second index is index of the facet corner. - The buffer size is the x getContourCellCount(). - Memory space of data depends on runtime policy. + Memory space of data corresponds to allocator set in the constructor. */ axom::ArrayView getContourFacetCorners() const { return m_facetNodeIds.view(); } /*! - @brief Return pointer to node coordinates. + @brief Return view of node coordinates Array. - The buffer size is x getContourNodeCount(). - Memory space of data depends on runtime policy. + The array shape is (getContourNodeCount(), ), where + the second index is the spatial index. + + Memory space of data corresponds to allocator set in the constructor. */ axom::ArrayView getContourNodeCoords() const { return m_facetNodeCoords.view(); } /*! - @brief Return pointer to parent cell indices + @brief Return view of parent cell indices Array. - The buffer size is getContourCellCount(). - Memory space of data depends on runtime policy. + The buffer size is getContourCellCount(). The parent ID is the + column-major ordered flat index of the cell in the parent domain + (see ArrayIndexer), not counting ghost cells. + + Memory space of data corresponds to allocator set in the constructor. */ axom::ArrayView getContourFacetParents() const { return m_facetParentIds.view(); } /*! - @brief Put generated contour in a mint::UnstructuredMesh. - @param mesh Output contour mesh - @param cellIdField Name of field to store the array of - parent cells' multidimensional indices. - If empty, the data is not provided. - @param domainIdField Name of field to store the (axom::IndexType) - parent domain ids. If omitted, the data is not provided. + @brief Return view of parent domain indices Array. + @param allocatorID Allocator id for the output data. If omitted, + use the id set in the constructor. - If the fields aren't in the mesh, they will be created. + The buffer size is getContourCellCount(). - Blueprint allows users to specify ids for the domains. If - "state/domain_id" exists in the domains, it is used as the domain - id. Otherwise, the domain's interation index within the - multidomain mesh is used. + Memory space of data corresponds to allocator set in the constructor. */ - void populateContourMesh( - axom::mint::UnstructuredMesh &mesh, - const std::string &cellIdField = {}, - const std::string &domainIdField = {}); + axom::Array getContourFacetDomainIds(int allocatorID=axom::INVALID_ALLOCATOR_ID) const; + +#if 1 + // Is there a use case for this? + /*! + @brief Give caller posession of the contour data. + + This efficiently turns the generated contour data to the caller, + to stay in scope after the MarchingCubes object is deleted. + + @pre isoContour() must have been called. + @post outputs can no longer be accessed from object. + */ + void relinguishContourData( + axom::Array& facetNodeIds, + axom::Array& facetNodeCoords, + axom::Array& facetParentIds) + { + facetNodeIds.swap(m_facetNodeIds); + facetNodeCoords.swap(m_facetNodeCoords); + facetParentIds.swap(m_facetParentIds); + m_facetNodeIds.clear(); + m_facetNodeCoords.clear(); + m_facetParentIds.clear(); + } +#endif + //@} private: RuntimePolicy m_runtimePolicy; + int m_allocatorID = axom::INVALID_ALLOCATOR_ID; //@brief Choice of full or partial data-parallelism, or byPolicy. MarchingCubesDataParallelism m_dataParallelism = @@ -260,6 +322,8 @@ class MarchingCubesSingleDomain * \param [in] runtimePolicy A value from RuntimePolicy. * The simplest policy is RuntimePolicy::seq, which specifies * running sequentially on the CPU. + * \param [in] allocatorID Data allocator ID. Choose something compatible + * with \c runtimePolicy. See \c esecution_space. * \param [in] dataPar Choice of data-parallel implementation. * \param [in] dom Blueprint single-domain mesh containing scalar field. * \param [in] topologyName Name of Blueprint topology to use in \a dom @@ -279,6 +343,7 @@ class MarchingCubesSingleDomain * transformation and storage of the temporary contiguous layout. */ MarchingCubesSingleDomain(RuntimePolicy runtimePolicy, + int allocatorID, MarchingCubesDataParallelism dataPar, const conduit::Node &dom, const std::string &topologyName, @@ -318,6 +383,9 @@ class MarchingCubesSingleDomain axom::ArrayView& facetParentIds, axom::IndexType facetIndexOffset) { + SLIC_ASSERT(facetNodeIds.getAllocatorID() == m_allocatorID); + SLIC_ASSERT(facetNodeCoords.getAllocatorID() == m_allocatorID); + SLIC_ASSERT(facetParentIds.getAllocatorID() == m_allocatorID); m_impl->setOutputBuffers( facetNodeIds, facetNodeCoords, facetParentIds, @@ -418,6 +486,7 @@ class MarchingCubesSingleDomain private: RuntimePolicy m_runtimePolicy; + int m_allocatorID = axom::INVALID_ALLOCATOR_ID; //@brief Choice of full or partial data-parallelism, or byPolicy. MarchingCubesDataParallelism m_dataParallelism = diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index f2c6117540..34c1e78c8a 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -51,6 +51,12 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase using SequentialLoopPolicy = typename execution_space::loop_policy; static constexpr auto MemorySpace = execution_space::memory_space; + + AXOM_HOST MarchingCubesImpl(int allocatorID) + : m_allocatorID(allocatorID) + , m_caseIds(emptyShape(), m_allocatorID) + {} + /*! @brief Initialize data to a blueprint domain. @param dom Blueprint structured mesh domain @@ -767,6 +773,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { } private: + int m_allocatorID; + axom::quest::MeshViewUtil m_mvu; MIdx m_bShape; //!< @brief Blueprint cell data shape. @@ -801,6 +809,13 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase static constexpr std::uint8_t CELL_CORNER_COUNT = (DIM == 3) ? 8 : 4; double m_contourVal = 0.0; + + axom::StackArray emptyShape() + { + axom::StackArray rval; + for(int d=0; d -newMarchingCubesImpl(MarchingCubes::RuntimePolicy runtimePolicy, int dim) +newMarchingCubesImpl(MarchingCubes::RuntimePolicy runtimePolicy, int allocatorID, int dim) { using ImplBase = axom::quest::MarchingCubesSingleDomain::ImplBase; @@ -817,35 +832,35 @@ newMarchingCubesImpl(MarchingCubes::RuntimePolicy runtimePolicy, int dim) if(runtimePolicy == MarchingCubes::RuntimePolicy::seq) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::SEQ_EXEC, axom::SEQ_EXEC>) + new MarchingCubesImpl<2, axom::SEQ_EXEC, axom::SEQ_EXEC>(allocatorID)) : std::unique_ptr( - new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>); + new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>(allocatorID)); } #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP else if(runtimePolicy == MarchingCubes::RuntimePolicy::omp) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::OMP_EXEC, axom::SEQ_EXEC>) + new MarchingCubesImpl<2, axom::OMP_EXEC, axom::SEQ_EXEC>(allocatorID)) : std::unique_ptr( - new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>); + new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>(allocatorID)); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_CUDA else if(runtimePolicy == MarchingCubes::RuntimePolicy::cuda) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>) + new MarchingCubesImpl<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>(allocatorID)) : std::unique_ptr( - new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>); + new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>(allocatorID)); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_HIP else if(runtimePolicy == MarchingCubes::RuntimePolicy::hip) { impl = dim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>) + new MarchingCubesImpl<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>(allocatorID)) : std::unique_ptr( - new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>); + new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>(allocatorID)); } #endif else diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 0f22d2324f..73164fbc10 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -244,45 +244,6 @@ struct Input //!@brief Our allocator id, based on execution policy. static int s_allocatorId = axom::INVALID_ALLOCATOR_ID; // Set in main. -static int allocatorIdForPolicy(axom::runtime_policy::Policy policy) -{ - //--------------------------------------------------------------------------- - // Set default allocator for possibly testing on devices - //--------------------------------------------------------------------------- - int aid = axom::INVALID_ALLOCATOR_ID; - - // clang-format off - if(policy == axom::runtime_policy::Policy::seq) - { - aid = axom::execution_space::allocatorID(); - } -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP - else if(policy == axom::runtime_policy::Policy::omp) - { - aid = axom::execution_space::allocatorID(); - } -#endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA - else if(policy == axom::runtime_policy::Policy::cuda) - { - // aid = axom::execution_space>::allocatorID(); - aid = axom::getUmpireResourceAllocatorID(umpire::resource::Device); - } -#endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP - else if(policy == axom::runtime_policy::Policy::hip) - { - // aid = axom::execution_space>::allocatorID(); - aid = axom::getUmpireResourceAllocatorID(umpire::resource::Device); - } -#endif - // clang-format on - - SLIC_ERROR_IF( - aid == axom::INVALID_ALLOCATOR_ID, - axom::fmt::format("Cannot find allocator id for policy '{}'", policy)); - return aid; -} //!@brief Put a conduit::Node array data into the specified memory space. template @@ -724,7 +685,7 @@ struct ContourTestBase const std::string m_domainIdField; ValueFunctorType m_valueFunctor; - int runTest(BlueprintStructuredMesh& computationalMesh, quest::MarchingCubes& mc) + int runTest(BlueprintStructuredMesh& computationalMesh) { SLIC_INFO(banner(axom::fmt::format("Testing {} contour.", name()))); @@ -782,6 +743,12 @@ struct ContourTestBase } } #endif + // Create marching cubes algorithm object and set some parameters + quest::MarchingCubes mc(params.policy, + s_allocatorId, + params.dataParallelism, + computationalMesh.asConduitNode(), + "mesh"); mc.setFunctionField(functionName()); @@ -825,16 +792,14 @@ struct ContourTestBase axom::execution_space::allocatorID()); } - // Put mesh mesh in a mint object for error checking and output. + // Put contour mesh in a mint object for error checking and output. std::string sidreGroupName = name() + "_mesh"; sidre::DataStore objectDS; sidre::Group* meshGroup = objectDS.getRoot()->createGroup(sidreGroupName); axom::mint::UnstructuredMesh contourMesh( DIM, DIM == 2 ? mint::CellType::SEGMENT : mint::CellType::TRIANGLE, - meshGroup, - mc.getContourNodeCount(), - mc.getContourCellCount()); + meshGroup); axom::utilities::Timer extractTimer(false); extractTimer.start(); mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); @@ -1076,10 +1041,10 @@ struct ContourTestBase } axom::ArrayView get_parent_cell_id_view( - axom::mint::UnstructuredMesh& contourMesh) const + const axom::mint::UnstructuredMesh& contourMesh) const { axom::IndexType numIdxComponents = -1; - axom::IndexType* ptr = + const axom::IndexType* ptr = contourMesh.getFieldPtr(m_parentCellIdField, axom::mint::CELL_CENTERED, numIdxComponents); @@ -1087,7 +1052,7 @@ struct ContourTestBase SLIC_ASSERT(numIdxComponents == 1); axom::ArrayView view( - (axom::IndexType*)ptr, + (const axom::IndexType*)ptr, contourMesh.getNumberOfCells()); return view; } @@ -1329,7 +1294,7 @@ struct RoundFunctor using PointType = axom::primal::Point; const axom::primal::Sphere _sphere; RoundFunctor(const PointType& center) : _sphere(center, 0.0) { } - double operator()(const PointType& pt) const + AXOM_HOST_DEVICE double operator()(const PointType& pt) const { return _sphere.computeSignedDistance(pt); } @@ -1388,7 +1353,7 @@ struct GyroidFunctor : _scale(scale) , _offset(offset) { } - double operator()(const PointType& pt) const + AXOM_HOST_DEVICE double operator()(const PointType& pt) const { if(DIM == 3) { @@ -1458,7 +1423,7 @@ struct PlanarFunctor const PointType& inPlane) : _plane(perpDir.unitVector(), inPlane) { } - double operator()(const PointType& pt) const + AXOM_HOST_DEVICE double operator()(const PointType& pt) const { return _plane.signedDistance(pt); } @@ -1518,6 +1483,31 @@ void makeCoordsInterleaved(conduit::Node& coordValues) } } +/// +int allocatorIdToTest(axom::runtime_policy::Policy policy) +{ +#if defined(AXOM_USE_UMPIRE) + //--------------------------------------------------------------------------- + // Memory resource. For testing, choose device memory if appropriate. + //--------------------------------------------------------------------------- + int allocatorID = + policy == RuntimePolicy::seq ? axom::detail::getAllocatorID() : +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + policy == RuntimePolicy::omp ? axom::detail::getAllocatorID() : +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + policy == RuntimePolicy::cuda ? axom::detail::getAllocatorID() : +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + policy == RuntimePolicy::hip ? axom::detail::getAllocatorID() : +#endif + axom::INVALID_ALLOCATOR_ID; +#else + int allocatorID = axom::getDefaultAllocatorID(); +#endif + return allocatorID; +} + /// Utility function to initialize the logger void initializeLogger() { @@ -1569,11 +1559,6 @@ void finalizeLogger() template int testNdimInstance(BlueprintStructuredMesh& computationalMesh) { - // Create marching cubes algorithm object and set some parameters - quest::MarchingCubes mc(params.policy, - params.dataParallelism, - computationalMesh.asConduitNode(), - "mesh"); //--------------------------------------------------------------------------- // params specify which tests to run. @@ -1617,19 +1602,19 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) if(planarTest) { - localErrCount += planarTest->runTest(computationalMesh, mc); + localErrCount += planarTest->runTest(computationalMesh); } slic::flushStreams(); if(roundTest) { - localErrCount += roundTest->runTest(computationalMesh, mc); + localErrCount += roundTest->runTest(computationalMesh); } slic::flushStreams(); if(gyroidTest) { - localErrCount += gyroidTest->runTest(computationalMesh, mc); + localErrCount += gyroidTest->runTest(computationalMesh); } slic::flushStreams(); @@ -1701,7 +1686,7 @@ int main(int argc, char** argv) exit(retval); } - s_allocatorId = allocatorIdForPolicy(params.policy); + s_allocatorId = allocatorIdToTest(params.policy); //--------------------------------------------------------------------------- // Load computational mesh. From 32f5ae0caa1d1e69d41a4c10a2683673fd71eed5 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 30 Jan 2024 08:35:18 -0800 Subject: [PATCH 08/61] Use specified alloator id for internal data. --- src/axom/quest/detail/MarchingCubesImpl.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 34c1e78c8a..b6fa046461 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -55,6 +55,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase AXOM_HOST MarchingCubesImpl(int allocatorID) : m_allocatorID(allocatorID) , m_caseIds(emptyShape(), m_allocatorID) + , m_crossingParentIds(0, 0, m_allocatorID) + , m_facetIncrs(0, 0, m_allocatorID) + , m_firstFacetIds(0, 0, m_allocatorID) {} /*! @@ -93,7 +96,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase row-major if fcn is that way, and vice versa. However, Array only support column-major, so we're stuck with that for now. */ - m_caseIds = axom::Array(m_bShape); + // m_caseIds.resize(m_bShape, 0); // This unexpectedly fails. + m_caseIds = axom::Array(m_bShape, m_allocatorID); m_caseIds.fill(0); } @@ -259,7 +263,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { constexpr MarchingCubesDataParallelism autoPolicy = std::is_same::value ? MarchingCubesDataParallelism::hybridParallel : +#ifdef AXOM_USE_OPENMP std::is_same::value ? MarchingCubesDataParallelism::hybridParallel : +#endif MarchingCubesDataParallelism::fullParallel; if(m_dataParallelism == From 3b7951ead0a1fdf71a573f56554e927cb17e14d6 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 30 Jan 2024 12:34:00 -0800 Subject: [PATCH 09/61] Silence warnings. --- src/axom/quest/MarchingCubes.hpp | 2 +- src/axom/quest/detail/MarchingCubesImpl.hpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 284fa8f421..e6743456a8 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -435,7 +435,7 @@ class MarchingCubesSingleDomain */ virtual void initialize(const conduit::Node &dom, const std::string &topologyName, - const std::string &maskPath) = 0; + const std::string &maskPath = {}) = 0; virtual void setFunctionField(const std::string& fcnFieldName) = 0; virtual void setContourValue(double contourVal) = 0; diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index b6fa046461..4b33c024bd 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -75,7 +75,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase */ AXOM_HOST void initialize(const conduit::Node& dom, const std::string& topologyName, - const std::string& maskFieldName = {}) override + const std::string& maskFieldName) override { SLIC_ASSERT(conduit::blueprint::mesh::topology::dims(dom.fetch_existing( axom::fmt::format("topologies/{}", topologyName))) == DIM); @@ -802,12 +802,12 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::IndexType m_facetCount = 0; axom::IndexType getContourCellCount() const override { return m_facetCount; } - //!@brief Number of surface mesh facets added by each crossing. - axom::Array m_facetIncrs; - //!@brief Parent cell id (flat index into m_caseIds) for each crossing. axom::Array m_crossingParentIds; + //!@brief Number of surface mesh facets added by each crossing. + axom::Array m_facetIncrs; + //!@brief First index of facets for each crossing. axom::Array m_firstFacetIds; From c03f5c21172d2e28d0dccb0836a119274973e754 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 30 Jan 2024 12:48:01 -0800 Subject: [PATCH 10/61] Autoformat. --- src/axom/quest/ArrayIndexer.hpp | 7 +- src/axom/quest/MarchingCubes.cpp | 79 ++++++++------- src/axom/quest/MarchingCubes.hpp | 77 ++++++++------- src/axom/quest/MeshViewUtil.hpp | 2 +- src/axom/quest/detail/MarchingCubesImpl.hpp | 95 ++++++++++++------- .../examples/quest_marching_cubes_example.cpp | 47 +++++---- 6 files changed, 178 insertions(+), 129 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index 0b627511c3..8e17e71ab8 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -46,7 +46,8 @@ class ArrayIndexer @param [in] shape Shape of the array @param [in] order: c is column major; r is row major. */ - inline AXOM_HOST_DEVICE void initialize(const axom::StackArray& shape, char order) + inline AXOM_HOST_DEVICE void initialize(const axom::StackArray& shape, + char order) { SLIC_ASSERT(order == 'c' || order == 'r'); if(order == 'r') @@ -120,9 +121,9 @@ class ArrayIndexer inline AXOM_HOST_DEVICE char getOrder() const { char order = 'r' | 'c'; - for(int d=0; d < DIM - 1; ++d) + for(int d = 0; d < DIM - 1; ++d) { - order &= m_slowestDirs[d] < m_slowestDirs[d+1] ? 'c' : 'r'; + order &= m_slowestDirs[d] < m_slowestDirs[d + 1] ? 'c' : 'r'; } return order; } diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 8a86747c27..e0568658d3 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -21,7 +21,6 @@ namespace axom { namespace quest { - MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, int allocatorID, MarchingCubesDataParallelism dataParallelism, @@ -70,7 +69,7 @@ void MarchingCubes::computeIsocontour(double contourVal) // facet counts to get the total facet counts. m_facetIndexOffsets.resize(m_singles.size()); m_facetCount = 0; - for(axom::IndexType d=0; dsetOutputBuffers(facetNodeIdsView, facetNodeCoordsView, @@ -94,7 +93,7 @@ void MarchingCubes::computeIsocontour(double contourVal) m_facetIndexOffsets[d]); } - for(axom::IndexType d=0; dcomputeContour(); } @@ -129,8 +128,10 @@ axom::Array MarchingCubes::getContourFacetDomainIds( { // Put parent domain ids into a new Array. const axom::IndexType len = getContourCellCount(); - axom::Array rval(len, len, allocatorID != axom::INVALID_ALLOCATOR_ID ? - allocatorID : m_allocatorID); + axom::Array rval( + len, + len, + allocatorID != axom::INVALID_ALLOCATOR_ID ? allocatorID : m_allocatorID); for(int d = 0; d < m_singles.size(); ++d) { axom::detail::ArrayOps::fill( @@ -167,25 +168,33 @@ void MarchingCubes::populateContourMesh( mesh.reserveCells(contourCellCount); mesh.reserveNodes(contourNodeCount); - if (m_facetCount) { + if(m_facetCount) + { // Put nodes and cells into the mesh. // If data is not in host memory, copy to temporary host memory first. - axom::MemorySpace internalMemorySpace = axom::detail::getAllocatorSpace(m_allocatorID); + axom::MemorySpace internalMemorySpace = + axom::detail::getAllocatorSpace(m_allocatorID); bool copyToHost = internalMemorySpace != axom::MemorySpace::Dynamic #ifdef AXOM_USE_UMPIRE - && internalMemorySpace != axom::MemorySpace::Host + && internalMemorySpace != axom::MemorySpace::Host #endif ; - if(copyToHost) { - const int hostAllocatorId = axom::detail::getAllocatorID(); - axom::ArraytmpfacetNodeCoords(m_facetNodeCoords, hostAllocatorId); - axom::ArraytmpfacetNodeIds(m_facetNodeIds, hostAllocatorId); - mesh.appendNodes(tmpfacetNodeCoords.data(), mesh.getDimension()*m_facetCount); + if(copyToHost) + { + const int hostAllocatorId = + axom::detail::getAllocatorID(); + axom::Array tmpfacetNodeCoords(m_facetNodeCoords, + hostAllocatorId); + axom::Array tmpfacetNodeIds(m_facetNodeIds, + hostAllocatorId); + mesh.appendNodes(tmpfacetNodeCoords.data(), + mesh.getDimension() * m_facetCount); mesh.appendCells(tmpfacetNodeIds.data(), m_facetCount); } else { - mesh.appendNodes(m_facetNodeCoords.data(), mesh.getDimension()*m_facetCount); + mesh.appendNodes(m_facetNodeCoords.data(), + mesh.getDimension() * m_facetCount); mesh.appendCells(m_facetNodeIds.data(), m_facetCount); } @@ -193,11 +202,10 @@ void MarchingCubes::populateContourMesh( { // Put parent cell ids into the mesh. axom::IndexType* cellIdPtr = - mesh.getFieldPtr(cellIdField, - axom::mint::CELL_CENTERED); + mesh.getFieldPtr(cellIdField, axom::mint::CELL_CENTERED); axom::copy(cellIdPtr, m_facetParentIds.data(), - m_facetCount*sizeof(axom::IndexType)); + m_facetCount * sizeof(axom::IndexType)); } if(!domainIdField.empty()) @@ -208,33 +216,34 @@ void MarchingCubes::populateContourMesh( axom::mint::CELL_CENTERED); auto tmpContourFacetDomainIds = getContourFacetDomainIds( axom::execution_space::allocatorID()); - axom::copy(domainIdPtr, tmpContourFacetDomainIds.data(), m_facetCount*sizeof(axom::IndexType)); + axom::copy(domainIdPtr, + tmpContourFacetDomainIds.data(), + m_facetCount * sizeof(axom::IndexType)); } } } void MarchingCubes::allocateOutputBuffers() { - if (!m_singles.empty()) + if(!m_singles.empty()) { int ndim = m_singles[0]->spatialDimension(); const auto nodeCount = m_facetCount * ndim; m_facetNodeIds = axom::Array({m_facetCount, ndim}, m_allocatorID); - m_facetNodeCoords = - axom::Array({nodeCount, ndim}, m_allocatorID); - axom::StackArray t1{m_facetCount}; - m_facetParentIds = - axom::Array(t1, m_allocatorID); + m_facetNodeCoords = axom::Array({nodeCount, ndim}, m_allocatorID); + axom::StackArray t1 {m_facetCount}; + m_facetParentIds = axom::Array(t1, m_allocatorID); } } -MarchingCubesSingleDomain::MarchingCubesSingleDomain(RuntimePolicy runtimePolicy, - int allocatorID, - MarchingCubesDataParallelism dataPar, - const conduit::Node& dom, - const std::string& topologyName, - const std::string& maskField) +MarchingCubesSingleDomain::MarchingCubesSingleDomain( + RuntimePolicy runtimePolicy, + int allocatorID, + MarchingCubesDataParallelism dataPar, + const conduit::Node& dom, + const std::string& topologyName, + const std::string& maskField) : m_runtimePolicy(runtimePolicy) , m_allocatorID(allocatorID) , m_dataParallelism(dataPar) @@ -249,10 +258,10 @@ MarchingCubesSingleDomain::MarchingCubesSingleDomain(RuntimePolicy runtimePolicy // Set domain first, to get m_ndim, which is required to allocate m_impl. setDomain(dom); - m_impl = axom::quest::detail::marching_cubes::newMarchingCubesImpl( - m_runtimePolicy, - m_allocatorID, - m_ndim); + m_impl = + axom::quest::detail::marching_cubes::newMarchingCubesImpl(m_runtimePolicy, + m_allocatorID, + m_ndim); m_impl->initialize(*m_dom, m_topologyName, m_maskFieldName); m_impl->setDataParallelism(m_dataParallelism); diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index e6743456a8..ba170eb01d 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -196,7 +196,9 @@ class MarchingCubes Memory space of data corresponds to allocator set in the constructor. */ axom::ArrayView getContourFacetCorners() const - { return m_facetNodeIds.view(); } + { + return m_facetNodeIds.view(); + } /*! @brief Return view of node coordinates Array. @@ -207,7 +209,9 @@ class MarchingCubes Memory space of data corresponds to allocator set in the constructor. */ axom::ArrayView getContourNodeCoords() const - { return m_facetNodeCoords.view(); } + { + return m_facetNodeCoords.view(); + } /*! @brief Return view of parent cell indices Array. @@ -219,7 +223,9 @@ class MarchingCubes Memory space of data corresponds to allocator set in the constructor. */ axom::ArrayView getContourFacetParents() const - { return m_facetParentIds.view(); } + { + return m_facetParentIds.view(); + } /*! @brief Return view of parent domain indices Array. @@ -230,9 +236,10 @@ class MarchingCubes Memory space of data corresponds to allocator set in the constructor. */ - axom::Array getContourFacetDomainIds(int allocatorID=axom::INVALID_ALLOCATOR_ID) const; + axom::Array getContourFacetDomainIds( + int allocatorID = axom::INVALID_ALLOCATOR_ID) const; -#if 1 + #if 1 // Is there a use case for this? /*! @brief Give caller posession of the contour data. @@ -243,10 +250,9 @@ class MarchingCubes @pre isoContour() must have been called. @post outputs can no longer be accessed from object. */ - void relinguishContourData( - axom::Array& facetNodeIds, - axom::Array& facetNodeCoords, - axom::Array& facetParentIds) + void relinguishContourData(axom::Array &facetNodeIds, + axom::Array &facetNodeCoords, + axom::Array &facetParentIds) { facetNodeIds.swap(m_facetNodeIds); facetNodeCoords.swap(m_facetNodeCoords); @@ -255,7 +261,7 @@ class MarchingCubes m_facetNodeCoords.clear(); m_facetParentIds.clear(); } -#endif + #endif //@} private: @@ -364,33 +370,35 @@ class MarchingCubesSingleDomain SLIC_ASSERT(m_dom->fetch_existing(m_fcnPath + "/association").as_string() == "vertex"); SLIC_ASSERT(m_dom->has_path(m_fcnPath + "/values")); - if (m_impl) m_impl->setFunctionField(fcnField); + if(m_impl) m_impl->setFunctionField(fcnField); } void setContourValue(double contourVal) { m_contourVal = contourVal; - if (m_impl) m_impl->setContourValue(m_contourVal); + if(m_impl) m_impl->setContourValue(m_contourVal); } // Methods trivially delegated to implementation. void markCrossings() { m_impl->markCrossings(); } void scanCrossings() { m_impl->scanCrossings(); } - axom::IndexType getContourCellCount() { return m_impl->getContourCellCount(); } - void setOutputBuffers( - axom::ArrayView& facetNodeIds, - axom::ArrayView& facetNodeCoords, - axom::ArrayView& facetParentIds, - axom::IndexType facetIndexOffset) - { - SLIC_ASSERT(facetNodeIds.getAllocatorID() == m_allocatorID); - SLIC_ASSERT(facetNodeCoords.getAllocatorID() == m_allocatorID); - SLIC_ASSERT(facetParentIds.getAllocatorID() == m_allocatorID); - m_impl->setOutputBuffers( facetNodeIds, - facetNodeCoords, - facetParentIds, - facetIndexOffset ); - } + axom::IndexType getContourCellCount() + { + return m_impl->getContourCellCount(); + } + void setOutputBuffers(axom::ArrayView &facetNodeIds, + axom::ArrayView &facetNodeCoords, + axom::ArrayView &facetParentIds, + axom::IndexType facetIndexOffset) + { + SLIC_ASSERT(facetNodeIds.getAllocatorID() == m_allocatorID); + SLIC_ASSERT(facetNodeCoords.getAllocatorID() == m_allocatorID); + SLIC_ASSERT(facetParentIds.getAllocatorID() == m_allocatorID); + m_impl->setOutputBuffers(facetNodeIds, + facetNodeCoords, + facetParentIds, + facetIndexOffset); + } void computeContour() { m_impl->computeContour(); } /*! @@ -437,11 +445,13 @@ class MarchingCubesSingleDomain const std::string &topologyName, const std::string &maskPath = {}) = 0; - virtual void setFunctionField(const std::string& fcnFieldName) = 0; + virtual void setFunctionField(const std::string &fcnFieldName) = 0; virtual void setContourValue(double contourVal) = 0; void setDataParallelism(MarchingCubesDataParallelism dataPar) - { m_dataParallelism = dataPar; } + { + m_dataParallelism = dataPar; + } //@{ //!@name Distinct phases in contour generation. @@ -460,11 +470,10 @@ class MarchingCubesSingleDomain virtual axom::IndexType getContourCellCount() const = 0; //@} - void setOutputBuffers( - axom::ArrayView& facetNodeIds, - axom::ArrayView& facetNodeCoords, - axom::ArrayView& facetParentIds, - axom::IndexType facetIndexOffset) + void setOutputBuffers(axom::ArrayView &facetNodeIds, + axom::ArrayView &facetNodeCoords, + axom::ArrayView &facetParentIds, + axom::IndexType facetIndexOffset) { m_facetNodeIds = facetNodeIds; m_facetNodeCoords = facetNodeCoords; diff --git a/src/axom/quest/MeshViewUtil.hpp b/src/axom/quest/MeshViewUtil.hpp index 7f898cfa09..d87d529deb 100644 --- a/src/axom/quest/MeshViewUtil.hpp +++ b/src/axom/quest/MeshViewUtil.hpp @@ -222,7 +222,7 @@ class MeshViewUtil , m_cdom(nullptr) , m_ctopology(nullptr) , m_ccoordset(nullptr) - {} + { } /*! @brief Construct view of a non-const domain. diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 4b33c024bd..949ac2b2c0 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -58,7 +58,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase , m_crossingParentIds(0, 0, m_allocatorID) , m_facetIncrs(0, 0, m_allocatorID) , m_firstFacetIds(0, 0, m_allocatorID) - {} + { } /*! @brief Initialize data to a blueprint domain. @@ -97,7 +97,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase only support column-major, so we're stuck with that for now. */ // m_caseIds.resize(m_bShape, 0); // This unexpectedly fails. - m_caseIds = axom::Array(m_bShape, m_allocatorID); + m_caseIds = + axom::Array(m_bShape, m_allocatorID); m_caseIds.fill(0); } @@ -262,14 +263,18 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase void scanCrossings() override { constexpr MarchingCubesDataParallelism autoPolicy = - std::is_same::value ? MarchingCubesDataParallelism::hybridParallel : + std::is_same::value + ? MarchingCubesDataParallelism::hybridParallel + : #ifdef AXOM_USE_OPENMP - std::is_same::value ? MarchingCubesDataParallelism::hybridParallel : + std::is_same::value + ? MarchingCubesDataParallelism::hybridParallel + : #endif - MarchingCubesDataParallelism::fullParallel; + MarchingCubesDataParallelism::fullParallel; if(m_dataParallelism == - axom::quest::MarchingCubesDataParallelism::hybridParallel || + axom::quest::MarchingCubesDataParallelism::hybridParallel || (m_dataParallelism == axom::quest::MarchingCubesDataParallelism::byPolicy && autoPolicy == MarchingCubesDataParallelism::hybridParallel)) { @@ -310,7 +315,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase 0, parentCellCount, AXOM_LAMBDA(axom::IndexType parentCellId) { - auto numContourCells = num_contour_cells(caseIdsView.flatIndex(parentCellId)); + auto numContourCells = + num_contour_cells(caseIdsView.flatIndex(parentCellId)); crossingFlagsView[parentCellId] = bool(numContourCells); }); @@ -336,9 +342,10 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase auto crossingParentIdsView = m_crossingParentIds.view(); auto facetIncrsView = m_facetIncrs.view(); - auto loopBody = AXOM_LAMBDA(axom::IndexType parentCellId) { - if(scannedFlagsView[parentCellId] != - scannedFlagsView[1+parentCellId]) { + auto loopBody = AXOM_LAMBDA(axom::IndexType parentCellId) + { + if(scannedFlagsView[parentCellId] != scannedFlagsView[1 + parentCellId]) + { auto crossingId = scannedFlagsView[parentCellId]; auto facetIncr = num_contour_cells(caseIdsView.flatIndex(parentCellId)); crossingParentIdsView[crossingId] = parentCellId; @@ -442,7 +449,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase const auto firstFacetIdsView = m_firstFacetIds.view(); #if defined(AXOM_USE_RAJA) - // Intel oneAPI compiler segfaults with OpenMP RAJA scan + // Intel oneAPI compiler segfaults with OpenMP RAJA scan #ifdef __INTEL_LLVM_COMPILER using ScanPolicy = typename axom::execution_space::loop_policy; @@ -635,13 +642,15 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase if(axom::utilities::isNearlyEqual(contourVal, f1) || axom::utilities::isNearlyEqual(f1, f2)) { - crossingPt[0] = p1[0]; crossingPt[1] = p1[1]; // crossingPt = p1; + crossingPt[0] = p1[0]; + crossingPt[1] = p1[1]; // crossingPt = p1; return; } if(axom::utilities::isNearlyEqual(contourVal, f2)) { - crossingPt[0] = p2[0]; crossingPt[1] = p2[1]; // crossingPt = p2; + crossingPt[0] = p2[0]; + crossingPt[1] = p2[1]; // crossingPt = p2; return; } @@ -688,13 +697,17 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase if(axom::utilities::isNearlyEqual(contourVal, f1) || axom::utilities::isNearlyEqual(f1, f2)) { - crossingPt[0] = p1[0]; crossingPt[1] = p1[1]; crossingPt[2] = p1[2]; // crossingPt = p1; + crossingPt[0] = p1[0]; + crossingPt[1] = p1[1]; + crossingPt[2] = p1[2]; // crossingPt = p1; return; } if(axom::utilities::isNearlyEqual(contourVal, f2)) { - crossingPt[0] = p2[0]; crossingPt[1] = p2[1]; crossingPt[2] = p2[2]; // crossingPt = p2; + crossingPt[0] = p2[0]; + crossingPt[1] = p2[1]; + crossingPt[2] = p2[2]; // crossingPt = p2; return; } @@ -775,8 +788,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase /*! @brief Constructor. */ - MarchingCubesImpl() - { } + MarchingCubesImpl() { } private: int m_allocatorID; @@ -819,7 +831,10 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::StackArray emptyShape() { axom::StackArray rval; - for(int d=0; d -newMarchingCubesImpl(MarchingCubes::RuntimePolicy runtimePolicy, int allocatorID, int dim) +newMarchingCubesImpl(MarchingCubes::RuntimePolicy runtimePolicy, + int allocatorID, + int dim) { using ImplBase = axom::quest::MarchingCubesSingleDomain::ImplBase; @@ -837,36 +854,44 @@ newMarchingCubesImpl(MarchingCubes::RuntimePolicy runtimePolicy, int allocatorID std::unique_ptr impl; if(runtimePolicy == MarchingCubes::RuntimePolicy::seq) { - impl = dim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::SEQ_EXEC, axom::SEQ_EXEC>(allocatorID)) - : std::unique_ptr( - new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>(allocatorID)); + impl = dim == 2 + ? std::unique_ptr( + new MarchingCubesImpl<2, axom::SEQ_EXEC, axom::SEQ_EXEC>(allocatorID)) + : std::unique_ptr( + new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>(allocatorID)); } #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP else if(runtimePolicy == MarchingCubes::RuntimePolicy::omp) { - impl = dim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::OMP_EXEC, axom::SEQ_EXEC>(allocatorID)) - : std::unique_ptr( - new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>(allocatorID)); + impl = dim == 2 + ? std::unique_ptr( + new MarchingCubesImpl<2, axom::OMP_EXEC, axom::SEQ_EXEC>(allocatorID)) + : std::unique_ptr( + new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>(allocatorID)); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_CUDA else if(runtimePolicy == MarchingCubes::RuntimePolicy::cuda) { - impl = dim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>(allocatorID)) - : std::unique_ptr( - new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>(allocatorID)); + impl = dim == 2 + ? std::unique_ptr( + new MarchingCubesImpl<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( + allocatorID)) + : std::unique_ptr( + new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( + allocatorID)); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_HIP else if(runtimePolicy == MarchingCubes::RuntimePolicy::hip) { - impl = dim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>(allocatorID)) - : std::unique_ptr( - new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>(allocatorID)); + impl = dim == 2 + ? std::unique_ptr( + new MarchingCubesImpl<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( + allocatorID)) + : std::unique_ptr( + new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( + allocatorID)); } #endif else diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 73164fbc10..8426c89d3b 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -745,10 +745,10 @@ struct ContourTestBase #endif // Create marching cubes algorithm object and set some parameters quest::MarchingCubes mc(params.policy, - s_allocatorId, - params.dataParallelism, - computationalMesh.asConduitNode(), - "mesh"); + s_allocatorId, + params.dataParallelism, + computationalMesh.asConduitNode(), + "mesh"); mc.setFunctionField(functionName()); @@ -1051,9 +1051,8 @@ struct ContourTestBase SLIC_ASSERT(numIdxComponents == 1); - axom::ArrayView view( - (const axom::IndexType*)ptr, - contourMesh.getNumberOfCells()); + axom::ArrayView view((const axom::IndexType*)ptr, + contourMesh.getNumberOfCells()); return view; } @@ -1093,7 +1092,7 @@ struct ContourTestBase } axom::Array> indexers(domainCount); - for(int d=0; d domShape; computationalMesh.domainLengths(d, domShape); @@ -1490,18 +1489,25 @@ int allocatorIdToTest(axom::runtime_policy::Policy policy) //--------------------------------------------------------------------------- // Memory resource. For testing, choose device memory if appropriate. //--------------------------------------------------------------------------- - int allocatorID = - policy == RuntimePolicy::seq ? axom::detail::getAllocatorID() : -#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - policy == RuntimePolicy::omp ? axom::detail::getAllocatorID() : -#endif -#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - policy == RuntimePolicy::cuda ? axom::detail::getAllocatorID() : -#endif -#if defined(AXOM_RUNTIME_POLICY_USE_HIP) - policy == RuntimePolicy::hip ? axom::detail::getAllocatorID() : -#endif - axom::INVALID_ALLOCATOR_ID; + int allocatorID = policy == RuntimePolicy::seq + ? axom::detail::getAllocatorID() + : + #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + policy == RuntimePolicy::omp + ? axom::detail::getAllocatorID() + : + #endif + #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + policy == RuntimePolicy::cuda + ? axom::detail::getAllocatorID() + : + #endif + #if defined(AXOM_RUNTIME_POLICY_USE_HIP) + policy == RuntimePolicy::hip + ? axom::detail::getAllocatorID() + : + #endif + axom::INVALID_ALLOCATOR_ID; #else int allocatorID = axom::getDefaultAllocatorID(); #endif @@ -1559,7 +1565,6 @@ void finalizeLogger() template int testNdimInstance(BlueprintStructuredMesh& computationalMesh) { - //--------------------------------------------------------------------------- // params specify which tests to run. //--------------------------------------------------------------------------- From 6159dd25dfa4adc3ac2bf860bab9d236e442374c Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 30 Jan 2024 13:39:40 -0800 Subject: [PATCH 11/61] Fix non-raja build. --- src/axom/quest/detail/MarchingCubesImpl.hpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 949ac2b2c0..776419b987 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -266,7 +266,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase std::is_same::value ? MarchingCubesDataParallelism::hybridParallel : -#ifdef AXOM_USE_OPENMP +#if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) std::is_same::value ? MarchingCubesDataParallelism::hybridParallel : @@ -322,10 +322,18 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::Array scannedFlags(1 + parentCellCount); auto scannedFlagsView = scannedFlags.view(); +#if defined(AXOM_USE_RAJA) RAJA::inclusive_scan( RAJA::make_span(crossingFlags.data(), parentCellCount), RAJA::make_span(scannedFlags.data() + 1, parentCellCount), RAJA::operators::plus {}); +#else + scannedFlags[0] = 0; + for(axom::IndexType n = 0; n < parentCellCount; ++n) + { + scannedFlags[n+1] = scannedFlags[n] + crossingFlags[n]; + } +#endif axom::copy(&m_crossingCount, scannedFlags.data() + scannedFlags.size() - 1, @@ -359,10 +367,18 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase // and the total number of facets. // +#if defined(AXOM_USE_RAJA) RAJA::inclusive_scan( RAJA::make_span(m_facetIncrs.data(), m_crossingCount), RAJA::make_span(m_firstFacetIds.data() + 1, m_crossingCount), RAJA::operators::plus {}); +#else + m_firstFacetIds[0] = 0; + for(axom::IndexType n = 0; n < parentCellCount; ++n) + { + m_firstFacetIds[n+1] = m_firstFacetIds[n] + m_facetIncrs[n]; + } +#endif axom::copy(&m_facetCount, m_firstFacetIds.data() + m_firstFacetIds.size() - 1, From bdd6005f4aa6b088512392131a61c94d9837f7d8 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 30 Jan 2024 19:16:47 -0800 Subject: [PATCH 12/61] Autoformat. --- src/axom/quest/detail/MarchingCubesImpl.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 776419b987..2aa40c6b68 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -331,7 +331,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase scannedFlags[0] = 0; for(axom::IndexType n = 0; n < parentCellCount; ++n) { - scannedFlags[n+1] = scannedFlags[n] + crossingFlags[n]; + scannedFlags[n + 1] = scannedFlags[n] + crossingFlags[n]; } #endif @@ -376,7 +376,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase m_firstFacetIds[0] = 0; for(axom::IndexType n = 0; n < parentCellCount; ++n) { - m_firstFacetIds[n+1] = m_firstFacetIds[n] + m_facetIncrs[n]; + m_firstFacetIds[n + 1] = m_firstFacetIds[n] + m_facetIncrs[n]; } #endif From e703128e116eae59138ec11c2bbe632cfb3b0c7c Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 30 Jan 2024 23:15:58 -0800 Subject: [PATCH 13/61] Fix inconsistent declaration of output domain id type. --- src/axom/quest/MarchingCubes.cpp | 36 +++++++++++++++++++------------- src/axom/quest/MarchingCubes.hpp | 5 +++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index e0568658d3..e046614bd6 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -123,23 +123,26 @@ axom::IndexType MarchingCubes::getContourNodeCount() const Domain ids are provided as a new Array instead of ArrayView because we don't store it internally. */ -axom::Array MarchingCubes::getContourFacetDomainIds( +axom::Array MarchingCubes::getContourFacetDomainIds( int allocatorID) const { // Put parent domain ids into a new Array. const axom::IndexType len = getContourCellCount(); - axom::Array rval( + axom::Array rval( len, len, allocatorID != axom::INVALID_ALLOCATOR_ID ? allocatorID : m_allocatorID); for(int d = 0; d < m_singles.size(); ++d) { - axom::detail::ArrayOps::fill( + MarchingCubes::DomainIdType domainId = m_singles[d]->getDomainId(d); + axom::IndexType contourCellCount = m_singles[d]->getContourCellCount(); + axom::IndexType offset = m_facetIndexOffsets[d]; + axom::detail::ArrayOps::fill( rval.data(), - m_facetIndexOffsets[d], - m_singles[d]->getContourCellCount(), - m_allocatorID, - m_singles[d]->getDomainId(d)); + offset, + contourCellCount, + allocatorID, + domainId); } return rval; } @@ -174,15 +177,18 @@ void MarchingCubes::populateContourMesh( // If data is not in host memory, copy to temporary host memory first. axom::MemorySpace internalMemorySpace = axom::detail::getAllocatorSpace(m_allocatorID); - bool copyToHost = internalMemorySpace != axom::MemorySpace::Dynamic + const bool hostAndInternalMemoriesAreSeparate = + internalMemorySpace != axom::MemorySpace::Dynamic #ifdef AXOM_USE_UMPIRE && internalMemorySpace != axom::MemorySpace::Host #endif ; - if(copyToHost) + const int hostAllocatorId = hostAndInternalMemoriesAreSeparate + ? axom::detail::getAllocatorID() + : m_allocatorID; + + if(hostAndInternalMemoriesAreSeparate) { - const int hostAllocatorId = - axom::detail::getAllocatorID(); axom::Array tmpfacetNodeCoords(m_facetNodeCoords, hostAllocatorId); axom::Array tmpfacetNodeIds(m_facetNodeIds, @@ -214,8 +220,7 @@ void MarchingCubes::populateContourMesh( auto* domainIdPtr = mesh.getFieldPtr(domainIdField, axom::mint::CELL_CENTERED); - auto tmpContourFacetDomainIds = getContourFacetDomainIds( - axom::execution_space::allocatorID()); + auto tmpContourFacetDomainIds = getContourFacetDomainIds(hostAllocatorId); axom::copy(domainIdPtr, tmpContourFacetDomainIds.data(), m_facetCount * sizeof(axom::IndexType)); @@ -298,9 +303,10 @@ void MarchingCubesSingleDomain::setDomain(const conduit::Node& dom) "MarchingCubes currently requires contiguous coordinates layout."); } -int MarchingCubesSingleDomain::getDomainId(int defaultId) const +MarchingCubes::DomainIdType MarchingCubesSingleDomain::getDomainId( + MarchingCubes::DomainIdType defaultId) const { - int rval = defaultId; + MarchingCubes::DomainIdType rval = defaultId; if(m_dom->has_path("state/domain_id")) { rval = m_dom->fetch_existing("state/domain_id").as_int(); diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index ba170eb01d..06e83bccf7 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -107,6 +107,7 @@ class MarchingCubes { public: using RuntimePolicy = axom::runtime_policy::Policy; + using DomainIdType = int; /*! * \brief Constructor sets up computational mesh and data for running the * marching cubes algorithm. @@ -236,7 +237,7 @@ class MarchingCubes Memory space of data corresponds to allocator set in the constructor. */ - axom::Array getContourFacetDomainIds( + axom::Array getContourFacetDomainIds( int allocatorID = axom::INVALID_ALLOCATOR_ID) const; #if 1 @@ -405,7 +406,7 @@ class MarchingCubesSingleDomain @brief Get the Blueprint domain id specified in \a state/domain_id if it is provided, or use the given default if not provided. */ - int getDomainId(int defaultId) const; + MarchingCubes::DomainIdType getDomainId(MarchingCubes::DomainIdType defaultId) const; //!@brief Get number of cells in the generated contour mesh. axom::IndexType getContourCellCount() const From ba20ad76f42d780630dc28d6748cda2785c0d658 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 31 Jan 2024 00:24:16 -0800 Subject: [PATCH 14/61] Fix more inconsistent domain id types. --- src/axom/quest/MarchingCubes.cpp | 7 ++-- .../examples/quest_marching_cubes_example.cpp | 34 ++++++++++++------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index e046614bd6..34adc863bf 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -162,7 +162,7 @@ void MarchingCubes::populateContourMesh( if(!domainIdField.empty() && !mesh.hasField(domainIdField, axom::mint::CELL_CENTERED)) { - mesh.createField(domainIdField, axom::mint::CELL_CENTERED); + mesh.createField(domainIdField, axom::mint::CELL_CENTERED); } // Reserve space once for all local domains. @@ -218,12 +218,11 @@ void MarchingCubes::populateContourMesh( { // Put parent domain ids into the mesh. auto* domainIdPtr = - mesh.getFieldPtr(domainIdField, - axom::mint::CELL_CENTERED); + mesh.getFieldPtr(domainIdField, axom::mint::CELL_CENTERED); auto tmpContourFacetDomainIds = getContourFacetDomainIds(hostAllocatorId); axom::copy(domainIdPtr, tmpContourFacetDomainIds.data(), - m_facetCount * sizeof(axom::IndexType)); + m_facetCount * sizeof(DomainIdType)); } } } diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 8426c89d3b..5364944278 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -1011,14 +1011,16 @@ struct ContourTestBase } //!@brief Get view of output domain id data. - axom::ArrayView getDomainIdView( + axom::ArrayView getDomainIdView( axom::mint::UnstructuredMesh& contourMesh) const { const auto* ptr = - contourMesh.getFieldPtr(m_domainIdField, - axom::mint::CELL_CENTERED); - axom::ArrayView view(ptr, - contourMesh.getNumberOfCells()); + contourMesh.getFieldPtr( + m_domainIdField, + axom::mint::CELL_CENTERED); + axom::ArrayView view( + ptr, + contourMesh.getNumberOfCells()); return view; } @@ -1079,11 +1081,13 @@ struct ContourTestBase allCoordsViews[n] = mvu.getConstCoordsViews(false); } - std::map domainIdToContiguousId; + std::map + domainIdToContiguousId; for(int n = 0; n < domainCount; ++n) { const auto& dom = computationalMesh.domain(n); - int domainId = n; + axom::quest::MarchingCubes::DomainIdType domainId = n; if(dom.has_path("state/domain_id")) { domainId = dom.fetch_existing("state/domain_id").value(); @@ -1102,8 +1106,10 @@ struct ContourTestBase for(axom::IndexType contourCellNum = 0; contourCellNum < cellCount; ++contourCellNum) { - axom::IndexType domainId = domainIdView[contourCellNum]; - axom::IndexType contiguousIndex = domainIdToContiguousId[domainId]; + axom::quest::MarchingCubes::DomainIdType domainId = + domainIdView[contourCellNum]; + axom::quest::MarchingCubes::DomainIdType contiguousIndex = + domainIdToContiguousId[domainId]; typename axom::quest::MeshViewUtil::ConstCoordsViewsType& coordsViews = allCoordsViews[contiguousIndex]; @@ -1193,10 +1199,10 @@ struct ContourTestBase } std::map domainIdToContiguousId; - for(int n = 0; n < domainCount; ++n) + for(axom::quest::MarchingCubes::DomainIdType n = 0; n < domainCount; ++n) { const auto& dom = computationalMesh.domain(n); - int domainId = n; + axom::quest::MarchingCubes::DomainIdType domainId = n; if(dom.has_path("state/domain_id")) { domainId = dom.fetch_existing("state/domain_id").value(); @@ -1207,8 +1213,10 @@ struct ContourTestBase for(axom::IndexType contourCellNum = 0; contourCellNum < cellCount; ++contourCellNum) { - axom::IndexType domainId = domainIdView[contourCellNum]; - axom::IndexType contiguousId = domainIdToContiguousId[domainId]; + axom::quest::MarchingCubes::DomainIdType domainId = + domainIdView[contourCellNum]; + axom::quest::MarchingCubes::DomainIdType contiguousId = + domainIdToContiguousId[domainId]; const axom::IndexType parentCellId = parentCellIdView[contourCellNum]; hasContours[contiguousId][parentCellId] = true; } From 6a152f1ad1fb582c01c5e4ee66c3351d6f4d307a Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 31 Jan 2024 14:26:58 -0800 Subject: [PATCH 15/61] Attempt to fix weird crash in Umpire. Make sure the correct overloaded Array functions are used. --- src/axom/quest/MarchingCubes.cpp | 18 ++++++------------ src/axom/quest/MarchingCubes.hpp | 2 +- src/axom/quest/detail/MarchingCubesImpl.hpp | 20 ++++++++++++-------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 34adc863bf..e01d298d89 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -13,7 +13,6 @@ #include "axom/core/execution/execution_space.hpp" #include "axom/quest/MarchingCubes.hpp" -// #include "axom/quest/detail/MarchingCubesHybridParallel.hpp" #include "axom/quest/detail/MarchingCubesImpl.hpp" #include "axom/fmt.hpp" @@ -99,16 +98,6 @@ void MarchingCubes::computeIsocontour(double contourVal) } } -axom::IndexType MarchingCubes::getContourCellCount() const -{ - axom::IndexType contourCellCount = 0; - for(int dId = 0; dId < m_singles.size(); ++dId) - { - contourCellCount += m_singles[dId]->getContourCellCount(); - } - return contourCellCount; -} - axom::IndexType MarchingCubes::getContourNodeCount() const { axom::IndexType contourNodeCount = 0; @@ -184,7 +173,12 @@ void MarchingCubes::populateContourMesh( #endif ; const int hostAllocatorId = hostAndInternalMemoriesAreSeparate - ? axom::detail::getAllocatorID() + ? +#ifdef AXOM_USE_UMPIRE + axom::detail::getAllocatorID() +#else + axom::detail::getAllocatorID() +#endif : m_allocatorID; if(hostAndInternalMemoriesAreSeparate) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 06e83bccf7..15313d026f 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -160,7 +160,7 @@ class MarchingCubes void computeIsocontour(double contourVal = 0.0); //!@brief Get number of cells in the generated contour mesh. - axom::IndexType getContourCellCount() const; + axom::IndexType getContourCellCount() const { return m_facetCount; } //!@brief Get number of nodes in the generated contour mesh. axom::IndexType getContourNodeCount() const; diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 2aa40c6b68..419a4fbf60 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -55,9 +55,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase AXOM_HOST MarchingCubesImpl(int allocatorID) : m_allocatorID(allocatorID) , m_caseIds(emptyShape(), m_allocatorID) - , m_crossingParentIds(0, 0, m_allocatorID) - , m_facetIncrs(0, 0, m_allocatorID) - , m_firstFacetIds(0, 0, m_allocatorID) + , m_crossingParentIds(axom::StackArray {0}, m_allocatorID) + , m_facetIncrs(axom::StackArray {0}, m_allocatorID) + , m_firstFacetIds(axom::StackArray {0}, m_allocatorID) { } /*! @@ -309,7 +309,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase // cell ids, regardless of the ordering of the input mesh data. // - axom::Array crossingFlags(parentCellCount); + axom::StackArray tmpShape {parentCellCount}; + axom::Array crossingFlags(tmpShape); auto crossingFlagsView = crossingFlags.view(); axom::for_all( 0, @@ -320,7 +321,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase crossingFlagsView[parentCellId] = bool(numContourCells); }); - axom::Array scannedFlags(1 + parentCellCount); + axom::StackArray tmpShape1 {1 + parentCellCount}; + axom::Array scannedFlags(tmpShape1); auto scannedFlagsView = scannedFlags.view(); #if defined(AXOM_USE_RAJA) RAJA::inclusive_scan( @@ -343,9 +345,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase // Generate crossing-cells index list and corresponding facet counts. // - m_crossingParentIds.resize(m_crossingCount); - m_facetIncrs.resize(m_crossingCount); - m_firstFacetIds.resize(1 + m_crossingCount); + const axom::StackArray tmpShape2 {m_crossingCount}; + const axom::StackArray tmpShape3 {1 + m_crossingCount}; + m_crossingParentIds.resize(tmpShape2, 0); + m_facetIncrs.resize(tmpShape2, 0); + m_firstFacetIds.resize(tmpShape3, 0); auto crossingParentIdsView = m_crossingParentIds.view(); auto facetIncrsView = m_facetIncrs.view(); From 06465cf4cffc82a14b30f0b049da2085efb74948 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 31 Jan 2024 17:49:35 -0800 Subject: [PATCH 16/61] Fix doxygen comments. --- src/axom/quest/MarchingCubes.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 15313d026f..2080f0248a 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -78,12 +78,13 @@ class MarchingCubesSingleDomain; * Usage example: * @beginverbatim * void foo( conduit::Node &meshNode, - * const std::string &coordsName, + * const std::string &topologyName, * const std::string &functionName, * double contourValue ) * { * MarchingCubes mc(axom::runtime_policy::Policy::seq, - * meshNode, coordsName); + * MarchingCubesDataParallelism::byPolicy, + * meshNode, topologyName); * mc.setFunctionField(functionName); * mc.computeIsocontour(contourValue); * axom::mint::UnstructuredMesh From 478df11480fd30940132132312cfafc53e8ed6d4 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 12 Feb 2024 17:39:28 -0800 Subject: [PATCH 17/61] Small comment and typo fixes. --- src/axom/quest/ArrayIndexer.hpp | 4 ++-- src/axom/quest/MarchingCubes.cpp | 10 ++++------ src/axom/quest/MarchingCubes.hpp | 13 +++++++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index f65bba5ee8..d8de41ba73 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -26,7 +26,7 @@ class ArrayIndexer public: /*! - @brief Constructor for row- or column major indexing. + @brief Constructor for row- or column-major indexing. @param [in] lengths Lengths of the array @param [in] order: c is column major; r is row major. */ @@ -42,7 +42,7 @@ class ArrayIndexer } /*! - @brief Initialize for row- or column major indexing. + @brief Initialize for row- or column-major indexing. @param [in] shape Shape of the array @param [in] order: c is column major; r is row major. */ diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index a459fda394..83952b838f 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -187,15 +187,13 @@ void MarchingCubes::populateContourMesh( hostAllocatorId); axom::Array tmpfacetNodeIds(m_facetNodeIds, hostAllocatorId); - mesh.appendNodes(tmpfacetNodeCoords.data(), - mesh.getDimension() * m_facetCount); - mesh.appendCells(tmpfacetNodeIds.data(), m_facetCount); + mesh.appendNodes(tmpfacetNodeCoords.data(), contourNodeCount); + mesh.appendCells(tmpfacetNodeIds.data(), contourCellCount); } else { - mesh.appendNodes(m_facetNodeCoords.data(), - mesh.getDimension() * m_facetCount); - mesh.appendCells(m_facetNodeIds.data(), m_facetCount); + mesh.appendNodes(m_facetNodeCoords.data(), contourNodeCount); + mesh.appendCells(m_facetNodeIds.data(), contourCellCount); } if(!cellIdField.empty()) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 491d6d4143..df977bc083 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -102,7 +102,7 @@ class MarchingCubesSingleDomain; * individual contour facets are provided. Blueprint allows users to * specify ids for the domains. If "state/domain_id" exists in the * domains, it is used as the domain id. Otherwise, the domain's - * interation index within the multidomain mesh is used. + * iteration index within the multidomain mesh is used. */ class MarchingCubes { @@ -172,10 +172,11 @@ class MarchingCubes @brief Put generated contour in a mint::UnstructuredMesh. @param mesh Output contour mesh @param cellIdField Name of field to store the array of - parent cells' multidimensional indices. + parent cells ids, numbered in column-major ordering. If empty, the data is not provided. - @param domainIdField Name of field to store the (axom::IndexType) - parent domain ids. If omitted, the data is not provided. + @param domainIdField Name of field to store the + parent domain ids. The type of this data is \c DomainIdType. + If omitted, the data is not provided. If the fields aren't in the mesh, they will be created. @@ -252,7 +253,7 @@ class MarchingCubes @pre isoContour() must have been called. @post outputs can no longer be accessed from object. */ - void relinguishContourData(axom::Array &facetNodeIds, + void relinquishContourData(axom::Array &facetNodeIds, axom::Array &facetNodeCoords, axom::Array &facetParentIds) { @@ -372,7 +373,7 @@ class MarchingCubesSingleDomain SLIC_ASSERT(m_dom->fetch_existing(m_fcnPath + "/association").as_string() == "vertex"); SLIC_ASSERT(m_dom->has_path(m_fcnPath + "/values")); - if(m_impl) m_impl->setFunctionField(fcnField); + m_impl->setFunctionField(fcnField); } void setContourValue(double contourVal) From 897bf1248e4cac50c4803fc01c2afc580e5541bb Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 12 Feb 2024 17:42:33 -0800 Subject: [PATCH 18/61] Fix uninitialized values in RAJA build. --- src/axom/quest/detail/MarchingCubesImpl.hpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 8a2dc55176..4fa7118770 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -323,14 +323,13 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::StackArray tmpShape1 {1 + parentCellCount}; axom::Array scannedFlags(tmpShape1); - auto scannedFlagsView = scannedFlags.view(); + scannedFlags.fill(0, 1, 0); #if defined(AXOM_USE_RAJA) RAJA::inclusive_scan( RAJA::make_span(crossingFlags.data(), parentCellCount), RAJA::make_span(scannedFlags.data() + 1, parentCellCount), RAJA::operators::plus {}); #else - scannedFlags[0] = 0; for(axom::IndexType n = 0; n < parentCellCount; ++n) { scannedFlags[n + 1] = scannedFlags[n] + crossingFlags[n]; @@ -351,6 +350,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase m_facetIncrs.resize(tmpShape2, 0); m_firstFacetIds.resize(tmpShape3, 0); + auto scannedFlagsView = scannedFlags.view(); auto crossingParentIdsView = m_crossingParentIds.view(); auto facetIncrsView = m_facetIncrs.view(); @@ -371,13 +371,13 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase // and the total number of facets. // + m_firstFacetIds.fill(0, 1, 0); #if defined(AXOM_USE_RAJA) RAJA::inclusive_scan( RAJA::make_span(m_facetIncrs.data(), m_crossingCount), RAJA::make_span(m_firstFacetIds.data() + 1, m_crossingCount), RAJA::operators::plus {}); #else - m_firstFacetIds[0] = 0; for(axom::IndexType n = 0; n < parentCellCount; ++n) { m_firstFacetIds[n + 1] = m_firstFacetIds[n] + m_facetIncrs[n]; @@ -465,9 +465,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::deallocate(crossingId); - // axom::Array prefixSum(m_crossingCount, m_crossingCount); - const auto firstFacetIdsView = m_firstFacetIds.view(); + m_firstFacetIds.fill(0, 1, 0); + const auto firstFacetIdsView = m_firstFacetIds.view(); #if defined(AXOM_USE_RAJA) // Intel oneAPI compiler segfaults with OpenMP RAJA scan #ifdef __INTEL_LLVM_COMPILER @@ -481,13 +481,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase RAJA::make_span(firstFacetIdsView.data() + 1, m_crossingCount), RAJA::operators::plus {}); #else - if(m_crossingCount > 0) + for(axom::IndexType i = 1; i < 1 + m_crossingCount; ++i) { - firstFacetIdsView[0] = 0; - for(axom::IndexType i = 1; i < 1 + m_crossingCount; ++i) - { - firstFacetIdsView[i] = firstFacetIdsView[i - 1] + facetIncrsView[i - 1]; - } + firstFacetIdsView[i] = firstFacetIdsView[i - 1] + facetIncrsView[i - 1]; } #endif axom::copy(&m_facetCount, From 6b810a48b622187c184f4a2d35e6b2d638f045fa Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 12 Feb 2024 22:07:15 -0800 Subject: [PATCH 19/61] Construct ArrayIndexer from order permutation. --- src/axom/quest/ArrayIndexer.hpp | 72 ++++++++++++++++++-- src/axom/quest/tests/quest_array_indexer.cpp | 8 ++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index d8de41ba73..62ffb29e37 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -27,12 +27,25 @@ class ArrayIndexer public: /*! @brief Constructor for row- or column-major indexing. - @param [in] lengths Lengths of the array + @param [in] shape Shape of the array @param [in] order: c is column major; r is row major. */ - ArrayIndexer(const axom::StackArray& lengths, char order) + ArrayIndexer(const axom::StackArray& Shape, char order) + { + initialize(Shape, order); + } + + /*! + @brief Constructor for a given order permutation. + @param [in] shape Shape of the array + @param [in] slowestDirs: permutation vector, where + slowestDirs[0] is the slowest direction and + slowestDirs[DIM-1] is the fastest. + */ + ArrayIndexer(const axom::StackArray& shape, + const axom::StackArray& slowestDirs) { - initialize(lengths, order); + initialize(shape, slowestDirs); } //!@brief Constructor for arbitrary-stride indexing. @@ -74,7 +87,29 @@ class ArrayIndexer m_strides[d] = m_strides[d + 1] * shape[d + 1]; } } - SLIC_ASSERT((DIM == 1 && getOrder() == ('r' | 's')) || (getOrder() == order)); + SLIC_ASSERT((DIM == 1 && getOrder() == ('r' | 'c')) || (getOrder() == order)); + } + + /*! + @brief Initialize for a given order permutation. + @param [in] shape Shape of the array + @param [in] slowestDirs: permutation vector, where + slowestDirs[0] is the slowest direction and + slowestDirs[DIM-1] is the fastest. + */ + inline AXOM_HOST_DEVICE void initialize( + const axom::StackArray& shape, + const axom::StackArray& slowestDirs) + { + SLIC_ASSERT(isPermutation(slowestDirs)); + m_slowestDirs = slowestDirs; + m_strides[m_slowestDirs[DIM - 1]] = 1; + for(int d = DIM - 2; d >= 0; --d) + { + int dir = m_slowestDirs[d]; + int fasterDir = m_slowestDirs[d + 1]; + m_strides[dir] = m_strides[fasterDir] * shape[fasterDir]; + } } //!@brief Initialize for arbitrary-stride indexing. @@ -100,6 +135,11 @@ class ArrayIndexer } } + bool operator==(const ArrayIndexer& other) const + { + return m_slowestDirs == other.m_slowestDirs && m_strides == other.m_strides; + } + //!@brief Index directions, ordered from slowest to fastest. inline AXOM_HOST_DEVICE const axom::StackArray& slowestDirs() const { @@ -112,6 +152,30 @@ class ArrayIndexer return m_strides; } + //!@brief Whether a vector is a permutation vector + bool isPermutation(const axom::StackArray& v) + { + // v is a permutation if all its values are unique and in [0, DIM). + axom::StackArray found; + for(int d = 0; d < DIM; ++d) + { + found[d] = false; + } + for(int d = 0; d < DIM; ++d) + { + if(v[d] < 0 || v[d] >= DIM) + { + return false; + } // Out of range. + if(found[v[d]] == true) + { + return false; + } // Repeated indices + found[v[d]] = true; + } + return true; + } + /*! @brief Get the stride order (row- or column-major, or something else). diff --git a/src/axom/quest/tests/quest_array_indexer.cpp b/src/axom/quest/tests/quest_array_indexer.cpp index 7702560b5d..f805574f50 100644 --- a/src/axom/quest/tests/quest_array_indexer.cpp +++ b/src/axom/quest/tests/quest_array_indexer.cpp @@ -12,7 +12,7 @@ #include "gtest/gtest.h" // Test strides and permutations. -TEST(quest_array_indexer, quest_strides_and_permutatations) +TEST(quest_array_indexer, quest_strides_and_permutations) { { // 1D @@ -28,6 +28,9 @@ TEST(quest_array_indexer, quest_strides_and_permutatations) EXPECT_EQ(colIndexer.slowestDirs()[d], colSlowestDirs[d]); EXPECT_EQ(colIndexer.strides()[d], colStrides[d]); } + EXPECT_TRUE( + colIndexer == + (axom::ArrayIndexer(lengths, colSlowestDirs))); axom::ArrayIndexer rowIndexer(lengths, 'r'); EXPECT_EQ(rowIndexer.getOrder(), 'c' | 'r'); @@ -38,6 +41,9 @@ TEST(quest_array_indexer, quest_strides_and_permutatations) EXPECT_EQ(rowIndexer.slowestDirs()[d], rowSlowestDirs[d]); EXPECT_EQ(rowIndexer.strides()[d], rowStrides[d]); } + EXPECT_TRUE( + rowIndexer == + (axom::ArrayIndexer(lengths, rowSlowestDirs))); } { // 2D From 8a6f288e9779133f545bb38ab253d3d2f13bb006 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 14 Feb 2024 12:28:46 -0800 Subject: [PATCH 20/61] ArrayIndexer handles non-unique strides more robustly. --- src/axom/quest/ArrayIndexer.hpp | 146 +++++++++++++++--- .../examples/quest_marching_cubes_example.cpp | 24 ++- src/axom/quest/tests/quest_array_indexer.cpp | 79 ++++++---- 3 files changed, 194 insertions(+), 55 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index 62ffb29e37..0cefe603d3 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -10,8 +10,19 @@ #include "axom/core/StackArray.hpp" #include "axom/core/numerics/matvecops.hpp" +#include +#include + namespace axom { +struct ArrayStrideOrder +{ + static constexpr int NEITHER = 0; // Neither row nor column + static constexpr int ROW = 1; // Row-major + static constexpr int COLUMN = 2; // Column-major + static constexpr int BOTH = ROW | COLUMN; // Only for 1D arrays +}; + /*! @brief Indexing into a multidimensional structured array. @@ -30,9 +41,9 @@ class ArrayIndexer @param [in] shape Shape of the array @param [in] order: c is column major; r is row major. */ - ArrayIndexer(const axom::StackArray& Shape, char order) + ArrayIndexer(const axom::StackArray& shape, int order) { - initialize(Shape, order); + initializeShape(shape, order); } /*! @@ -45,25 +56,43 @@ class ArrayIndexer ArrayIndexer(const axom::StackArray& shape, const axom::StackArray& slowestDirs) { - initialize(shape, slowestDirs); + initializeShape(shape, slowestDirs); } - //!@brief Constructor for arbitrary-stride indexing. + /*! + @brief Constructor for arbitrary-stride indexing. + + @param [i] strides Strides. Must be unique when DIM > 1. + If not unique, use default constructor and initializeStrides(). + + @internal We could add the order preference to this constructor to + handle the degenerate case of non-unique strides. But that would + clash with the more prevalent constructor taking the array's + shape. + */ ArrayIndexer(const axom::StackArray& strides) : m_strides(strides) { - initialize(strides); + initializeStrides(strides); } + /*! + @brief Default constructor + + Object must be initialized before use. + */ + ArrayIndexer() = default; + /*! @brief Initialize for row- or column-major indexing. @param [in] shape Shape of the array @param [in] order: c is column major; r is row major. */ - inline AXOM_HOST_DEVICE void initialize(const axom::StackArray& shape, - char order) + inline AXOM_HOST_DEVICE void initializeShape(const axom::StackArray& shape, + int order) { - SLIC_ASSERT(order == 'c' || order == 'r'); - if(order == 'r') + SLIC_ASSERT(order == ArrayStrideOrder::COLUMN || + order == ArrayStrideOrder::ROW); + if(order == ArrayStrideOrder::ROW) { for(int d = 0; d < DIM; ++d) { @@ -87,7 +116,8 @@ class ArrayIndexer m_strides[d] = m_strides[d + 1] * shape[d + 1]; } } - SLIC_ASSERT((DIM == 1 && getOrder() == ('r' | 'c')) || (getOrder() == order)); + SLIC_ASSERT((DIM == 1 && getStrideOrder() == ArrayStrideOrder::BOTH) || + (getStrideOrder() == order)); } /*! @@ -97,7 +127,7 @@ class ArrayIndexer slowestDirs[0] is the slowest direction and slowestDirs[DIM-1] is the fastest. */ - inline AXOM_HOST_DEVICE void initialize( + inline AXOM_HOST_DEVICE void initializeShape( const axom::StackArray& shape, const axom::StackArray& slowestDirs) { @@ -112,13 +142,57 @@ class ArrayIndexer } } - //!@brief Initialize for arbitrary-stride indexing. - inline AXOM_HOST_DEVICE void initialize(const axom::StackArray& strides) + /*! + @brief Initialize for arbitrary-stride indexing. + + @param [i] strides Strides. Must be unique when DIM > 1. + If not satisfied, you must use one of the other initializers. + */ + inline AXOM_HOST_DEVICE void initializeStrides( + const axom::StackArray& strides) + { + if(DIM > 1 && !stridesAreUnique(strides)) + { +#if !defined(AXOM_DEVICE_CODE) + std::ostringstream os; + os << "("; + for(int d = 0; d < DIM - 1; ++d) + { + os << strides[d] << ","; + } + os << strides[DIM - 1] << ")"; + std::cerr << "ERROR: ArrayIndexer: Non-unique strides " << os.str() << ".\n" + << "Likely, multi-dim array shape is 1 in some direction.\n" + << "Impossible to compute index ordering.\n" + << "Please use a different ArrayIndexer initializer.\n"; +#endif + utilities::processAbort(); + } + + // 2nd argument doesn't matter because strides are unique. + initializeStrides(strides, axom::ArrayStrideOrder::COLUMN); + } + + /*! + @brief Initialize for arbitrary-stride indexing, + with ordering preference for non-unique strides. + + @param [i] strides Strides. + @param [i] orderPref Ordering preference if strides + are non-unique. + */ + inline AXOM_HOST_DEVICE void initializeStrides( + const axom::StackArray& strides, + int orderPref) { + SLIC_ASSERT(orderPref == axom::ArrayStrideOrder::COLUMN || + orderPref == axom::ArrayStrideOrder::ROW); + m_strides = strides; for(int d = 0; d < DIM; ++d) { - m_slowestDirs[d] = d; + m_slowestDirs[d] = + orderPref == axom::ArrayStrideOrder::COLUMN ? d : DIM - 1 - d; } for(int s = 0; s < DIM; ++s) { @@ -135,7 +209,20 @@ class ArrayIndexer } } - bool operator==(const ArrayIndexer& other) const + static AXOM_HOST_DEVICE bool stridesAreUnique(const axom::StackArray& strides) + { + bool repeats = false; + for(int d = 0; d < DIM; ++d) + { + for(int e = 0; e < d; ++e) + { + repeats |= strides[d] == strides[e]; + } + } + return !repeats; + } + + inline AXOM_HOST_DEVICE bool operator==(const ArrayIndexer& other) const { return m_slowestDirs == other.m_slowestDirs && m_strides == other.m_strides; } @@ -179,17 +266,18 @@ class ArrayIndexer /*! @brief Get the stride order (row- or column-major, or something else). - @return 'r' or 'c' for row- or column major, '\0' for neither, - or if DIM == 1, the value of 'r' | 'c'. + @return 1 if row major, 2 if column major, 0 if neither + and, DIM == 1, 3 (satisfying both row and column ordering). */ - inline AXOM_HOST_DEVICE char getOrder() const + inline AXOM_HOST_DEVICE int getStrideOrder() const { - char order = 'r' | 'c'; + int ord = ArrayStrideOrder::BOTH; for(int d = 0; d < DIM - 1; ++d) { - order &= m_slowestDirs[d] < m_slowestDirs[d + 1] ? 'c' : 'r'; + ord &= m_slowestDirs[d] < m_slowestDirs[d + 1] ? ArrayStrideOrder::COLUMN + : ArrayStrideOrder::ROW; } - return order; + return ord; } //!@brief Convert multidimensional index to flat index. @@ -211,6 +299,22 @@ class ArrayIndexer } return multiIndex; } + + friend std::ostream& operator<<(std::ostream& os, const ArrayIndexer& a) + { + os << "ArrayIndexer: strides=(" << a.m_strides[0]; + for(int d = 1; d < DIM; ++d) + { + os << ',' << a.m_strides[d]; + } + os << ") slowestDirs=(" << a.m_slowestDirs[0]; + for(int d = 1; d < DIM; ++d) + { + os << ',' << a.m_slowestDirs[d]; + } + os << ')'; + return os; + } }; } // end namespace axom diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 48120b2a1e..299a2f516f 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -1100,7 +1100,7 @@ struct ContourTestBase { axom::StackArray domShape; computationalMesh.domainLengths(d, domShape); - indexers[d].initialize(domShape, 'c'); + indexers[d].initializeShape(domShape, int(axom::ArrayStrideOrder::COLUMN)); } for(axom::IndexType contourCellNum = 0; contourCellNum < cellCount; @@ -1145,8 +1145,8 @@ struct ContourTestBase ++errCount; SLIC_INFO_IF( params.isVerbose(), - axom::fmt::format("checkContourCellLimits: node {} at {} is not " - "on parent cell boundary.", + axom::fmt::format("checkContourCellLimits: node {} at {} " + "is not on parent cell boundary.", cellNodeIds[nn], nodeCoords)); } @@ -1154,8 +1154,8 @@ struct ContourTestBase } SLIC_INFO_IF(params.isVerbose(), - axom::fmt::format("checkContourCellLimits: found {} nodes " - "not on parent cell boundary.", + axom::fmt::format("checkContourCellLimits: found {} " + "nodes not on parent cell boundary.", errCount)); return errCount; } @@ -1179,10 +1179,13 @@ struct ContourTestBase /* Space to store function views and whether a computational cell contains the contour. We set these up for all domains ahead - of time for accessing as needed later. + of time for accessing as needed later. cellIndexers are for + converting between flat and multidim indices. */ axom::Array> fcnViews( domainCount); + axom::Array> cellIndexers( + domainCount); axom::Array> hasContours(domainCount); for(axom::IndexType domId = 0; domId < domainCount; ++domId) { @@ -1196,6 +1199,9 @@ struct ContourTestBase fcnViews[domId] = mvu.template getConstFieldView(functionName(), false); + cellIndexers[domId].initializeShape( + mvu.getCellShape(), + axom::ArrayStrideOrder::COLUMN); } std::map domainIdToContiguousId; @@ -1234,12 +1240,13 @@ struct ContourTestBase const auto& fcnView = fcnViews[domId]; - axom::ArrayIndexer colMajor(domLengths, 'c'); + const axom::ArrayIndexer& cellIndexer = + cellIndexers[domId]; const axom::IndexType cellCount = product(domLengths); for(axom::IndexType cellId = 0; cellId < cellCount; ++cellId) { axom::StackArray cellIdx = - colMajor.toMultiIndex(cellId); + cellIndexer.toMultiIndex(cellId); // Compute min and max function value in the cell. double minFcnValue = std::numeric_limits::max(); @@ -1248,6 +1255,7 @@ struct ContourTestBase (1 << DIM); // Number of nodes in a cell. for(short int cornerId = 0; cornerId < cornerCount; ++cornerId) { + // Compute multidim index of current corner of parent cell. axom::StackArray cornerIdx = cellIdx; for(int d = 0; d < DIM; ++d) { diff --git a/src/axom/quest/tests/quest_array_indexer.cpp b/src/axom/quest/tests/quest_array_indexer.cpp index f805574f50..4e3b3b4172 100644 --- a/src/axom/quest/tests/quest_array_indexer.cpp +++ b/src/axom/quest/tests/quest_array_indexer.cpp @@ -19,8 +19,10 @@ TEST(quest_array_indexer, quest_strides_and_permutations) constexpr int DIM = 1; axom::StackArray lengths {2}; - axom::ArrayIndexer colIndexer(lengths, 'c'); - EXPECT_EQ(colIndexer.getOrder(), 'c' | 'r'); + axom::ArrayIndexer colIndexer( + lengths, + int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); axom::StackArray colSlowestDirs {0}; axom::StackArray colStrides {1}; for(int d = 0; d < DIM; ++d) @@ -32,8 +34,10 @@ TEST(quest_array_indexer, quest_strides_and_permutations) colIndexer == (axom::ArrayIndexer(lengths, colSlowestDirs))); - axom::ArrayIndexer rowIndexer(lengths, 'r'); - EXPECT_EQ(rowIndexer.getOrder(), 'c' | 'r'); + axom::ArrayIndexer rowIndexer( + lengths, + int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); axom::StackArray rowSlowestDirs {0}; axom::StackArray rowStrides {1}; for(int d = 0; d < DIM; ++d) @@ -50,8 +54,10 @@ TEST(quest_array_indexer, quest_strides_and_permutations) constexpr int DIM = 2; axom::StackArray lengths {3, 2}; - axom::ArrayIndexer colIndexer(lengths, 'c'); - EXPECT_EQ(colIndexer.getOrder(), 'c'); + axom::ArrayIndexer colIndexer( + lengths, + int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::StackArray colSlowestDirs {0, 1}; axom::StackArray colStrides {2, 1}; for(int d = 0; d < DIM; ++d) @@ -60,8 +66,10 @@ TEST(quest_array_indexer, quest_strides_and_permutations) EXPECT_EQ(colIndexer.strides()[d], colStrides[d]); } - axom::ArrayIndexer rowIndexer(lengths, 'r'); - EXPECT_EQ(rowIndexer.getOrder(), 'r'); + axom::ArrayIndexer rowIndexer( + lengths, + int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::StackArray rowSlowestDirs {1, 0}; axom::StackArray rowStrides {1, 3}; for(int d = 0; d < DIM; ++d) @@ -75,8 +83,10 @@ TEST(quest_array_indexer, quest_strides_and_permutations) constexpr int DIM = 3; axom::StackArray lengths {5, 3, 2}; - axom::ArrayIndexer colIndexer(lengths, 'c'); - EXPECT_EQ(colIndexer.getOrder(), 'c'); + axom::ArrayIndexer colIndexer( + lengths, + int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::StackArray colSlowestDirs {0, 1, 2}; axom::StackArray colStrides {6, 2, 1}; for(int d = 0; d < DIM; ++d) @@ -85,8 +95,10 @@ TEST(quest_array_indexer, quest_strides_and_permutations) EXPECT_EQ(colIndexer.strides()[d], colStrides[d]); } - axom::ArrayIndexer rowIndexer(lengths, 'r'); - EXPECT_EQ(rowIndexer.getOrder(), 'r'); + axom::ArrayIndexer rowIndexer( + lengths, + int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::StackArray rowSlowestDirs {2, 1, 0}; axom::StackArray rowStrides {1, 5, 15}; for(int d = 0; d < DIM; ++d) @@ -104,8 +116,11 @@ TEST(quest_array_indexer, quest_col_major_offset) // 1D constexpr int DIM = 1; axom::StackArray lengths {3}; - axom::ArrayIndexer ai(lengths, 'c'); - EXPECT_EQ(ai.getOrder(), 'c' | 'r'); + axom::ArrayIndexer ai( + lengths, + int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(ai.getStrideOrder(), + int(axom::ArrayStrideOrder::COLUMN) | axom::ArrayStrideOrder::ROW); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -119,8 +134,10 @@ TEST(quest_array_indexer, quest_col_major_offset) // 2D constexpr int DIM = 2; axom::StackArray lengths {3, 2}; - axom::ArrayIndexer ai(lengths, 'c'); - EXPECT_EQ(ai.getOrder(), 'c'); + axom::ArrayIndexer ai( + lengths, + int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -136,8 +153,10 @@ TEST(quest_array_indexer, quest_col_major_offset) { // 3D axom::StackArray lengths {5, 3, 2}; - axom::ArrayIndexer ai(lengths, 'c'); - EXPECT_EQ(ai.getOrder(), 'c'); + axom::ArrayIndexer ai( + lengths, + int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -162,8 +181,10 @@ TEST(quest_array_indexer, quest_row_major_offset) // 1D constexpr int DIM = 1; axom::StackArray lengths {3}; - axom::ArrayIndexer ai(lengths, 'r'); - EXPECT_EQ(ai.getOrder(), 'r' | 'c'); + axom::ArrayIndexer ai(lengths, + int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(ai.getStrideOrder(), + int(axom::ArrayStrideOrder::ROW) | axom::ArrayStrideOrder::COLUMN); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -177,8 +198,9 @@ TEST(quest_array_indexer, quest_row_major_offset) // 2D constexpr int DIM = 2; axom::StackArray lengths {3, 2}; - axom::ArrayIndexer ai(lengths, 'r'); - EXPECT_EQ(ai.getOrder(), 'r'); + axom::ArrayIndexer ai(lengths, + int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::IndexType offset = 0; for(int j = 0; j < lengths[1]; ++j) { @@ -195,8 +217,9 @@ TEST(quest_array_indexer, quest_row_major_offset) // 3D constexpr int DIM = 3; axom::StackArray lengths {5, 3, 2}; - axom::ArrayIndexer ai(lengths, 'r'); - EXPECT_EQ(ai.getOrder(), 'r'); + axom::ArrayIndexer ai(lengths, + int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::IndexType offset = 0; for(int k = 0; k < lengths[2]; ++k) { @@ -384,7 +407,9 @@ TEST(quest_array_indexer, quest_array_match) constexpr int DIM = 2; axom::StackArray lengths {3, 2}; axom::Array a(lengths); - axom::ArrayIndexer ai(lengths, 'c'); + axom::ArrayIndexer ai( + lengths, + int(axom::ArrayStrideOrder::COLUMN)); for(axom::IndexType i = 0; i < lengths[0]; ++i) { for(axom::IndexType j = 0; j < lengths[1]; ++j) @@ -398,7 +423,9 @@ TEST(quest_array_indexer, quest_array_match) constexpr int DIM = 3; axom::StackArray lengths {5, 3, 2}; axom::Array a(lengths); - axom::ArrayIndexer ai(lengths, 'c'); + axom::ArrayIndexer ai( + lengths, + int(axom::ArrayStrideOrder::COLUMN)); for(axom::IndexType i = 0; i < lengths[0]; ++i) { for(axom::IndexType j = 0; j < lengths[1]; ++j) From a70c71aedd56bc890c2e2f96911ca7ef7c1b299b Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 14 Feb 2024 23:50:10 -0800 Subject: [PATCH 21/61] Silence compiler warning. --- src/axom/quest/detail/MarchingCubesImpl.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 4fa7118770..806c527fcf 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -743,8 +743,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase // to put static 1D and 2D arrays on both host and device? BTNG. template - AXOM_HOST_DEVICE inline typename std::enable_if::type - num_contour_cells(int iCase) const + static AXOM_HOST_DEVICE inline typename std::enable_if::type + num_contour_cells(int iCase) { #define _MC_LOOKUP_NUM_SEGMENTS #include "marching_cubes_lookup.hpp" @@ -754,8 +754,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase } template - AXOM_HOST_DEVICE inline typename std::enable_if::type - cases_table(int iCase, int iEdge) const + static AXOM_HOST_DEVICE inline typename std::enable_if::type + cases_table(int iCase, int iEdge) { #define _MC_LOOKUP_CASES2D #include "marching_cubes_lookup.hpp" @@ -765,8 +765,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase } template - AXOM_HOST_DEVICE inline typename std::enable_if::type - num_contour_cells(int iCase) const + static AXOM_HOST_DEVICE inline typename std::enable_if::type + num_contour_cells(int iCase) { #define _MC_LOOKUP_NUM_TRIANGLES #include "marching_cubes_lookup.hpp" @@ -776,8 +776,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase } template - AXOM_HOST_DEVICE inline typename std::enable_if::type - cases_table(int iCase, int iEdge) const + static AXOM_HOST_DEVICE inline typename std::enable_if::type + cases_table(int iCase, int iEdge) { #define _MC_LOOKUP_CASES3D #include "marching_cubes_lookup.hpp" From eb74d9d52b37a65f2bd2b2b77efecdec5e804916 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 14 Feb 2024 23:50:45 -0800 Subject: [PATCH 22/61] Add option to change multidimensional nested loop ordering. Ordering is hardwired for now. --- src/axom/quest/detail/MarchingCubesImpl.hpp | 85 +++++++++++++++++---- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 806c527fcf..be080ed056 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -130,22 +130,47 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { MarkCrossings_Util mcu(m_caseIds, m_fcnView, m_maskView, m_contourVal); + auto order = axom::ArrayStrideOrder::ROW; #if defined(AXOM_USE_RAJA) RAJA::RangeSegment jRange(0, m_bShape[1]); RAJA::RangeSegment iRange(0, m_bShape[0]); using EXEC_POL = typename axom::mint::internal::structured_exec::loop2d_policy; - RAJA::kernel( - RAJA::make_tuple(iRange, jRange), - AXOM_LAMBDA(axom::IndexType i, axom::IndexType j) { - mcu.computeCaseId(i, j); - }); + if(order & axom::ArrayStrideOrder::ROW) + { + RAJA::kernel( + RAJA::make_tuple(iRange, jRange), + AXOM_LAMBDA(axom::IndexType i, axom::IndexType j) { + mcu.computeCaseId(i, j); + }); + } + else + { + RAJA::kernel( + RAJA::make_tuple(jRange, iRange), + AXOM_LAMBDA(axom::IndexType j, axom::IndexType i) { + mcu.computeCaseId(i, j); + }); + } #else - for(int j = 0; j < m_bShape[1]; ++j) + if(order & axom::ArrayStrideOrder::ROW) + { + for(int j = 0; j < m_bShape[1]; ++j) + { + for(int i = 0; i < m_bShape[0]; ++i) + { + mcu.computeCaseId(i, j); + } + } + } + else { for(int i = 0; i < m_bShape[0]; ++i) { - mcu.computeCaseId(i, j); + for(int j = 0; j < m_bShape[1]; ++j) + { + mcu.computeCaseId(i, j); + } } } #endif @@ -157,25 +182,53 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { MarkCrossings_Util mcu(m_caseIds, m_fcnView, m_maskView, m_contourVal); + auto order = axom::ArrayStrideOrder::COLUMN; #if defined(AXOM_USE_RAJA) RAJA::RangeSegment kRange(0, m_bShape[2]); RAJA::RangeSegment jRange(0, m_bShape[1]); RAJA::RangeSegment iRange(0, m_bShape[0]); using EXEC_POL = typename axom::mint::internal::structured_exec::loop3d_policy; - RAJA::kernel( - RAJA::make_tuple(iRange, jRange, kRange), - AXOM_LAMBDA(axom::IndexType i, axom::IndexType j, axom::IndexType k) { - mcu.computeCaseId(i, j, k); - }); + if(order & axom::ArrayStrideOrder::ROW) + { + RAJA::kernel( + RAJA::make_tuple(iRange, jRange, kRange), + AXOM_LAMBDA(axom::IndexType i, axom::IndexType j, axom::IndexType k) { + mcu.computeCaseId(i, j, k); + }); + } + else + { + RAJA::kernel( + RAJA::make_tuple(kRange, jRange, iRange), + AXOM_LAMBDA(axom::IndexType k, axom::IndexType j, axom::IndexType i) { + mcu.computeCaseId(i, j, k); + }); + } #else - for(int k = 0; k < m_bShape[2]; ++k) + if(order & axom::ArrayStrideOrder::ROW) { - for(int j = 0; j < m_bShape[1]; ++j) + for(int k = 0; k < m_bShape[2]; ++k) { - for(int i = 0; i < m_bShape[0]; ++i) + for(int j = 0; j < m_bShape[1]; ++j) { - mcu.computeCaseId(i, j, k); + for(int i = 0; i < m_bShape[0]; ++i) + { + mcu.computeCaseId(i, j, k); + } + } + } + } + else + { + for(int i = 0; i < m_bShape[0]; ++i) + { + for(int j = 0; j < m_bShape[1]; ++j) + { + for(int k = 0; k < m_bShape[2]; ++k) + { + mcu.computeCaseId(i, j, k); + } } } } From 0a8b135ed85e3ddf1a608a84bccf8e87d0bd31fb Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 15 Feb 2024 08:06:24 -0800 Subject: [PATCH 23/61] Set caseIds and multidim loop ordering to input data ordering to improve cache use. --- src/axom/quest/MarchingCubes.hpp | 3 +- src/axom/quest/detail/MarchingCubesImpl.hpp | 74 ++++++++++++------- .../examples/quest_marching_cubes_example.cpp | 9 ++- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index df977bc083..8379039778 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -172,7 +172,8 @@ class MarchingCubes @brief Put generated contour in a mint::UnstructuredMesh. @param mesh Output contour mesh @param cellIdField Name of field to store the array of - parent cells ids, numbered in column-major ordering. + parent cells ids, numbered in the row- or column-major + ordering of the nodal scalar function. If empty, the data is not provided. @param domainIdField Name of field to store the parent domain ids. The type of this data is \c DomainIdType. diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index be080ed056..5e56461dab 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -12,6 +12,7 @@ #include "conduit_blueprint.hpp" #include "axom/core/execution/execution_space.hpp" +#include "axom/slic/interface/slic_macros.hpp" #include "axom/quest/ArrayIndexer.hpp" #include "axom/quest/MeshViewUtil.hpp" #include "axom/primal/geometry/Point.hpp" @@ -45,6 +46,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase public: using Point = axom::primal::Point; using MIdx = axom::StackArray; + using Indexer = axom::ArrayIndexer; using FacetIdType = int; using LoopPolicy = typename execution_space::loop_policy; using ReducePolicy = typename execution_space::reduce_policy; @@ -54,7 +56,6 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase AXOM_HOST MarchingCubesImpl(int allocatorID) : m_allocatorID(allocatorID) - , m_caseIds(emptyShape(), m_allocatorID) , m_crossingParentIds(axom::StackArray {0}, m_allocatorID) , m_facetIncrs(axom::StackArray {0}, m_allocatorID) , m_firstFacetIds(axom::StackArray {0}, m_allocatorID) @@ -80,8 +81,6 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase SLIC_ASSERT(conduit::blueprint::mesh::topology::dims(dom.fetch_existing( axom::fmt::format("topologies/{}", topologyName))) == DIM); - // clear(); - m_mvu = axom::quest::MeshViewUtil(dom, topologyName); m_bShape = m_mvu.getCellShape(); @@ -91,15 +90,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase m_maskView = m_mvu.template getConstFieldView(maskFieldName, false); } - /* - TODO: To get good cache performance, we should make m_caseIds - row-major if fcn is that way, and vice versa. However, Array - only support column-major, so we're stuck with that for now. - */ - // m_caseIds.resize(m_bShape, 0); // This unexpectedly fails. - m_caseIds = - axom::Array(m_bShape, m_allocatorID); - m_caseIds.fill(0); + // I would like to move this section to markCrossings() but it + // increases runtime to 10x on HIP and 1.3x on CUDA for some reason. + m_caseIdsFlat = + axom::Array(m_mvu.getCellCount(), + m_allocatorID); } /*! @@ -122,7 +117,22 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase Virtual methods cannot be templated, so this implementation delegates to an implementation templated on DIM. */ - void markCrossings() override { markCrossings_dim(); } + void markCrossings() override + { + m_caseIdsFlat.fill(0); + Indexer fcnIndexer(m_fcnView.strides()); + // Choose caseIds stride order to match function stride order. + m_caseIdsIndexer.initializeShape(m_bShape, fcnIndexer.slowestDirs()); + m_caseIds = axom::ArrayView( + m_caseIdsFlat.data(), + m_bShape, + m_caseIdsIndexer.strides()); + SLIC_ASSERT_MSG(Indexer(m_caseIds.strides()).getStrideOrder() == + fcnIndexer.getStrideOrder(), + "Mismatched order is inefficient."); + + markCrossings_dim(); + } //!@brief Populate m_caseIds with crossing indices. template @@ -130,7 +140,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { MarkCrossings_Util mcu(m_caseIds, m_fcnView, m_maskView, m_contourVal); - auto order = axom::ArrayStrideOrder::ROW; + auto order = m_caseIdsIndexer.getStrideOrder(); + // order ^= axom::ArrayStrideOrder::BOTH; // Pick wrong ordering to test behavior. #if defined(AXOM_USE_RAJA) RAJA::RangeSegment jRange(0, m_bShape[1]); RAJA::RangeSegment iRange(0, m_bShape[0]); @@ -182,7 +193,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { MarkCrossings_Util mcu(m_caseIds, m_fcnView, m_maskView, m_contourVal); - auto order = axom::ArrayStrideOrder::COLUMN; + auto order = m_caseIdsIndexer.getStrideOrder(); + // order ^= axom::ArrayStrideOrder::BOTH; // Pick wrong ordering to test behavior. #if defined(AXOM_USE_RAJA) RAJA::RangeSegment kRange(0, m_bShape[2]); RAJA::RangeSegment jRange(0, m_bShape[1]); @@ -246,11 +258,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::ArrayView fcnView; axom::ArrayView maskView; double contourVal; - MarkCrossings_Util(axom::Array& caseIds, + MarkCrossings_Util(axom::ArrayView& caseIds, axom::ArrayView& fcnView_, axom::ArrayView& maskView_, double contourVal_) - : caseIdsView(caseIds.view()) + : caseIdsView(caseIds) , fcnView(fcnView_) , maskView(maskView_) , contourVal(contourVal_) @@ -351,7 +363,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase #endif #endif const axom::IndexType parentCellCount = m_caseIds.size(); - auto caseIdsView = m_caseIds.view(); + auto caseIdsView = m_caseIds; // // Initialize crossingFlags to 0 or 1, prefix-sum it, and count the @@ -449,7 +461,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase // Compute number of crossings in m_caseIds // const axom::IndexType parentCellCount = m_caseIds.size(); - auto caseIdsView = m_caseIds.view(); + auto caseIdsView = m_caseIds; #if defined(AXOM_USE_RAJA) RAJA::ReduceSum vsum(0); RAJA::forall( @@ -550,7 +562,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase const auto facetIncrsView = m_facetIncrs.view(); const auto firstFacetIdsView = m_firstFacetIds.view(); const auto crossingParentIdsView = m_crossingParentIds.view(); - const auto caseIdsView = m_caseIds.view(); + const auto caseIdsView = m_caseIds; // Internal contour mesh data to populate axom::ArrayView facetNodeIdsView = m_facetNodeIds; @@ -559,7 +571,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase const axom::IndexType facetIndexOffset = m_facetIndexOffset; ComputeContour_Util ccu(m_contourVal, - m_caseIds.strides(), + m_caseIdsIndexer, m_fcnView, m_coordsViews); @@ -606,19 +618,17 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase struct ComputeContour_Util { double contourVal; - MIdx bStrides; axom::ArrayIndexer indexer; axom::ArrayView fcnView; axom::StackArray, DIM> coordsViews; ComputeContour_Util( double contourVal_, - const MIdx& bStrides_, + const axom::ArrayIndexer& parentIndexer_, const axom::ArrayView& fcnView_, const axom::StackArray, DIM> coordsViews_) : contourVal(contourVal_) - , bStrides(bStrides_) - , indexer(bStrides_) + , indexer(parentIndexer_) , fcnView(fcnView_) , coordsViews(coordsViews_) { } @@ -860,7 +870,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase MarchingCubesImpl() { } private: - int m_allocatorID; + const int m_allocatorID; axom::quest::MeshViewUtil m_mvu; MIdx m_bShape; //!< @brief Blueprint cell data shape. @@ -874,7 +884,17 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::ArrayView m_maskView; //!@brief Crossing case for each computational mesh cell. - axom::Array m_caseIds; + axom::Array m_caseIdsFlat; + axom::ArrayView m_caseIds; + /*! + @brief Multi-dim indexer to control m_caseIdsFlat data ordering. + + We want caseIds ordering to match m_fcnView, but Array + only supports column-major ordering currently. To control + the order, we put caseIds in a 1D array and construct a + multidim view with the ordering we want. + */ + axom::ArrayIndexer m_caseIdsIndexer; //!@brief Number of parent cells crossing the contour surface. axom::IndexType m_crossingCount = 0; diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 299a2f516f..975025f03c 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -1095,12 +1095,16 @@ struct ContourTestBase domainIdToContiguousId[domainId] = n; } + // Indexers to transltate between flat and multidim indices. axom::Array> indexers(domainCount); for(int d = 0; d < domainCount; ++d) { axom::StackArray domShape; computationalMesh.domainLengths(d, domShape); - indexers[d].initializeShape(domShape, int(axom::ArrayStrideOrder::COLUMN)); + indexers[d].initializeShape( + domShape, + axom::ArrayIndexer(allCoordsViews[d][0].strides()) + .slowestDirs()); } for(axom::IndexType contourCellNum = 0; contourCellNum < cellCount; @@ -1201,7 +1205,8 @@ struct ContourTestBase mvu.template getConstFieldView(functionName(), false); cellIndexers[domId].initializeShape( mvu.getCellShape(), - axom::ArrayStrideOrder::COLUMN); + axom::ArrayIndexer(fcnViews[domId].strides()) + .slowestDirs()); } std::map domainIdToContiguousId; From bbb84441149de1af5ee3adc582f055bbf53f6ab8 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sat, 17 Feb 2024 00:35:18 -0800 Subject: [PATCH 24/61] Optimize out unneeded cuda memory allocation/deallocation. --- src/axom/quest/MarchingCubes.cpp | 39 ++- src/axom/quest/MarchingCubes.hpp | 28 ++- src/axom/quest/detail/MarchingCubesImpl.hpp | 229 ++++++++++-------- .../examples/quest_marching_cubes_example.cpp | 5 + 4 files changed, 182 insertions(+), 119 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 83952b838f..bbe2dea7f5 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -20,6 +20,8 @@ namespace axom { namespace quest { +const axom::StackArray twoZeros{0, 0}; + MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, int allocatorID, MarchingCubesDataParallelism dataParallelism, @@ -35,6 +37,11 @@ MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, , m_fcnPath() , m_maskFieldName(maskField) , m_maskPath(maskField.empty() ? std::string() : "fields/" + maskField) + , m_facetIndexOffsets(0, 0) + , m_facetCount(0) + , m_facetNodeIds(twoZeros, m_allocatorID) + , m_facetNodeCoords(twoZeros, m_allocatorID) + , m_facetParentIds(0, 0, m_allocatorID) { SLIC_ASSERT_MSG( conduit::blueprint::mesh::is_multi_domain(bpMesh), @@ -64,6 +71,7 @@ void MarchingCubes::setFunctionField(const std::string& fcnField) void MarchingCubes::computeIsocontour(double contourVal) { + AXOM_PERF_MARK_FUNCTION("MarchingCubes::computeIsoContour"); // Mark and scan domains while adding up their // facet counts to get the total facet counts. m_facetIndexOffsets.resize(m_singles.size()); @@ -86,10 +94,10 @@ void MarchingCubes::computeIsocontour(double contourVal) auto facetParentIdsView = m_facetParentIds.view(); for(axom::IndexType d = 0; d < m_singles.size(); ++d) { - m_singles[d]->setOutputBuffers(facetNodeIdsView, - facetNodeCoordsView, - facetParentIdsView, - m_facetIndexOffsets[d]); + m_singles[d]->getImpl().setOutputBuffers(facetNodeIdsView, + facetNodeCoordsView, + facetParentIdsView, + m_facetIndexOffsets[d]); } for(axom::IndexType d = 0; d < m_singles.size(); ++d) @@ -136,11 +144,23 @@ axom::Array MarchingCubes::getContourFacetDomainIds return rval; } +void MarchingCubes::clear() +{ + for(int d = 0; d < m_singles.size(); ++d) + { + m_singles[d]->getImpl().clear(); + } + m_facetNodeIds.clear(); + m_facetNodeCoords.clear(); + m_facetParentIds.clear(); +} + void MarchingCubes::populateContourMesh( axom::mint::UnstructuredMesh& mesh, const std::string& cellIdField, const std::string& domainIdField) const { + AXOM_PERF_MARK_FUNCTION("MarchingCubes::populateContourMesh"); if(!cellIdField.empty() && !mesh.hasField(cellIdField, axom::mint::CELL_CENTERED)) { @@ -157,6 +177,8 @@ void MarchingCubes::populateContourMesh( // Reserve space once for all local domains. const axom::IndexType contourCellCount = getContourCellCount(); const axom::IndexType contourNodeCount = getContourNodeCount(); + // Temporarily disable reservation due to unknown bug. + // See https://github.com/LLNL/axom/pull/1271 mesh.reserveCells(contourCellCount); mesh.reserveNodes(contourNodeCount); @@ -221,15 +243,14 @@ void MarchingCubes::populateContourMesh( void MarchingCubes::allocateOutputBuffers() { + AXOM_PERF_MARK_FUNCTION("MarchingCubes::allocateOutputBuffers"); if(!m_singles.empty()) { int ndim = m_singles[0]->spatialDimension(); const auto nodeCount = m_facetCount * ndim; - m_facetNodeIds = - axom::Array({m_facetCount, ndim}, m_allocatorID); - m_facetNodeCoords = axom::Array({nodeCount, ndim}, m_allocatorID); - axom::StackArray t1 {m_facetCount}; - m_facetParentIds = axom::Array(t1, m_allocatorID); + m_facetNodeIds.resize(axom::StackArray{m_facetCount, ndim}, 0); + m_facetNodeCoords.resize(axom::StackArray{nodeCount, ndim}, 0.0); + m_facetParentIds.resize(axom::StackArray{m_facetCount}, 0); } } diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 8379039778..91b9fbefa9 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -268,6 +268,14 @@ class MarchingCubes #endif //@} + /*! + @brief Clear computed data (without deallocating memory). + + After clearing, you can choose another field, contour value and + recompute the contour. You cannot change the mesh. + */ + void clear(); + private: RuntimePolicy m_runtimePolicy; int m_allocatorID = axom::INVALID_ALLOCATOR_ID; @@ -390,19 +398,6 @@ class MarchingCubesSingleDomain { return m_impl->getContourCellCount(); } - void setOutputBuffers(axom::ArrayView &facetNodeIds, - axom::ArrayView &facetNodeCoords, - axom::ArrayView &facetParentIds, - axom::IndexType facetIndexOffset) - { - SLIC_ASSERT(facetNodeIds.getAllocatorID() == m_allocatorID); - SLIC_ASSERT(facetNodeCoords.getAllocatorID() == m_allocatorID); - SLIC_ASSERT(facetParentIds.getAllocatorID() == m_allocatorID); - m_impl->setOutputBuffers(facetNodeIds, - facetNodeCoords, - facetParentIds, - facetIndexOffset); - } void computeContour() { m_impl->computeContour(); } /*! @@ -487,6 +482,8 @@ class MarchingCubesSingleDomain virtual ~ImplBase() { } + virtual void clear() = 0; + MarchingCubesDataParallelism m_dataParallelism = MarchingCubesDataParallelism::byPolicy; @@ -497,6 +494,11 @@ class MarchingCubesSingleDomain axom::IndexType m_facetIndexOffset = -1; }; + ImplBase& getImpl() + { + return *m_impl; + } + private: RuntimePolicy m_runtimePolicy; int m_allocatorID = axom::INVALID_ALLOCATOR_ID; diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 5e56461dab..6e091ea4c3 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -50,15 +50,28 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase using FacetIdType = int; using LoopPolicy = typename execution_space::loop_policy; using ReducePolicy = typename execution_space::reduce_policy; +#if defined(AXOM_USE_RAJA) + // Intel oneAPI compiler segfaults with OpenMP RAJA scan + #ifdef __INTEL_LLVM_COMPILER + using ScanPolicy = + typename axom::execution_space::loop_policy; + #else + using ScanPolicy = typename axom::execution_space::loop_policy; + #endif +#endif using SequentialLoopPolicy = typename execution_space::loop_policy; static constexpr auto MemorySpace = execution_space::memory_space; AXOM_HOST MarchingCubesImpl(int allocatorID) : m_allocatorID(allocatorID) - , m_crossingParentIds(axom::StackArray {0}, m_allocatorID) - , m_facetIncrs(axom::StackArray {0}, m_allocatorID) - , m_firstFacetIds(axom::StackArray {0}, m_allocatorID) + , m_caseIdsFlat(0, 0, m_allocatorID) + , m_caseIds() + , m_crossingFlags(0, 0, m_allocatorID) + , m_scannedFlags(0, 0, m_allocatorID) + , m_crossingParentIds(0, 0, m_allocatorID) + , m_facetIncrs(0, 0, m_allocatorID) + , m_firstFacetIds(0, 0, m_allocatorID) { } /*! @@ -78,6 +91,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase const std::string& topologyName, const std::string& maskFieldName) override { + // Time this due to potentially slow memory allocation + AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::initialize"); + SLIC_ASSERT(conduit::blueprint::mesh::topology::dims(dom.fetch_existing( axom::fmt::format("topologies/{}", topologyName))) == DIM); @@ -89,12 +105,6 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { m_maskView = m_mvu.template getConstFieldView(maskFieldName, false); } - - // I would like to move this section to markCrossings() but it - // increases runtime to 10x on HIP and 1.3x on CUDA for some reason. - m_caseIdsFlat = - axom::Array(m_mvu.getCellCount(), - m_allocatorID); } /*! @@ -119,9 +129,13 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase */ void markCrossings() override { + AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::markCrossings"); + + m_caseIdsFlat.resize(m_mvu.getCellCount(), 0); m_caseIdsFlat.fill(0); - Indexer fcnIndexer(m_fcnView.strides()); + // Choose caseIds stride order to match function stride order. + Indexer fcnIndexer(m_fcnView.strides()); m_caseIdsIndexer.initializeShape(m_bShape, fcnIndexer.slowestDirs()); m_caseIds = axom::ArrayView( m_caseIdsFlat.data(), @@ -291,11 +305,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase if(useZone) { // clang-format off - double nodalValues[CELL_CORNER_COUNT] = - {fcnView(i , j ), - fcnView(i + 1, j ), - fcnView(i + 1, j + 1), - fcnView(i , j + 1)}; + double nodalValues[CELL_CORNER_COUNT] = + {fcnView(i , j ), + fcnView(i + 1, j ), + fcnView(i + 1, j + 1), + fcnView(i , j + 1)}; // clang-format on caseIdsView(i, j) = computeCrossingCase(nodalValues); } @@ -310,15 +324,15 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase if(useZone) { // clang-format off - double nodalValues[CELL_CORNER_COUNT] = - {fcnView(i + 1, j , k ), - fcnView(i + 1, j + 1, k ), - fcnView(i , j + 1, k ), - fcnView(i , j , k ), - fcnView(i + 1, j , k + 1), - fcnView(i + 1, j + 1, k + 1), - fcnView(i , j + 1, k + 1), - fcnView(i , j , k + 1)}; + double nodalValues[CELL_CORNER_COUNT] = + {fcnView(i + 1, j , k ), + fcnView(i + 1, j + 1, k ), + fcnView(i , j + 1, k ), + fcnView(i , j , k ), + fcnView(i + 1, j , k + 1), + fcnView(i + 1, j + 1, k + 1), + fcnView(i , j + 1, k + 1), + fcnView(i , j , k + 1)}; // clang-format on caseIdsView(i, j, k) = computeCrossingCase(nodalValues); } @@ -327,6 +341,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase void scanCrossings() override { + AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::scanCrossings"); constexpr MarchingCubesDataParallelism autoPolicy = std::is_same::value ? MarchingCubesDataParallelism::hybridParallel @@ -343,93 +358,92 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase (m_dataParallelism == axom::quest::MarchingCubesDataParallelism::byPolicy && autoPolicy == MarchingCubesDataParallelism::hybridParallel)) { - scanCrossings_hybridParallel(); + AXOM_PERF_MARK_SECTION( + "MarchingCubesImpl::scanCrossings:hybridParallel", + scanCrossings_hybridParallel();); } else { - scanCrossings_fullParallel(); + AXOM_PERF_MARK_SECTION( + "MarchingCubesImpl::scanCrossings:fullParallel", + scanCrossings_fullParallel();); } } + void allocateIndexLists() + { + AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::allocateIndexLists"); + m_crossingParentIds.resize(m_crossingCount, 0); + m_facetIncrs.resize(m_crossingCount, 0); + m_firstFacetIds.resize(1 + m_crossingCount, 0); + } + void scanCrossings_fullParallel() { -#if defined(AXOM_USE_RAJA) - #ifdef __INTEL_LLVM_COMPILER - // Intel oneAPI compiler segfaults with OpenMP RAJA scan - using ScanPolicy = - typename axom::execution_space::loop_policy; - #else - using ScanPolicy = typename axom::execution_space::loop_policy; - #endif -#endif const axom::IndexType parentCellCount = m_caseIds.size(); auto caseIdsView = m_caseIds; // - // Initialize crossingFlags to 0 or 1, prefix-sum it, and count the + // Initialize m_crossingFlags, prefix-sum it, and count the // crossings. // - // All 1D array data alligns with m_caseId, which is column-major - // (the default for Array class), leading to *column-major* parent - // cell ids, regardless of the ordering of the input mesh data. - // - - axom::StackArray tmpShape {parentCellCount}; - axom::Array crossingFlags(tmpShape); - auto crossingFlagsView = crossingFlags.view(); - axom::for_all( - 0, - parentCellCount, - AXOM_LAMBDA(axom::IndexType parentCellId) { - auto numContourCells = - num_contour_cells(caseIdsView.flatIndex(parentCellId)); - crossingFlagsView[parentCellId] = bool(numContourCells); - }); - axom::StackArray tmpShape1 {1 + parentCellCount}; - axom::Array scannedFlags(tmpShape1); - scannedFlags.fill(0, 1, 0); + m_crossingFlags.resize(m_mvu.getCellCount(), 0); + m_scannedFlags.resize(1 + m_mvu.getCellCount(), 0); + + auto crossingFlagsView = m_crossingFlags.view(); + AXOM_PERF_MARK_SECTION( + "MarchingCubesImpl::scanCrossings:set_flags", + axom::for_all( + 0, + parentCellCount, + AXOM_LAMBDA(axom::IndexType parentCellId) { + auto numContourCells = + num_contour_cells(caseIdsView.flatIndex(parentCellId)); + crossingFlagsView[parentCellId] = bool(numContourCells); + });); + + m_scannedFlags.fill(0, 1, 0); + AXOM_PERF_MARK_SECTION( + "MarchingCubesImpl::scanCrossings:scan_flags", #if defined(AXOM_USE_RAJA) - RAJA::inclusive_scan( - RAJA::make_span(crossingFlags.data(), parentCellCount), - RAJA::make_span(scannedFlags.data() + 1, parentCellCount), - RAJA::operators::plus {}); + RAJA::inclusive_scan( + RAJA::make_span(m_crossingFlags.data(), parentCellCount), + RAJA::make_span(m_scannedFlags.data() + 1, parentCellCount), + RAJA::operators::plus {}); #else - for(axom::IndexType n = 0; n < parentCellCount; ++n) - { - scannedFlags[n + 1] = scannedFlags[n] + crossingFlags[n]; - } + for(axom::IndexType n = 0; n < parentCellCount; ++n) + { + m_scannedFlags[n + 1] = m_scannedFlags[n] + m_crossingFlags[n]; + } #endif + ); axom::copy(&m_crossingCount, - scannedFlags.data() + scannedFlags.size() - 1, + m_scannedFlags.data() + m_scannedFlags.size() - 1, sizeof(axom::IndexType)); // // Generate crossing-cells index list and corresponding facet counts. // - - const axom::StackArray tmpShape2 {m_crossingCount}; - const axom::StackArray tmpShape3 {1 + m_crossingCount}; - m_crossingParentIds.resize(tmpShape2, 0); - m_facetIncrs.resize(tmpShape2, 0); - m_firstFacetIds.resize(tmpShape3, 0); - - auto scannedFlagsView = scannedFlags.view(); + allocateIndexLists(); + auto scannedFlagsView = m_scannedFlags.view(); auto crossingParentIdsView = m_crossingParentIds.view(); auto facetIncrsView = m_facetIncrs.view(); - auto loopBody = AXOM_LAMBDA(axom::IndexType parentCellId) - { - if(scannedFlagsView[parentCellId] != scannedFlagsView[1 + parentCellId]) + AXOM_PERF_MARK_SECTION( + "MarchingCubesImpl::scanCrossings:set_incrs", + auto loopBody = AXOM_LAMBDA(axom::IndexType parentCellId) { - auto crossingId = scannedFlagsView[parentCellId]; - auto facetIncr = num_contour_cells(caseIdsView.flatIndex(parentCellId)); - crossingParentIdsView[crossingId] = parentCellId; - facetIncrsView[crossingId] = facetIncr; - } - }; - axom::for_all(0, parentCellCount, loopBody); + if(scannedFlagsView[parentCellId] != scannedFlagsView[1 + parentCellId]) + { + auto crossingId = scannedFlagsView[parentCellId]; + auto facetIncr = num_contour_cells(caseIdsView.flatIndex(parentCellId)); + crossingParentIdsView[crossingId] = parentCellId; + facetIncrsView[crossingId] = facetIncr; + } + }; + axom::for_all(0, parentCellCount, loopBody);); // // Prefix-sum the facets counts to get first facet id for each crossing @@ -437,17 +451,20 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase // m_firstFacetIds.fill(0, 1, 0); + AXOM_PERF_MARK_SECTION( + "MarchingCubesImpl::scanCrossings:scan_incrs", #if defined(AXOM_USE_RAJA) - RAJA::inclusive_scan( - RAJA::make_span(m_facetIncrs.data(), m_crossingCount), - RAJA::make_span(m_firstFacetIds.data() + 1, m_crossingCount), - RAJA::operators::plus {}); + RAJA::inclusive_scan( + RAJA::make_span(m_facetIncrs.data(), m_crossingCount), + RAJA::make_span(m_firstFacetIds.data() + 1, m_crossingCount), + RAJA::operators::plus {}); #else - for(axom::IndexType n = 0; n < parentCellCount; ++n) - { - m_firstFacetIds[n + 1] = m_firstFacetIds[n] + m_facetIncrs[n]; - } + for(axom::IndexType n = 0; n < parentCellCount; ++n) + { + m_firstFacetIds[n + 1] = m_firstFacetIds[n] + m_facetIncrs[n]; + } #endif + ); axom::copy(&m_facetCount, m_firstFacetIds.data() + m_firstFacetIds.size() - 1, @@ -534,13 +551,6 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase const auto firstFacetIdsView = m_firstFacetIds.view(); #if defined(AXOM_USE_RAJA) - // Intel oneAPI compiler segfaults with OpenMP RAJA scan - #ifdef __INTEL_LLVM_COMPILER - using ScanPolicy = - typename axom::execution_space::loop_policy; - #else - using ScanPolicy = typename axom::execution_space::loop_policy; - #endif RAJA::inclusive_scan( RAJA::make_span(facetIncrsView.data(), m_crossingCount), RAJA::make_span(firstFacetIdsView.data() + 1, m_crossingCount), @@ -559,6 +569,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase void computeContour() override { + AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::computeContour"); const auto facetIncrsView = m_facetIncrs.view(); const auto firstFacetIdsView = m_firstFacetIds.view(); const auto crossingParentIdsView = m_crossingParentIds.view(); @@ -869,6 +880,24 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase */ MarchingCubesImpl() { } + /*! + @brief Clear computed data (without deallocating memory). + + After clearing, you can change the field, contour value + and recompute the contour. + */ + void clear() override + { + m_caseIdsFlat.clear(); + m_crossingFlags.clear(); + m_scannedFlags.clear(); + m_crossingParentIds.clear(); + m_facetIncrs.clear(); + m_firstFacetIds.clear(); + m_crossingCount = 0; + m_facetCount = 0; + } + private: const int m_allocatorID; @@ -896,6 +925,12 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase */ axom::ArrayIndexer m_caseIdsIndexer; + //!@brief Whether a parent cell crosses the contour. + axom::Array m_crossingFlags; + + //!@brief Prefix sum of m_crossingFlags + axom::Array m_scannedFlags; + //!@brief Number of parent cells crossing the contour surface. axom::IndexType m_crossingCount = 0; diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 975025f03c..4388588761 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -515,6 +515,7 @@ struct BlueprintStructuredMesh template void moveMeshDataToNewMemorySpace(const std::string& path, int allocId) { + AXOM_PERF_MARK_FUNCTION("moveMeshDataToNewMemorySpace"); // For reference for(auto& dom : _mdMesh.children()) { moveConduitDataToNewMemorySpace(dom, path, allocId); @@ -758,6 +759,10 @@ struct ContourTestBase #endif computeTimer.start(); mc.computeIsocontour(params.contourVal); + for(int i=0; i<4; ++i) { + mc.clear(); + mc.computeIsocontour(params.contourVal); + } computeTimer.stop(); printTimingStats(computeTimer, name() + " contour"); From a13bc1f1e0835ce44aac4e1931c0f92d0cd4e5f7 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sat, 17 Feb 2024 18:54:04 -0800 Subject: [PATCH 25/61] Allow MarchingCubes to retain allocated memory when initialzed to a different mesh. --- src/axom/quest/MarchingCubes.cpp | 91 +++++++---- src/axom/quest/MarchingCubes.hpp | 142 ++++++++++-------- .../examples/quest_marching_cubes_example.cpp | 17 ++- 3 files changed, 150 insertions(+), 100 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index bbe2dea7f5..22f3e07482 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -24,39 +24,62 @@ const axom::StackArray twoZeros{0, 0}; MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, int allocatorID, - MarchingCubesDataParallelism dataParallelism, - const conduit::Node& bpMesh, - const std::string& topologyName, - const std::string& maskField) + MarchingCubesDataParallelism dataParallelism) : m_runtimePolicy(runtimePolicy) , m_allocatorID(allocatorID) , m_dataParallelism(dataParallelism) , m_singles() - , m_topologyName(topologyName) + , m_topologyName() , m_fcnFieldName() , m_fcnPath() - , m_maskFieldName(maskField) - , m_maskPath(maskField.empty() ? std::string() : "fields/" + maskField) + , m_maskFieldName() + , m_maskPath() , m_facetIndexOffsets(0, 0) , m_facetCount(0) , m_facetNodeIds(twoZeros, m_allocatorID) , m_facetNodeCoords(twoZeros, m_allocatorID) , m_facetParentIds(0, 0, m_allocatorID) +{ +} + +// Set the object up for a blueprint mesh state. +void MarchingCubes::initialize( + const conduit::Node& bpMesh, + const std::string& topologyName, + const std::string& maskField) { SLIC_ASSERT_MSG( conduit::blueprint::mesh::is_multi_domain(bpMesh), "MarchingCubes class input mesh must be in multidomain format."); - m_singles.reserve(conduit::blueprint::mesh::number_of_domains(bpMesh)); - for(auto& dom : bpMesh.children()) + clear(); + + m_topologyName = topologyName; + m_maskFieldName = maskField; + + /* + To avoid slow memory allocations (especially on GPUs) keep the + single-domain objects around and just re-initialize them. Arrays + will be cleared, but not deallocated. To really deallocate + memory, deallocate the MarchingCubes object. The actual number + of domains is m_domainCount, not m_singles.size(). + */ + auto newDomainCount = conduit::blueprint::mesh::number_of_domains(bpMesh); + + while( m_singles.size() < newDomainCount ) { m_singles.emplace_back(new MarchingCubesSingleDomain(m_runtimePolicy, m_allocatorID, - m_dataParallelism, - dom, - m_topologyName, - maskField)); + m_dataParallelism)); + } + + for (int d = 0; d < newDomainCount; ++d) + { + const auto& dom = bpMesh.child(d); + m_singles[d]->initialize(dom, m_topologyName, maskField); } + + m_domainCount = newDomainCount; } void MarchingCubes::setFunctionField(const std::string& fcnField) @@ -150,6 +173,7 @@ void MarchingCubes::clear() { m_singles[d]->getImpl().clear(); } + m_domainCount = 0; m_facetNodeIds.clear(); m_facetNodeCoords.clear(); m_facetParentIds.clear(); @@ -257,36 +281,28 @@ void MarchingCubes::allocateOutputBuffers() MarchingCubesSingleDomain::MarchingCubesSingleDomain( RuntimePolicy runtimePolicy, int allocatorID, - MarchingCubesDataParallelism dataPar, - const conduit::Node& dom, - const std::string& topologyName, - const std::string& maskField) + MarchingCubesDataParallelism dataPar) : m_runtimePolicy(runtimePolicy) , m_allocatorID(allocatorID) , m_dataParallelism(dataPar) , m_dom(nullptr) , m_ndim(0) - , m_topologyName(topologyName) + , m_topologyName() , m_fcnFieldName() , m_fcnPath() - , m_maskFieldName(maskField) - , m_maskPath(maskField.empty() ? std::string() : "fields/" + maskField) + , m_maskFieldName() + , m_maskPath() { - // Set domain first, to get m_ndim, which is required to allocate m_impl. - setDomain(dom); - - m_impl = - axom::quest::detail::marching_cubes::newMarchingCubesImpl(m_runtimePolicy, - m_allocatorID, - m_ndim); - - m_impl->initialize(*m_dom, m_topologyName, m_maskFieldName); - m_impl->setDataParallelism(m_dataParallelism); return; } -void MarchingCubesSingleDomain::setDomain(const conduit::Node& dom) +void MarchingCubesSingleDomain::initialize( + const conduit::Node& dom, + const std::string& topologyName, + const std::string& maskField) { + m_topologyName = topologyName; + SLIC_ASSERT_MSG( !conduit::blueprint::mesh::is_multi_domain(dom), "MarchingCubesSingleDomain is single-domain only. Try MarchingCubes."); @@ -300,8 +316,13 @@ void MarchingCubesSingleDomain::setDomain(const conduit::Node& dom) if(!m_maskPath.empty()) { + m_maskPath = maskField.empty() ? std::string() : "fields/" + maskField; SLIC_ASSERT(dom.has_path(m_maskPath + "/values")); } + else + { + m_maskPath.clear(); + } m_dom = &dom; @@ -313,6 +334,14 @@ void MarchingCubesSingleDomain::setDomain(const conduit::Node& dom) !conduit::blueprint::mcarray::is_interleaved( dom.fetch_existing(coordsetPath + "/values")), "MarchingCubes currently requires contiguous coordinates layout."); + + m_impl = + axom::quest::detail::marching_cubes::newMarchingCubesImpl(m_runtimePolicy, + m_allocatorID, + m_ndim); + + m_impl->initialize(dom, topologyName, maskField); + m_impl->setDataParallelism(m_dataParallelism); } MarchingCubes::DomainIdType MarchingCubesSingleDomain::getDomainId( diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 91b9fbefa9..1f4b6c6412 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -110,43 +110,44 @@ class MarchingCubes using RuntimePolicy = axom::runtime_policy::Policy; using DomainIdType = int; /*! - * \brief Constructor sets up computational mesh and data for running the - * marching cubes algorithm. - * - * \param [in] runtimePolicy A value from RuntimePolicy. - * The simplest policy is RuntimePolicy::seq, which specifies - * running sequentially on the CPU. - * \param [in] allocatorID Data allocator ID. Choose something compatible - * with \c runtimePolicy. See \c esecution_space. - * \param [in] dataParallelism Data parallel implementation choice. - * \param [in] bpMesh Blueprint multi-domain mesh containing scalar field. - * \param [in] topologyName Name of Blueprint topology to use in \a bpMesh. - * \param [in] maskField Cell-based std::int32_t mask field. If provided, - * cells where this field evaluates to false are skipped. - * - * Array data in \a dom must be accessible in the \a runtimePolicy - * environment. It's an error if not, e.g., using CPU memory with - * a GPU policy. - * - * Some data from \a bpMesh may be cached by the constructor. - * Any change to it after the constructor leads to undefined behavior. - * - * The mesh coordinates should be contiguous. See - * conduit::blueprint::is_contiguous(). In the future, this - * requirement may be relaxed, possibly at the cost of a - * transformation and storage of the temporary contiguous layout. - * - * Blueprint allows users to specify ids for the domains. If - * "state/domain_id" exists in the domains, it is used as the domain - * id. Otherwise, the domain's interation index within the - * multidomain mesh is used. - */ + \brief Constructor sets up computational mesh and data for running the + marching cubes algorithm. + + \param [in] runtimePolicy A value from RuntimePolicy. + The simplest policy is RuntimePolicy::seq, which specifies + running sequentially on the CPU. + \param [in] allocatorID Data allocator ID. Choose something compatible + with \c runtimePolicy. See \c esecution_space. + \param [in] dataParallelism Data parallel implementation choice. + \param [in] bpMesh Blueprint multi-domain mesh containing scalar field. + \param [in] topologyName Name of Blueprint topology to use in \a bpMesh. + \param [in] maskField Cell-based std::int32_t mask field. If provided, + cells where this field evaluates to false are skipped. + + Array data in \a dom must be accessible in the \a runtimePolicy + environment. It's an error if not, e.g., using CPU memory with + a GPU policy. + + Some data from \a bpMesh may be cached by the constructor. + Any change to it after the constructor leads to undefined behavior. + + The mesh coordinates should be contiguous. See + conduit::blueprint::is_contiguous(). In the future, this + requirement may be relaxed, possibly at the cost of a + transformation and storage of the temporary contiguous layout. + + Blueprint allows users to specify ids for the domains. If + "state/domain_id" exists in the domains, it is used as the domain + id. Otherwise, the domain's interation index within the + multidomain mesh is used. + */ MarchingCubes(RuntimePolicy runtimePolicy, int allocatorId, - MarchingCubesDataParallelism dataParallelism, - const conduit::Node &bpMesh, - const std::string &topologyName, - const std::string &maskField = {}); + MarchingCubesDataParallelism dataParallelism); + + void initialize(const conduit::Node &bpMesh, + const std::string &topologyName, + const std::string &maskField = {}); /*! @brief Set the field containing the nodal function. @@ -269,10 +270,13 @@ class MarchingCubes //@} /*! - @brief Clear computed data (without deallocating memory). + @brief Clear computed data. + + After clearing, you have to call initialize as if it + was a new object. - After clearing, you can choose another field, contour value and - recompute the contour. You cannot change the mesh. + @internal For good GPU performance, memory is not deallocated. To + really deallocate memory, destruct this object and use another. */ void clear(); @@ -284,9 +288,16 @@ class MarchingCubes MarchingCubesDataParallelism m_dataParallelism = MarchingCubesDataParallelism::byPolicy; - //! @brief Single-domain implementations. + //!@brief Number of domains. + axom::IndexType m_domainCount; + + /*! + @brief Single-domain implementations. + + May be longer than m_domainCount (the real count). + */ axom::Array> m_singles; - const std::string m_topologyName; + std::string m_topologyName; std::string m_fcnFieldName; std::string m_fcnPath; std::string m_maskFieldName; @@ -343,29 +354,34 @@ class MarchingCubesSingleDomain * \param [in] allocatorID Data allocator ID. Choose something compatible * with \c runtimePolicy. See \c esecution_space. * \param [in] dataPar Choice of data-parallel implementation. - * \param [in] dom Blueprint single-domain mesh containing scalar field. - * \param [in] topologyName Name of Blueprint topology to use in \a dom - * \param [in] maskField Cell-based std::int32_t mask field. If provided, - * cells where this field evaluates to false are skipped. - * - * Array data in \a dom must be accessible in the \a runtimePolicy - * environment. It's an error if not, e.g., using CPU memory with - * a GPU policy. - * - * Some data from \a dom may be cached by the constructor. - * Any change to it after the constructor leads to undefined behavior. - * - * The mesh coordinates should be stored contiguously. See - * conduit::blueprint::is_contiguous(). In the future, this - * requirement may be relaxed, possibly at the cost of a - * transformation and storage of the temporary contiguous layout. */ MarchingCubesSingleDomain(RuntimePolicy runtimePolicy, int allocatorID, - MarchingCubesDataParallelism dataPar, - const conduit::Node &dom, - const std::string &topologyName, - const std::string &maskfield); + MarchingCubesDataParallelism dataPar); + + /*! + @brief Intitialize object to a domain. + \param [in] dom Blueprint single-domain mesh containing scalar field. + \param [in] topologyName Name of Blueprint topology to use in \a dom + \param [in] maskField Cell-based std::int32_t mask field. If provided, + cells where this field evaluates to false are skipped. + + Array data in \a dom must be accessible in the the \a + runtimePolicy environment in the constructor. It's an error if + not, e.g., using CPU memory with a GPU policy. + + Some data from \a dom may be cached by the constructor. Any + change to it without re-initialization leads to undefined + behavior. + + The mesh coordinates should be stored contiguously. See + conduit::blueprint::is_contiguous(). In the future, this + requirement may be relaxed, possibly at the cost of a + transformation and storage of the temporary contiguous layout. + */ + void initialize( const conduit::Node &dom, + const std::string &topologyName, + const std::string &maskfield); int spatialDimension() const { return m_ndim; } @@ -514,15 +530,15 @@ class MarchingCubesSingleDomain int m_ndim; //!@brief Name of Blueprint topology in m_dom. - const std::string m_topologyName; + std::string m_topologyName; std::string m_fcnFieldName; //!@brief Path to nodal scalar function in m_dom. std::string m_fcnPath; - const std::string m_maskFieldName; + std::string m_maskFieldName; //!@brief Path to mask in m_dom. - const std::string m_maskPath; + std::string m_maskPath; double m_contourVal = 0.0; diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 4388588761..8188a78167 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -744,13 +744,13 @@ struct ContourTestBase } } #endif +for(int j=0; j<5; ++j) +{ // Create marching cubes algorithm object and set some parameters quest::MarchingCubes mc(params.policy, s_allocatorId, - params.dataParallelism, - computationalMesh.asConduitNode(), - "mesh"); - + params.dataParallelism); + mc.initialize(computationalMesh.asConduitNode(), "mesh"); mc.setFunctionField(functionName()); axom::utilities::Timer computeTimer(false); @@ -759,12 +759,12 @@ struct ContourTestBase #endif computeTimer.start(); mc.computeIsocontour(params.contourVal); - for(int i=0; i<4; ++i) { + for(int i=0; i<3; ++i) { mc.clear(); mc.computeIsocontour(params.contourVal); } computeTimer.stop(); - printTimingStats(computeTimer, name() + " contour"); + // printTimingStats(computeTimer, name() + " contour"); { int mn, mx, sum; @@ -781,6 +781,7 @@ struct ContourTestBase axom::fmt::format("Surface mesh has locally {} cells, {} nodes.", mc.getContourCellCount(), mc.getContourNodeCount())); +} // Return conduit data to host memory. if(s_allocatorId != axom::execution_space::allocatorID()) @@ -797,6 +798,7 @@ struct ContourTestBase axom::execution_space::allocatorID()); } +#if 0 // Put contour mesh in a mint object for error checking and output. std::string sidreGroupName = name() + "_mesh"; sidre::DataStore objectDS; @@ -831,6 +833,9 @@ struct ContourTestBase objectDS.getRoot()->destroyGroupAndData(sidreGroupName); return localErrCount; +#else + return 0; +#endif } void computeNodalDistance(BlueprintStructuredMesh& bpMesh) From 1aa60eeb749381cb2b770c13965857f148c4a0ea Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sun, 18 Feb 2024 11:31:09 -0800 Subject: [PATCH 26/61] Parameterize the number of repetitions for performance testing. --- .../examples/quest_marching_cubes_example.cpp | 93 +++++++++++-------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 8188a78167..e0e582c9da 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -100,6 +100,11 @@ struct Input quest::MarchingCubesDataParallelism dataParallelism = quest::MarchingCubesDataParallelism::byPolicy; + // Distinct MarchingCubes objects count. + int objectRepCount = 1; + // Contour generation count for each MarchingCubes objects. + int contourGenCount = 1; + private: bool _verboseOutput {false}; @@ -185,6 +190,18 @@ struct Input "Enable/disable checking results against analytical solution") ->capture_default_str(); + // + // Number of repetitions to run + // + + app.add_option("--objectReps", objectRepCount) + ->description("Number of MarchingCube object repetitions to run") + ->capture_default_str(); + + app.add_option("--contourGenReps", contourGenCount) + ->description("Number of contour repetitions to run for each MarchingCubes object") + ->capture_default_str(); + app.get_formatter()->column_width(60); // could throw an exception @@ -744,44 +761,47 @@ struct ContourTestBase } } #endif -for(int j=0; j<5; ++j) -{ - // Create marching cubes algorithm object and set some parameters - quest::MarchingCubes mc(params.policy, - s_allocatorId, - params.dataParallelism); - mc.initialize(computationalMesh.asConduitNode(), "mesh"); - mc.setFunctionField(functionName()); - - axom::utilities::Timer computeTimer(false); + std::unique_ptr mcPtr; + for(int j=0; j(params.policy, + s_allocatorId, + params.dataParallelism); + auto& mc = *mcPtr; + mc.initialize(computationalMesh.asConduitNode(), "mesh"); + mc.setFunctionField(functionName()); + + axom::utilities::Timer computeTimer(false); #ifdef AXOM_USE_MPI - MPI_Barrier(MPI_COMM_WORLD); + MPI_Barrier(MPI_COMM_WORLD); #endif - computeTimer.start(); - mc.computeIsocontour(params.contourVal); - for(int i=0; i<3; ++i) { - mc.clear(); - mc.computeIsocontour(params.contourVal); - } - computeTimer.stop(); - // printTimingStats(computeTimer, name() + " contour"); + computeTimer.start(); + for(int i=0; i::allocatorID()) @@ -798,7 +818,6 @@ for(int j=0; j<5; ++j) axom::execution_space::allocatorID()); } -#if 0 // Put contour mesh in a mint object for error checking and output. std::string sidreGroupName = name() + "_mesh"; sidre::DataStore objectDS; @@ -807,6 +826,7 @@ for(int j=0; j<5; ++j) DIM, DIM == 2 ? mint::CellType::SEGMENT : mint::CellType::TRIANGLE, meshGroup); + auto& mc = *mcPtr; axom::utilities::Timer extractTimer(false); extractTimer.start(); mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); @@ -833,9 +853,6 @@ for(int j=0; j<5; ++j) objectDS.getRoot()->destroyGroupAndData(sidreGroupName); return localErrCount; -#else - return 0; -#endif } void computeNodalDistance(BlueprintStructuredMesh& bpMesh) From 17cc260df2304954d974c1bef9a385ba50def92c Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sun, 18 Feb 2024 12:56:18 -0800 Subject: [PATCH 27/61] Factor MarchingCubesSingleDomain into its own file. --- src/axom/quest/MarchingCubes.cpp | 12 +- src/axom/quest/MarchingCubes.hpp | 229 +-------------- src/axom/quest/detail/MarchingCubesImpl.hpp | 1 + .../detail/MarchingCubesSingleDomain.cpp | 102 +++++++ .../detail/MarchingCubesSingleDomain.hpp | 271 ++++++++++++++++++ 5 files changed, 385 insertions(+), 230 deletions(-) create mode 100644 src/axom/quest/detail/MarchingCubesSingleDomain.cpp create mode 100644 src/axom/quest/detail/MarchingCubesSingleDomain.hpp diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 22f3e07482..5b2e2b7a90 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -13,6 +13,7 @@ #include "axom/core/execution/execution_space.hpp" #include "axom/quest/MarchingCubes.hpp" +#include "axom/quest/detail/MarchingCubesSingleDomain.hpp" #include "axom/quest/detail/MarchingCubesImpl.hpp" #include "axom/fmt.hpp" @@ -143,21 +144,22 @@ axom::IndexType MarchingCubes::getContourNodeCount() const Domain ids are provided as a new Array instead of ArrayView because we don't store it internally. */ -axom::Array MarchingCubes::getContourFacetDomainIds( +template +axom::Array MarchingCubes::getContourFacetDomainIds( int allocatorID) const { // Put parent domain ids into a new Array. const axom::IndexType len = getContourCellCount(); - axom::Array rval( + axom::Array rval( len, len, allocatorID != axom::INVALID_ALLOCATOR_ID ? allocatorID : m_allocatorID); for(int d = 0; d < m_singles.size(); ++d) { - MarchingCubes::DomainIdType domainId = m_singles[d]->getDomainId(d); + DomainIdType domainId = static_cast(m_singles[d]->getDomainId(d)); axom::IndexType contourCellCount = m_singles[d]->getContourCellCount(); axom::IndexType offset = m_facetIndexOffsets[d]; - axom::detail::ArrayOps::fill( + axom::detail::ArrayOps::fill( rval.data(), offset, contourCellCount, @@ -257,7 +259,7 @@ void MarchingCubes::populateContourMesh( // Put parent domain ids into the mesh. auto* domainIdPtr = mesh.getFieldPtr(domainIdField, axom::mint::CELL_CENTERED); - auto tmpContourFacetDomainIds = getContourFacetDomainIds(hostAllocatorId); + auto tmpContourFacetDomainIds = getContourFacetDomainIds(hostAllocatorId); axom::copy(domainIdPtr, tmpContourFacetDomainIds.data(), m_facetCount * sizeof(DomainIdType)); diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 1f4b6c6412..ef9c6a7865 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -72,8 +72,7 @@ class MarchingCubesSingleDomain; * cubes). * * The input mesh is a Conduit::Node following the Mesh Blueprint - * convention. The mesh must be in multi-domain format. For - * single-domain, see MarchingCubesSingleDomain. + * convention. The mesh must be in multi-domain format. * * Usage example: * @beginverbatim @@ -238,10 +237,9 @@ class MarchingCubes use the id set in the constructor. The buffer size is getContourCellCount(). - - Memory space of data corresponds to allocator set in the constructor. */ - axom::Array getContourFacetDomainIds( + template + axom::Array getContourFacetDomainIds( int allocatorID = axom::INVALID_ALLOCATOR_ID) const; #if 1 @@ -296,7 +294,7 @@ class MarchingCubes May be longer than m_domainCount (the real count). */ - axom::Array> m_singles; + axom::Array> m_singles; std::string m_topologyName; std::string m_fcnFieldName; std::string m_fcnPath; @@ -334,225 +332,6 @@ class MarchingCubes void allocateOutputBuffers(); }; -/*! - * \@brief Class implementing marching cubes algorithm for a single - * domain. - * - * \sa MarchingCubes - */ -class MarchingCubesSingleDomain -{ -public: - using RuntimePolicy = axom::runtime_policy::Policy; - /*! - * \brief Constructor for applying algorithm in a single domain. - * See MarchingCubes for the multi-domain implementation. - * - * \param [in] runtimePolicy A value from RuntimePolicy. - * The simplest policy is RuntimePolicy::seq, which specifies - * running sequentially on the CPU. - * \param [in] allocatorID Data allocator ID. Choose something compatible - * with \c runtimePolicy. See \c esecution_space. - * \param [in] dataPar Choice of data-parallel implementation. - */ - MarchingCubesSingleDomain(RuntimePolicy runtimePolicy, - int allocatorID, - MarchingCubesDataParallelism dataPar); - - /*! - @brief Intitialize object to a domain. - \param [in] dom Blueprint single-domain mesh containing scalar field. - \param [in] topologyName Name of Blueprint topology to use in \a dom - \param [in] maskField Cell-based std::int32_t mask field. If provided, - cells where this field evaluates to false are skipped. - - Array data in \a dom must be accessible in the the \a - runtimePolicy environment in the constructor. It's an error if - not, e.g., using CPU memory with a GPU policy. - - Some data from \a dom may be cached by the constructor. Any - change to it without re-initialization leads to undefined - behavior. - - The mesh coordinates should be stored contiguously. See - conduit::blueprint::is_contiguous(). In the future, this - requirement may be relaxed, possibly at the cost of a - transformation and storage of the temporary contiguous layout. - */ - void initialize( const conduit::Node &dom, - const std::string &topologyName, - const std::string &maskfield); - - int spatialDimension() const { return m_ndim; } - - /*! - @brief Specify the field containing the nodal scalar function - in the input mesh. - \param [in] fcnField Name of node-based scalar function values. - */ - void setFunctionField(const std::string &fcnField) - { - m_fcnFieldName = fcnField; - m_fcnPath = "fields/" + fcnField; - SLIC_ASSERT(m_dom->has_path(m_fcnPath)); - SLIC_ASSERT(m_dom->fetch_existing(m_fcnPath + "/association").as_string() == - "vertex"); - SLIC_ASSERT(m_dom->has_path(m_fcnPath + "/values")); - m_impl->setFunctionField(fcnField); - } - - void setContourValue(double contourVal) - { - m_contourVal = contourVal; - if(m_impl) m_impl->setContourValue(m_contourVal); - } - - // Methods trivially delegated to implementation. - void markCrossings() { m_impl->markCrossings(); } - void scanCrossings() { m_impl->scanCrossings(); } - axom::IndexType getContourCellCount() - { - return m_impl->getContourCellCount(); - } - void computeContour() { m_impl->computeContour(); } - - /*! - @brief Get the Blueprint domain id specified in \a state/domain_id - if it is provided, or use the given default if not provided. - */ - MarchingCubes::DomainIdType getDomainId(MarchingCubes::DomainIdType defaultId) const; - - //!@brief Get number of cells in the generated contour mesh. - axom::IndexType getContourCellCount() const - { - SLIC_ASSERT_MSG( - m_impl, - "There is no contour mesh until you call computeIsocontour()"); - axom::IndexType cellCount = m_impl->getContourCellCount(); - return cellCount; - } - - //!@brief Get number of nodes in the generated contour mesh. - axom::IndexType getContourNodeCount() const - { - return m_ndim * getContourCellCount(); - } - - /*! - @brief Base class for implementations templated on dimension DIM - and execution space ExecSpace. - - Implementation details templated on DIM and ExecSpace cannot - be in MarchingCubesSingleDomain so should live in this class. - - This class allows m_impl to refer to any implementation used - at runtime. - */ - struct ImplBase - { - /*! - @brief Prepare internal data for operating on the given domain. - - Put in here codes that can't be in MarchingCubesSingleDomain - due to template use (DIM and ExecSpace). - */ - virtual void initialize(const conduit::Node &dom, - const std::string &topologyName, - const std::string &maskPath = {}) = 0; - - virtual void setFunctionField(const std::string &fcnFieldName) = 0; - virtual void setContourValue(double contourVal) = 0; - - void setDataParallelism(MarchingCubesDataParallelism dataPar) - { - m_dataParallelism = dataPar; - } - - //@{ - //!@name Distinct phases in contour generation. - //!@brief Compute the contour mesh. - //!@brief Mark parent cells that cross the contour value. - virtual void markCrossings() = 0; - //!@brief Scan operations to determine counts and offsets. - virtual void scanCrossings() = 0; - //!@brief Compute contour data. - virtual void computeContour() = 0; - //@} - - //@{ - //!@name Output methods - //!@brief Return number of contour mesh facets generated. - virtual axom::IndexType getContourCellCount() const = 0; - //@} - - void setOutputBuffers(axom::ArrayView &facetNodeIds, - axom::ArrayView &facetNodeCoords, - axom::ArrayView &facetParentIds, - axom::IndexType facetIndexOffset) - { - m_facetNodeIds = facetNodeIds; - m_facetNodeCoords = facetNodeCoords; - m_facetParentIds = facetParentIds; - m_facetIndexOffset = facetIndexOffset; - } - - virtual ~ImplBase() { } - - virtual void clear() = 0; - - MarchingCubesDataParallelism m_dataParallelism = - MarchingCubesDataParallelism::byPolicy; - - double m_contourVal = 0.0; - axom::ArrayView m_facetNodeIds; - axom::ArrayView m_facetNodeCoords; - axom::ArrayView m_facetParentIds; - axom::IndexType m_facetIndexOffset = -1; - }; - - ImplBase& getImpl() - { - return *m_impl; - } - -private: - RuntimePolicy m_runtimePolicy; - int m_allocatorID = axom::INVALID_ALLOCATOR_ID; - - //@brief Choice of full or partial data-parallelism, or byPolicy. - MarchingCubesDataParallelism m_dataParallelism = - MarchingCubesDataParallelism::byPolicy; - - /*! - \brief Computational mesh as a conduit::Node. - */ - const conduit::Node *m_dom; - int m_ndim; - - //!@brief Name of Blueprint topology in m_dom. - std::string m_topologyName; - - std::string m_fcnFieldName; - //!@brief Path to nodal scalar function in m_dom. - std::string m_fcnPath; - - std::string m_maskFieldName; - //!@brief Path to mask in m_dom. - std::string m_maskPath; - - double m_contourVal = 0.0; - - std::unique_ptr m_impl; - - /*! - * \brief Set the blueprint single-domain mesh. - * - * Some data from \a dom may be cached. - */ - void setDomain(const conduit::Node &dom); - -}; // class MarchingCubesSingleDomain - } // namespace quest } // namespace axom diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 6e091ea4c3..6324a0692a 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -15,6 +15,7 @@ #include "axom/slic/interface/slic_macros.hpp" #include "axom/quest/ArrayIndexer.hpp" #include "axom/quest/MeshViewUtil.hpp" +#include "axom/quest/detail/MarchingCubesSingleDomain.hpp" #include "axom/primal/geometry/Point.hpp" #include "axom/primal/constants.hpp" #include "axom/mint/execution/internal/structured_exec.hpp" diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp new file mode 100644 index 0000000000..b06cddb0a1 --- /dev/null +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp @@ -0,0 +1,102 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/config.hpp" + +// Implementation requires Conduit. +#ifndef AXOM_USE_CONDUIT + #error "MarchingCubes.cpp requires conduit" +#endif +#include "conduit_blueprint.hpp" + +#include "axom/core/execution/execution_space.hpp" +#include "axom/quest/detail/MarchingCubesSingleDomain.hpp" +#include "axom/quest/detail/MarchingCubesImpl.hpp" +#include "axom/fmt.hpp" + +namespace axom +{ +namespace quest +{ + +MarchingCubesSingleDomain::MarchingCubesSingleDomain( + RuntimePolicy runtimePolicy, + int allocatorID, + MarchingCubesDataParallelism dataPar) + : m_runtimePolicy(runtimePolicy) + , m_allocatorID(allocatorID) + , m_dataParallelism(dataPar) + , m_dom(nullptr) + , m_ndim(0) + , m_topologyName() + , m_fcnFieldName() + , m_fcnPath() + , m_maskFieldName() + , m_maskPath() +{ + return; +} + +void MarchingCubesSingleDomain::initialize( + const conduit::Node& dom, + const std::string& topologyName, + const std::string& maskField) +{ + m_topologyName = topologyName; + + SLIC_ASSERT_MSG( + !conduit::blueprint::mesh::is_multi_domain(dom), + "MarchingCubesSingleDomain is single-domain only. Try MarchingCubes."); + SLIC_ASSERT( + dom.fetch_existing("topologies/" + m_topologyName + "/type").as_string() == + "structured"); + + const std::string coordsetPath = "coordsets/" + + dom.fetch_existing("topologies/" + m_topologyName + "/coordset").as_string(); + SLIC_ASSERT(dom.has_path(coordsetPath)); + + if(!m_maskPath.empty()) + { + m_maskPath = maskField.empty() ? std::string() : "fields/" + maskField; + SLIC_ASSERT(dom.has_path(m_maskPath + "/values")); + } + else + { + m_maskPath.clear(); + } + + m_dom = &dom; + + m_ndim = conduit::blueprint::mesh::topology::dims( + dom.fetch_existing(axom::fmt::format("topologies/{}", m_topologyName))); + SLIC_ASSERT(m_ndim >= 2 && m_ndim <= 3); + + SLIC_ASSERT_MSG( + !conduit::blueprint::mcarray::is_interleaved( + dom.fetch_existing(coordsetPath + "/values")), + "MarchingCubes currently requires contiguous coordinates layout."); + + m_impl = + axom::quest::detail::marching_cubes::newMarchingCubesImpl(m_runtimePolicy, + m_allocatorID, + m_ndim); + + m_impl->initialize(dom, topologyName, maskField); + m_impl->setDataParallelism(m_dataParallelism); +} + +int32_t MarchingCubesSingleDomain::getDomainId( + int32_t defaultId) const +{ + int rval = defaultId; + if(m_dom->has_path("state/domain_id")) + { + rval = m_dom->fetch_existing("state/domain_id").to_int32(); + } + return rval; +} + +} // end namespace quest +} // end namespace axom diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp new file mode 100644 index 0000000000..0fc4d1f117 --- /dev/null +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -0,0 +1,271 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/*! + * \file MarchingCubesSingleDomain.hpp + * + * \brief Consists of classes implementing marching cubes algorithm to + * compute isocontour from a scalar field in a blueprint mesh. + */ + +#ifndef AXOM_QUEST_MARCHINGCUBESSINGLEDOMAIN_H_ +#define AXOM_QUEST_MARCHINGCUBESSINGLEDOMAIN_H_ + +#include "axom/config.hpp" + +// Implementation requires Conduit. +#ifdef AXOM_USE_CONDUIT + + // Axom includes + #include "axom/core/execution/runtime_policy.hpp" + #include "axom/mint/mesh/UnstructuredMesh.hpp" + #include "axom/quest/MarchingCubes.hpp" + + // Conduit includes + #include "conduit_node.hpp" + + // C++ includes + #include + +namespace axom +{ +namespace quest +{ +namespace detail +{ +namespace marching_cubes +{ +template +class MarchingCubesImpl; +} // namespace marching_cubes +} // namespace detail + +/*! + * \@brief Class implementing marching cubes algorithm for a single + * domain. + * + * \sa MarchingCubes + */ +class MarchingCubesSingleDomain +{ +public: + using DomainIdType = int; + using RuntimePolicy = axom::runtime_policy::Policy; + /*! + * \brief Constructor for applying algorithm in a single domain. + * See MarchingCubes for the multi-domain implementation. + * + * \param [in] runtimePolicy A value from RuntimePolicy. + * The simplest policy is RuntimePolicy::seq, which specifies + * running sequentially on the CPU. + * \param [in] allocatorID Data allocator ID. Choose something compatible + * with \c runtimePolicy. See \c esecution_space. + * \param [in] dataPar Choice of data-parallel implementation. + */ + MarchingCubesSingleDomain(RuntimePolicy runtimePolicy, + int allocatorID, + MarchingCubesDataParallelism dataPar); + + ~MarchingCubesSingleDomain() {} + + /*! + @brief Intitialize object to a domain. + \param [in] dom Blueprint single-domain mesh containing scalar field. + \param [in] topologyName Name of Blueprint topology to use in \a dom + \param [in] maskField Cell-based std::int32_t mask field. If provided, + cells where this field evaluates to false are skipped. + + Array data in \a dom must be accessible in the the \a + runtimePolicy environment in the constructor. It's an error if + not, e.g., using CPU memory with a GPU policy. + + Some data from \a dom may be cached by the constructor. Any + change to it without re-initialization leads to undefined + behavior. + + The mesh coordinates should be stored contiguously. See + conduit::blueprint::is_contiguous(). In the future, this + requirement may be relaxed, possibly at the cost of a + transformation and storage of the temporary contiguous layout. + */ + void initialize( const conduit::Node &dom, + const std::string &topologyName, + const std::string &maskfield); + + int spatialDimension() const { return m_ndim; } + + /*! + @brief Specify the field containing the nodal scalar function + in the input mesh. + \param [in] fcnField Name of node-based scalar function values. + */ + void setFunctionField(const std::string &fcnField) + { + m_fcnFieldName = fcnField; + m_fcnPath = "fields/" + fcnField; + SLIC_ASSERT(m_dom->has_path(m_fcnPath)); + SLIC_ASSERT(m_dom->fetch_existing(m_fcnPath + "/association").as_string() == + "vertex"); + SLIC_ASSERT(m_dom->has_path(m_fcnPath + "/values")); + m_impl->setFunctionField(fcnField); + } + + void setContourValue(double contourVal) + { + m_contourVal = contourVal; + if(m_impl) m_impl->setContourValue(m_contourVal); + } + + // Methods trivially delegated to implementation. + void markCrossings() { m_impl->markCrossings(); } + void scanCrossings() { m_impl->scanCrossings(); } + axom::IndexType getContourCellCount() + { + return m_impl->getContourCellCount(); + } + void computeContour() { m_impl->computeContour(); } + + /*! + @brief Get the Blueprint domain id specified in \a state/domain_id + if it is provided, or use the given default if not provided. + */ + int32_t getDomainId(int32_t defaultId) const; + + //!@brief Get number of cells in the generated contour mesh. + axom::IndexType getContourCellCount() const + { + SLIC_ASSERT_MSG( + m_impl, + "There is no contour mesh until you call computeIsocontour()"); + axom::IndexType cellCount = m_impl->getContourCellCount(); + return cellCount; + } + + //!@brief Get number of nodes in the generated contour mesh. + axom::IndexType getContourNodeCount() const + { + return m_ndim * getContourCellCount(); + } + + /*! + @brief Base class for implementations templated on dimension DIM + and execution space ExecSpace. + + Implementation details templated on DIM and ExecSpace cannot + be in MarchingCubesSingleDomain so should live in this class. + + This class allows m_impl to refer to any implementation used + at runtime. + */ + struct ImplBase + { + /*! + @brief Prepare internal data for operating on the given domain. + + Put in here codes that can't be in MarchingCubesSingleDomain + due to template use (DIM and ExecSpace). + */ + virtual void initialize(const conduit::Node &dom, + const std::string &topologyName, + const std::string &maskPath = {}) = 0; + + virtual void setFunctionField(const std::string &fcnFieldName) = 0; + virtual void setContourValue(double contourVal) = 0; + + void setDataParallelism(MarchingCubesDataParallelism dataPar) + { + m_dataParallelism = dataPar; + } + + //@{ + //!@name Distinct phases in contour generation. + //!@brief Compute the contour mesh. + //!@brief Mark parent cells that cross the contour value. + virtual void markCrossings() = 0; + //!@brief Scan operations to determine counts and offsets. + virtual void scanCrossings() = 0; + //!@brief Compute contour data. + virtual void computeContour() = 0; + //@} + + //@{ + //!@name Output methods + //!@brief Return number of contour mesh facets generated. + virtual axom::IndexType getContourCellCount() const = 0; + //@} + + void setOutputBuffers(axom::ArrayView &facetNodeIds, + axom::ArrayView &facetNodeCoords, + axom::ArrayView &facetParentIds, + axom::IndexType facetIndexOffset) + { + m_facetNodeIds = facetNodeIds; + m_facetNodeCoords = facetNodeCoords; + m_facetParentIds = facetParentIds; + m_facetIndexOffset = facetIndexOffset; + } + + virtual ~ImplBase() { } + + virtual void clear() = 0; + + MarchingCubesDataParallelism m_dataParallelism = + MarchingCubesDataParallelism::byPolicy; + + double m_contourVal = 0.0; + axom::ArrayView m_facetNodeIds; + axom::ArrayView m_facetNodeCoords; + axom::ArrayView m_facetParentIds; + axom::IndexType m_facetIndexOffset = -1; + }; + + ImplBase& getImpl() + { + return *m_impl; + } + +private: + RuntimePolicy m_runtimePolicy; + int m_allocatorID = axom::INVALID_ALLOCATOR_ID; + + //@brief Choice of full or partial data-parallelism, or byPolicy. + MarchingCubesDataParallelism m_dataParallelism = + MarchingCubesDataParallelism::byPolicy; + + /*! + \brief Computational mesh as a conduit::Node. + */ + const conduit::Node *m_dom; + int m_ndim; + + //!@brief Name of Blueprint topology in m_dom. + std::string m_topologyName; + + std::string m_fcnFieldName; + //!@brief Path to nodal scalar function in m_dom. + std::string m_fcnPath; + + std::string m_maskFieldName; + //!@brief Path to mask in m_dom. + std::string m_maskPath; + + double m_contourVal = 0.0; + + std::unique_ptr m_impl; + + /*! + * \brief Set the blueprint single-domain mesh. + * + * Some data from \a dom may be cached. + */ + void setDomain(const conduit::Node &dom); + +}; // class MarchingCubesSingleDomain + +} // namespace quest +} // namespace axom + +#endif // AXOM_USE_CONDUIT +#endif // AXOM_QUEST_MARCHINGCUBES_H_ From 5d2903a63cd7c5d936c22f214778ae1bf7bc1b88 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sun, 18 Feb 2024 15:09:48 -0800 Subject: [PATCH 28/61] Rename computeContour, which was easily confused with computeIsocontour. --- src/axom/quest/CMakeLists.txt | 4 +- src/axom/quest/MarchingCubes.cpp | 79 +------------------ src/axom/quest/detail/MarchingCubesImpl.hpp | 18 ++--- .../detail/MarchingCubesSingleDomain.hpp | 4 +- .../examples/quest_marching_cubes_example.cpp | 35 ++++---- 5 files changed, 34 insertions(+), 106 deletions(-) diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index ac2468103f..7daceb2ea3 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -119,13 +119,13 @@ endif() blt_list_append( TO quest_headers - ELEMENTS MarchingCubes.hpp detail/MarchingCubesImpl.hpp + ELEMENTS MarchingCubes.hpp detail/MarchingCubesSingleDomain.hpp detail/MarchingCubesImpl.hpp IF CONDUIT_FOUND ) blt_list_append( TO quest_sources - ELEMENTS MarchingCubes.cpp + ELEMENTS MarchingCubes.cpp detail/MarchingCubesSingleDomain.cpp IF CONDUIT_FOUND ) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 5b2e2b7a90..64ad721453 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -126,7 +126,7 @@ void MarchingCubes::computeIsocontour(double contourVal) for(axom::IndexType d = 0; d < m_singles.size(); ++d) { - m_singles[d]->computeContour(); + m_singles[d]->computeFacets(); } } @@ -280,82 +280,5 @@ void MarchingCubes::allocateOutputBuffers() } } -MarchingCubesSingleDomain::MarchingCubesSingleDomain( - RuntimePolicy runtimePolicy, - int allocatorID, - MarchingCubesDataParallelism dataPar) - : m_runtimePolicy(runtimePolicy) - , m_allocatorID(allocatorID) - , m_dataParallelism(dataPar) - , m_dom(nullptr) - , m_ndim(0) - , m_topologyName() - , m_fcnFieldName() - , m_fcnPath() - , m_maskFieldName() - , m_maskPath() -{ - return; -} - -void MarchingCubesSingleDomain::initialize( - const conduit::Node& dom, - const std::string& topologyName, - const std::string& maskField) -{ - m_topologyName = topologyName; - - SLIC_ASSERT_MSG( - !conduit::blueprint::mesh::is_multi_domain(dom), - "MarchingCubesSingleDomain is single-domain only. Try MarchingCubes."); - SLIC_ASSERT( - dom.fetch_existing("topologies/" + m_topologyName + "/type").as_string() == - "structured"); - - const std::string coordsetPath = "coordsets/" + - dom.fetch_existing("topologies/" + m_topologyName + "/coordset").as_string(); - SLIC_ASSERT(dom.has_path(coordsetPath)); - - if(!m_maskPath.empty()) - { - m_maskPath = maskField.empty() ? std::string() : "fields/" + maskField; - SLIC_ASSERT(dom.has_path(m_maskPath + "/values")); - } - else - { - m_maskPath.clear(); - } - - m_dom = &dom; - - m_ndim = conduit::blueprint::mesh::topology::dims( - dom.fetch_existing(axom::fmt::format("topologies/{}", m_topologyName))); - SLIC_ASSERT(m_ndim >= 2 && m_ndim <= 3); - - SLIC_ASSERT_MSG( - !conduit::blueprint::mcarray::is_interleaved( - dom.fetch_existing(coordsetPath + "/values")), - "MarchingCubes currently requires contiguous coordinates layout."); - - m_impl = - axom::quest::detail::marching_cubes::newMarchingCubesImpl(m_runtimePolicy, - m_allocatorID, - m_ndim); - - m_impl->initialize(dom, topologyName, maskField); - m_impl->setDataParallelism(m_dataParallelism); -} - -MarchingCubes::DomainIdType MarchingCubesSingleDomain::getDomainId( - MarchingCubes::DomainIdType defaultId) const -{ - MarchingCubes::DomainIdType rval = defaultId; - if(m_dom->has_path("state/domain_id")) - { - rval = m_dom->fetch_existing("state/domain_id").as_int(); - } - return rval; -} - } // end namespace quest } // end namespace axom diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 6324a0692a..b2eb09d0be 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -568,9 +568,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase m_firstFacetIds.resize(m_crossingCount); } - void computeContour() override + void computeFacets() override { - AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::computeContour"); + AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::computeFacets"); const auto facetIncrsView = m_facetIncrs.view(); const auto firstFacetIdsView = m_firstFacetIds.view(); const auto crossingParentIdsView = m_crossingParentIds.view(); @@ -582,7 +582,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::ArrayView facetParentIdsView = m_facetParentIds; const axom::IndexType facetIndexOffset = m_facetIndexOffset; - ComputeContour_Util ccu(m_contourVal, + ComputeFacets_Util cfu(m_contourVal, m_caseIdsIndexer, m_fcnView, m_coordsViews); @@ -593,7 +593,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase auto caseId = caseIdsView.flatIndex(parentCellId); Point cornerCoords[CELL_CORNER_COUNT]; double cornerValues[CELL_CORNER_COUNT]; - ccu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); + cfu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); auto additionalFacets = facetIncrsView[crossingId]; auto firstFacetId = facetIndexOffset + firstFacetIdsView[crossingId]; @@ -611,7 +611,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase facetNodeIdsView[newFacetId][d] = newCornerId; int edge = cases_table(caseId, fId * DIM + d); - ccu.linear_interp(edge, + cfu.linear_interp(edge, cornerCoords, cornerValues, &facetNodeCoordsView(newCornerId, 0)); @@ -623,17 +623,17 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase } /*! - @brief Implementation used by MarchingCubesImpl::computeContour(). + @brief Implementation used by MarchingCubesImpl::computeFacets(). containing just the objects needed for that part, to be made available on devices. */ - struct ComputeContour_Util + struct ComputeFacets_Util { double contourVal; axom::ArrayIndexer indexer; axom::ArrayView fcnView; axom::StackArray, DIM> coordsViews; - ComputeContour_Util( + ComputeFacets_Util( double contourVal_, const axom::ArrayIndexer& parentIndexer_, const axom::ArrayView& fcnView_, @@ -811,7 +811,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase crossingPt[d] = p1[d] + w * (p2[d] - p1[d]); } } - }; // ComputeContour_Util + }; // ComputeFacets_Util // These 4 functions provide access to the look-up table // whether on host or device. Is there a more elegant way diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index 0fc4d1f117..ecbe7b8757 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -125,7 +125,7 @@ class MarchingCubesSingleDomain { return m_impl->getContourCellCount(); } - void computeContour() { m_impl->computeContour(); } + void computeFacets() { m_impl->computeFacets(); } /*! @brief Get the Blueprint domain id specified in \a state/domain_id @@ -187,7 +187,7 @@ class MarchingCubesSingleDomain //!@brief Scan operations to determine counts and offsets. virtual void scanCrossings() = 0; //!@brief Compute contour data. - virtual void computeContour() = 0; + virtual void computeFacets() = 0; //@} //@{ diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index e0e582c9da..459430eb83 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -786,21 +786,7 @@ struct ContourTestBase computeTimer.stop(); // printTimingStats(computeTimer, name() + " contour"); - { - int mn, mx, sum; - getIntMinMax(mc.getContourCellCount(), mn, mx, sum); - SLIC_INFO(axom::fmt::format( - "Contour mesh has {{min:{}, max:{}, sum:{}, avg:{}}} cells", - mn, - mx, - sum, - (double)sum / numRanks)); - } - SLIC_INFO_IF( - params.isVerbose(), - axom::fmt::format("Surface mesh has locally {} cells, {} nodes.", - mc.getContourCellCount(), - mc.getContourNodeCount())); + printRunStats(mc); } // Return conduit data to host memory. @@ -855,6 +841,25 @@ struct ContourTestBase return localErrCount; } + void printRunStats(const quest::MarchingCubes& mc) + { + { + int mn, mx, sum; + getIntMinMax(mc.getContourCellCount(), mn, mx, sum); + SLIC_INFO(axom::fmt::format( + "Contour mesh has {{min:{}, max:{}, sum:{}, avg:{}}} cells", + mn, + mx, + sum, + (double)sum / numRanks)); + } + SLIC_INFO_IF( + params.isVerbose(), + axom::fmt::format("Contour mesh has locally {} cells, {} nodes.", + mc.getContourCellCount(), + mc.getContourNodeCount())); + } + void computeNodalDistance(BlueprintStructuredMesh& bpMesh) { SLIC_ASSERT(bpMesh.dimension() == DIM); From 379ff7b94cec7fd427793dffeea65a6e9d7d90cf Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sun, 18 Feb 2024 18:38:15 -0800 Subject: [PATCH 29/61] Clean up data sharing code by giving friend access to MarchingCubes. --- src/axom/quest/MarchingCubes.cpp | 7 +- src/axom/quest/MarchingCubes.hpp | 17 ++- src/axom/quest/detail/MarchingCubesImpl.hpp | 75 ++----------- .../detail/MarchingCubesSingleDomain.cpp | 106 ++++++++++++++++-- .../detail/MarchingCubesSingleDomain.hpp | 16 ++- 5 files changed, 134 insertions(+), 87 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 64ad721453..420b0bc2f1 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -37,6 +37,9 @@ MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, , m_maskPath() , m_facetIndexOffsets(0, 0) , m_facetCount(0) + , m_caseIdsFlat(0, 0, m_allocatorID) + , m_crossingFlags(0, 0, m_allocatorID) + , m_scannedFlags(0, 0, m_allocatorID) , m_facetNodeIds(twoZeros, m_allocatorID) , m_facetNodeCoords(twoZeros, m_allocatorID) , m_facetParentIds(0, 0, m_allocatorID) @@ -69,9 +72,7 @@ void MarchingCubes::initialize( while( m_singles.size() < newDomainCount ) { - m_singles.emplace_back(new MarchingCubesSingleDomain(m_runtimePolicy, - m_allocatorID, - m_dataParallelism)); + m_singles.emplace_back(new detail::marching_cubes::MarchingCubesSingleDomain(*this)); } for (int d = 0; d < newDomainCount; ++d) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index ef9c6a7865..56da969a93 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -37,7 +37,8 @@ namespace detail namespace marching_cubes { template -class MarchingCubesImpl; +class MarchingCubesImpl; // TODO: Delete this. +class MarchingCubesSingleDomain; } // namespace marching_cubes } // namespace detail @@ -57,7 +58,6 @@ enum class MarchingCubesDataParallelism fullParallel = 2 }; -class MarchingCubesSingleDomain; /*! * \@brief Class implementing marching cubes to compute a contour @@ -278,6 +278,9 @@ class MarchingCubes */ void clear(); + // Allow single-domain code to share common scratch space. + friend detail::marching_cubes::MarchingCubesSingleDomain; + private: RuntimePolicy m_runtimePolicy; int m_allocatorID = axom::INVALID_ALLOCATOR_ID; @@ -294,7 +297,7 @@ class MarchingCubes May be longer than m_domainCount (the real count). */ - axom::Array> m_singles; + axom::Array> m_singles; std::string m_topologyName; std::string m_fcnFieldName; std::string m_fcnPath; @@ -307,6 +310,14 @@ class MarchingCubes //!@brief Facet count over all parent domains. axom::IndexType m_facetCount = 0; + //@{ + //!@name Scratch space, shared among singles + // Memory alloc is slow on CUDA, so this optimizes space AND time. + axom::Array m_caseIdsFlat; + axom::Array m_crossingFlags; + axom::Array m_scannedFlags; + //@} + //@{ //!@name Generated contour mesh, shared with singles. /*! diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index b2eb09d0be..e5b3fbc187 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -64,7 +64,10 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase typename execution_space::loop_policy; static constexpr auto MemorySpace = execution_space::memory_space; - AXOM_HOST MarchingCubesImpl(int allocatorID) + AXOM_HOST MarchingCubesImpl(int allocatorID, + axom::Array& caseIdsFlat, + axom::Array& crossingFlags, + axom::Array& scannedFlags) : m_allocatorID(allocatorID) , m_caseIdsFlat(0, 0, m_allocatorID) , m_caseIds() @@ -73,7 +76,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase , m_crossingParentIds(0, 0, m_allocatorID) , m_facetIncrs(0, 0, m_allocatorID) , m_firstFacetIds(0, 0, m_allocatorID) - { } + { + SLIC_ASSERT(caseIdsFlat.getAllocatorID() == allocatorID); + SLIC_ASSERT(crossingFlags.getAllocatorID() == allocatorID); + SLIC_ASSERT(scannedFlags.getAllocatorID() == allocatorID); + } /*! @brief Initialize data to a blueprint domain. @@ -964,70 +971,6 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase } }; -/*! - @brief Allocate a MarchingCubesImpl object, template-specialized - for caller-specified runtime policy and physical dimension. -*/ -static std::unique_ptr -newMarchingCubesImpl(MarchingCubes::RuntimePolicy runtimePolicy, - int allocatorID, - int dim) -{ - using ImplBase = axom::quest::MarchingCubesSingleDomain::ImplBase; - - SLIC_ASSERT(dim >= 2 && dim <= 3); - std::unique_ptr impl; - if(runtimePolicy == MarchingCubes::RuntimePolicy::seq) - { - impl = dim == 2 - ? std::unique_ptr( - new MarchingCubesImpl<2, axom::SEQ_EXEC, axom::SEQ_EXEC>(allocatorID)) - : std::unique_ptr( - new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>(allocatorID)); - } -#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP - else if(runtimePolicy == MarchingCubes::RuntimePolicy::omp) - { - impl = dim == 2 - ? std::unique_ptr( - new MarchingCubesImpl<2, axom::OMP_EXEC, axom::SEQ_EXEC>(allocatorID)) - : std::unique_ptr( - new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>(allocatorID)); - } -#endif -#ifdef AXOM_RUNTIME_POLICY_USE_CUDA - else if(runtimePolicy == MarchingCubes::RuntimePolicy::cuda) - { - impl = dim == 2 - ? std::unique_ptr( - new MarchingCubesImpl<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( - allocatorID)) - : std::unique_ptr( - new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( - allocatorID)); - } -#endif -#ifdef AXOM_RUNTIME_POLICY_USE_HIP - else if(runtimePolicy == MarchingCubes::RuntimePolicy::hip) - { - impl = dim == 2 - ? std::unique_ptr( - new MarchingCubesImpl<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( - allocatorID)) - : std::unique_ptr( - new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( - allocatorID)); - } -#endif - else - { - SLIC_ERROR(axom::fmt::format( - "MarchingCubesSingleDomain has no implementation for runtime policy {}", - runtimePolicy)); - } - return impl; -} - } // end namespace marching_cubes } // end namespace detail } // end namespace quest diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp index b06cddb0a1..00544dcc46 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp @@ -20,14 +20,17 @@ namespace axom { namespace quest { +namespace detail +{ +namespace marching_cubes +{ MarchingCubesSingleDomain::MarchingCubesSingleDomain( - RuntimePolicy runtimePolicy, - int allocatorID, - MarchingCubesDataParallelism dataPar) - : m_runtimePolicy(runtimePolicy) - , m_allocatorID(allocatorID) - , m_dataParallelism(dataPar) + MarchingCubes& mc) + : m_mc(mc) + , m_runtimePolicy(mc.m_runtimePolicy) + , m_allocatorID(mc.m_allocatorID) + , m_dataParallelism(mc.m_dataParallelism) , m_dom(nullptr) , m_ndim(0) , m_topologyName() @@ -78,15 +81,96 @@ void MarchingCubesSingleDomain::initialize( dom.fetch_existing(coordsetPath + "/values")), "MarchingCubes currently requires contiguous coordinates layout."); - m_impl = - axom::quest::detail::marching_cubes::newMarchingCubesImpl(m_runtimePolicy, - m_allocatorID, - m_ndim); + m_impl = newMarchingCubesImpl(); m_impl->initialize(dom, topologyName, maskField); m_impl->setDataParallelism(m_dataParallelism); } +/*! + @brief Allocate a MarchingCubesImpl object, template-specialized + for caller-specified runtime policy and physical dimension. +*/ +std::unique_ptr +MarchingCubesSingleDomain::newMarchingCubesImpl() +{ + SLIC_ASSERT(m_ndim >= 2 && m_ndim <= 3); + std::unique_ptr impl; + if(m_runtimePolicy == MarchingCubes::RuntimePolicy::seq) + { + impl = m_ndim == 2 + ? std::unique_ptr( + new MarchingCubesImpl<2, axom::SEQ_EXEC, axom::SEQ_EXEC>(m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)) + : std::unique_ptr( + new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>(m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)); + } +#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP + else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::omp) + { + impl = m_ndim == 2 + ? std::unique_ptr( + new MarchingCubesImpl<2, axom::OMP_EXEC, axom::SEQ_EXEC>(m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)) + : std::unique_ptr( + new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>(m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)); + } +#endif +#ifdef AXOM_RUNTIME_POLICY_USE_CUDA + else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::cuda) + { + impl = m_ndim == 2 + ? std::unique_ptr( + new MarchingCubesImpl<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)) + : std::unique_ptr( + new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)); + } +#endif +#ifdef AXOM_RUNTIME_POLICY_USE_HIP + else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::hip) + { + impl = m_ndim == 2 + ? std::unique_ptr( + new MarchingCubesImpl<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)) + : std::unique_ptr( + new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)); + } +#endif + else + { + SLIC_ERROR(axom::fmt::format( + "MarchingCubesSingleDomain has no implementation for runtime policy {}", + m_runtimePolicy)); + } + return impl; +} + int32_t MarchingCubesSingleDomain::getDomainId( int32_t defaultId) const { @@ -100,3 +184,5 @@ int32_t MarchingCubesSingleDomain::getDomainId( } // end namespace quest } // end namespace axom +} // end namespace quest +} // end namespace axom diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index ecbe7b8757..1958339a0a 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -39,8 +39,6 @@ namespace marching_cubes { template class MarchingCubesImpl; -} // namespace marching_cubes -} // namespace detail /*! * \@brief Class implementing marching cubes algorithm for a single @@ -64,9 +62,7 @@ class MarchingCubesSingleDomain * with \c runtimePolicy. See \c esecution_space. * \param [in] dataPar Choice of data-parallel implementation. */ - MarchingCubesSingleDomain(RuntimePolicy runtimePolicy, - int allocatorID, - MarchingCubesDataParallelism dataPar); + MarchingCubesSingleDomain(MarchingCubes& mc); ~MarchingCubesSingleDomain() {} @@ -227,6 +223,9 @@ class MarchingCubesSingleDomain } private: + //!@brief Multi-somain implementation this object is under. + MarchingCubes& m_mc; + RuntimePolicy m_runtimePolicy; int m_allocatorID = axom::INVALID_ALLOCATOR_ID; @@ -262,8 +261,15 @@ class MarchingCubesSingleDomain */ void setDomain(const conduit::Node &dom); + /*! + @brief Allocate MarchingCubesImpl object + */ + std::unique_ptr newMarchingCubesImpl(); + }; // class MarchingCubesSingleDomain +} // end namespace marching_cubes +} // end namespace detail } // namespace quest } // namespace axom From 0076384afe04b3a164f79a8ba9edf9138d561086 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sun, 18 Feb 2024 20:49:07 -0800 Subject: [PATCH 30/61] More scratch data sharing to improve CUDA performance. Introduce smaller domain-specific m_crossingCases to let us share the much bigger m_caseIdsFlat array. --- src/axom/quest/MarchingCubes.hpp | 4 +- src/axom/quest/detail/MarchingCubesImpl.hpp | 52 ++++++++++++++------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 56da969a93..18eee70db7 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -127,7 +127,7 @@ class MarchingCubes environment. It's an error if not, e.g., using CPU memory with a GPU policy. - Some data from \a bpMesh may be cached by the constructor. + Some metadata from \a bpMesh may be cached by the constructor. Any change to it after the constructor leads to undefined behavior. The mesh coordinates should be contiguous. See @@ -311,7 +311,7 @@ class MarchingCubes axom::IndexType m_facetCount = 0; //@{ - //!@name Scratch space, shared among singles + //!@name Scratch space from m_allocatorID, shared among singles // Memory alloc is slow on CUDA, so this optimizes space AND time. axom::Array m_caseIdsFlat; axom::Array m_crossingFlags; diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index e5b3fbc187..1540da79ea 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -69,10 +69,12 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::Array& crossingFlags, axom::Array& scannedFlags) : m_allocatorID(allocatorID) - , m_caseIdsFlat(0, 0, m_allocatorID) , m_caseIds() - , m_crossingFlags(0, 0, m_allocatorID) - , m_scannedFlags(0, 0, m_allocatorID) + , m_caseIdsIndexer() + , m_caseIdsFlat(caseIdsFlat) + , m_crossingCases(0, 0, m_allocatorID) + , m_crossingFlags(crossingFlags) + , m_scannedFlags(scannedFlags) , m_crossingParentIds(0, 0, m_allocatorID) , m_facetIncrs(0, 0, m_allocatorID) , m_firstFacetIds(0, 0, m_allocatorID) @@ -382,6 +384,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::allocateIndexLists"); m_crossingParentIds.resize(m_crossingCount, 0); + m_crossingCases.resize(m_crossingCount, 0); m_facetIncrs.resize(m_crossingCount, 0); m_firstFacetIds.resize(1 + m_crossingCount, 0); } @@ -432,11 +435,12 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase sizeof(axom::IndexType)); // - // Generate crossing-cells index list and corresponding facet counts. + // Generate crossing info in compact arrays. // allocateIndexLists(); auto scannedFlagsView = m_scannedFlags.view(); auto crossingParentIdsView = m_crossingParentIds.view(); + auto crossingCasesView = m_crossingCases.view(); auto facetIncrsView = m_facetIncrs.view(); AXOM_PERF_MARK_SECTION( @@ -446,8 +450,10 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase if(scannedFlagsView[parentCellId] != scannedFlagsView[1 + parentCellId]) { auto crossingId = scannedFlagsView[parentCellId]; - auto facetIncr = num_contour_cells(caseIdsView.flatIndex(parentCellId)); + auto caseId = caseIdsView.flatIndex(parentCellId); + auto facetIncr = num_contour_cells(caseId); crossingParentIdsView[crossingId] = parentCellId; + crossingCasesView[crossingId] = caseId; facetIncrsView[crossingId] = facetIncr; } }; @@ -507,11 +513,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase // // Allocate space for crossing info // - m_crossingParentIds.resize(m_crossingCount); - m_facetIncrs.resize(m_crossingCount); - m_firstFacetIds.resize(1 + m_crossingCount); - + allocateIndexLists(); auto crossingParentIdsView = m_crossingParentIds.view(); + auto crossingCasesView = m_crossingCases.view(); auto facetIncrsView = m_facetIncrs.view(); axom::IndexType* crossingId = axom::allocate( @@ -524,8 +528,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase auto ccc = num_contour_cells(caseId); if(ccc != 0) { - facetIncrsView[*crossingId] = ccc; crossingParentIdsView[*crossingId] = n; + crossingCasesView[*crossingId] = caseId; + facetIncrsView[*crossingId] = ccc; ++(*crossingId); } }; @@ -581,6 +586,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase const auto facetIncrsView = m_facetIncrs.view(); const auto firstFacetIdsView = m_firstFacetIds.view(); const auto crossingParentIdsView = m_crossingParentIds.view(); + const auto crossingCasesView = m_crossingCases.view(); const auto caseIdsView = m_caseIds; // Internal contour mesh data to populate @@ -597,7 +603,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase auto gen_for_parent_cell = AXOM_LAMBDA(axom::IndexType crossingId) { auto parentCellId = crossingParentIdsView[crossingId]; - auto caseId = caseIdsView.flatIndex(parentCellId); + auto caseId = crossingCasesView[crossingId]; + // auto caseId = caseIdsView.flatIndex(parentCellId); Point cornerCoords[CELL_CORNER_COUNT]; double cornerValues[CELL_CORNER_COUNT]; cfu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); @@ -920,11 +927,16 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::ArrayView m_fcnView; axom::ArrayView m_maskView; - //!@brief Crossing case for each computational mesh cell. - axom::Array m_caseIdsFlat; + /*! + @brief Crossing case for each computational mesh cell. + + This is a multidim view into 1D data from m_caseIdsFlat, + set up with help from m_caseIdsIndexer. + */ axom::ArrayView m_caseIds; /*! - @brief Multi-dim indexer to control m_caseIdsFlat data ordering. + @brief Multidim indexer to handle data ordering in + m_caseIdsFlat. We want caseIds ordering to match m_fcnView, but Array only supports column-major ordering currently. To control @@ -933,11 +945,19 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase */ axom::ArrayIndexer m_caseIdsIndexer; + // Array references refer to shared Arrays in MarchingCubes. + + //!@brief Crossing case for each computational mesh cell. + axom::Array& m_caseIdsFlat; + + //!@brief Case ids for found crossings. + axom::Array m_crossingCases; + //!@brief Whether a parent cell crosses the contour. - axom::Array m_crossingFlags; + axom::Array& m_crossingFlags; //!@brief Prefix sum of m_crossingFlags - axom::Array m_scannedFlags; + axom::Array& m_scannedFlags; //!@brief Number of parent cells crossing the contour surface. axom::IndexType m_crossingCount = 0; From 7f7acaf5b53c179e940d30f7cb2eb5d1c82a8491 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sun, 18 Feb 2024 21:43:22 -0800 Subject: [PATCH 31/61] Autoformat. --- src/axom/quest/MarchingCubes.cpp | 41 ++++---- src/axom/quest/MarchingCubes.hpp | 3 +- src/axom/quest/detail/MarchingCubesImpl.hpp | 58 +++++------- .../detail/MarchingCubesSingleDomain.cpp | 94 +++++++++---------- .../detail/MarchingCubesSingleDomain.hpp | 17 ++-- .../examples/quest_marching_cubes_example.cpp | 28 +++--- 6 files changed, 120 insertions(+), 121 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 420b0bc2f1..906d0840a6 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -21,7 +21,7 @@ namespace axom { namespace quest { -const axom::StackArray twoZeros{0, 0}; +const axom::StackArray twoZeros {0, 0}; MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, int allocatorID, @@ -43,14 +43,12 @@ MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, , m_facetNodeIds(twoZeros, m_allocatorID) , m_facetNodeCoords(twoZeros, m_allocatorID) , m_facetParentIds(0, 0, m_allocatorID) -{ -} +{ } // Set the object up for a blueprint mesh state. -void MarchingCubes::initialize( - const conduit::Node& bpMesh, - const std::string& topologyName, - const std::string& maskField) +void MarchingCubes::initialize(const conduit::Node& bpMesh, + const std::string& topologyName, + const std::string& maskField) { SLIC_ASSERT_MSG( conduit::blueprint::mesh::is_multi_domain(bpMesh), @@ -70,12 +68,13 @@ void MarchingCubes::initialize( */ auto newDomainCount = conduit::blueprint::mesh::number_of_domains(bpMesh); - while( m_singles.size() < newDomainCount ) + while(m_singles.size() < newDomainCount) { - m_singles.emplace_back(new detail::marching_cubes::MarchingCubesSingleDomain(*this)); + m_singles.emplace_back( + new detail::marching_cubes::MarchingCubesSingleDomain(*this)); } - for (int d = 0; d < newDomainCount; ++d) + for(int d = 0; d < newDomainCount; ++d) { const auto& dom = bpMesh.child(d); m_singles[d]->initialize(dom, m_topologyName, maskField); @@ -145,9 +144,8 @@ axom::IndexType MarchingCubes::getContourNodeCount() const Domain ids are provided as a new Array instead of ArrayView because we don't store it internally. */ -template -axom::Array MarchingCubes::getContourFacetDomainIds( - int allocatorID) const +template +axom::Array MarchingCubes::getContourFacetDomainIds(int allocatorID) const { // Put parent domain ids into a new Array. const axom::IndexType len = getContourCellCount(); @@ -157,7 +155,8 @@ axom::Array MarchingCubes::getContourFacetDomainIds( allocatorID != axom::INVALID_ALLOCATOR_ID ? allocatorID : m_allocatorID); for(int d = 0; d < m_singles.size(); ++d) { - DomainIdType domainId = static_cast(m_singles[d]->getDomainId(d)); + DomainIdType domainId = + static_cast(m_singles[d]->getDomainId(d)); axom::IndexType contourCellCount = m_singles[d]->getContourCellCount(); axom::IndexType offset = m_facetIndexOffsets[d]; axom::detail::ArrayOps::fill( @@ -260,7 +259,8 @@ void MarchingCubes::populateContourMesh( // Put parent domain ids into the mesh. auto* domainIdPtr = mesh.getFieldPtr(domainIdField, axom::mint::CELL_CENTERED); - auto tmpContourFacetDomainIds = getContourFacetDomainIds(hostAllocatorId); + auto tmpContourFacetDomainIds = + getContourFacetDomainIds(hostAllocatorId); axom::copy(domainIdPtr, tmpContourFacetDomainIds.data(), m_facetCount * sizeof(DomainIdType)); @@ -275,9 +275,14 @@ void MarchingCubes::allocateOutputBuffers() { int ndim = m_singles[0]->spatialDimension(); const auto nodeCount = m_facetCount * ndim; - m_facetNodeIds.resize(axom::StackArray{m_facetCount, ndim}, 0); - m_facetNodeCoords.resize(axom::StackArray{nodeCount, ndim}, 0.0); - m_facetParentIds.resize(axom::StackArray{m_facetCount}, 0); + m_facetNodeIds.resize( + axom::StackArray {m_facetCount, ndim}, + 0); + m_facetNodeCoords.resize( + axom::StackArray {nodeCount, ndim}, + 0.0); + m_facetParentIds.resize(axom::StackArray {m_facetCount}, + 0); } } diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 18eee70db7..fd9e90e1f9 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -58,7 +58,6 @@ enum class MarchingCubesDataParallelism fullParallel = 2 }; - /*! * \@brief Class implementing marching cubes to compute a contour * mesh from a scalar function on an input mesh. @@ -238,7 +237,7 @@ class MarchingCubes The buffer size is getContourCellCount(). */ - template + template axom::Array getContourFacetDomainIds( int allocatorID = axom::INVALID_ALLOCATOR_ID) const; diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 1540da79ea..284c3578c4 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -52,12 +52,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase using LoopPolicy = typename execution_space::loop_policy; using ReducePolicy = typename execution_space::reduce_policy; #if defined(AXOM_USE_RAJA) - // Intel oneAPI compiler segfaults with OpenMP RAJA scan + // Intel oneAPI compiler segfaults with OpenMP RAJA scan #ifdef __INTEL_LLVM_COMPILER - using ScanPolicy = - typename axom::execution_space::loop_policy; + using ScanPolicy = typename axom::execution_space::loop_policy; #else - using ScanPolicy = typename axom::execution_space::loop_policy; + using ScanPolicy = typename axom::execution_space::loop_policy; #endif #endif using SequentialLoopPolicy = @@ -368,15 +367,13 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase (m_dataParallelism == axom::quest::MarchingCubesDataParallelism::byPolicy && autoPolicy == MarchingCubesDataParallelism::hybridParallel)) { - AXOM_PERF_MARK_SECTION( - "MarchingCubesImpl::scanCrossings:hybridParallel", - scanCrossings_hybridParallel();); + AXOM_PERF_MARK_SECTION("MarchingCubesImpl::scanCrossings:hybridParallel", + scanCrossings_hybridParallel();); } else { - AXOM_PERF_MARK_SECTION( - "MarchingCubesImpl::scanCrossings:fullParallel", - scanCrossings_fullParallel();); + AXOM_PERF_MARK_SECTION("MarchingCubesImpl::scanCrossings:fullParallel", + scanCrossings_fullParallel();); } } @@ -412,7 +409,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase auto numContourCells = num_contour_cells(caseIdsView.flatIndex(parentCellId)); crossingFlagsView[parentCellId] = bool(numContourCells); - });); + });); m_scannedFlags.fill(0, 1, 0); AXOM_PERF_MARK_SECTION( @@ -423,12 +420,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase RAJA::make_span(m_scannedFlags.data() + 1, parentCellCount), RAJA::operators::plus {}); #else - for(axom::IndexType n = 0; n < parentCellCount; ++n) - { + for(axom::IndexType n = 0; n < parentCellCount; ++n) { m_scannedFlags[n + 1] = m_scannedFlags[n] + m_crossingFlags[n]; } #endif - ); + ); axom::copy(&m_crossingCount, m_scannedFlags.data() + m_scannedFlags.size() - 1, @@ -445,18 +441,18 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase AXOM_PERF_MARK_SECTION( "MarchingCubesImpl::scanCrossings:set_incrs", - auto loopBody = AXOM_LAMBDA(axom::IndexType parentCellId) - { - if(scannedFlagsView[parentCellId] != scannedFlagsView[1 + parentCellId]) - { - auto crossingId = scannedFlagsView[parentCellId]; - auto caseId = caseIdsView.flatIndex(parentCellId); - auto facetIncr = num_contour_cells(caseId); - crossingParentIdsView[crossingId] = parentCellId; - crossingCasesView[crossingId] = caseId; - facetIncrsView[crossingId] = facetIncr; - } - }; + auto loopBody = + AXOM_LAMBDA(axom::IndexType parentCellId) { + if(scannedFlagsView[parentCellId] != scannedFlagsView[1 + parentCellId]) + { + auto crossingId = scannedFlagsView[parentCellId]; + auto caseId = caseIdsView.flatIndex(parentCellId); + auto facetIncr = num_contour_cells(caseId); + crossingParentIdsView[crossingId] = parentCellId; + crossingCasesView[crossingId] = caseId; + facetIncrsView[crossingId] = facetIncr; + } + }; axom::for_all(0, parentCellCount, loopBody);); // @@ -473,12 +469,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase RAJA::make_span(m_firstFacetIds.data() + 1, m_crossingCount), RAJA::operators::plus {}); #else - for(axom::IndexType n = 0; n < parentCellCount; ++n) - { + for(axom::IndexType n = 0; n < parentCellCount; ++n) { m_firstFacetIds[n + 1] = m_firstFacetIds[n] + m_facetIncrs[n]; } #endif - ); + ); axom::copy(&m_facetCount, m_firstFacetIds.data() + m_firstFacetIds.size() - 1, @@ -595,10 +590,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::ArrayView facetParentIdsView = m_facetParentIds; const axom::IndexType facetIndexOffset = m_facetIndexOffset; - ComputeFacets_Util cfu(m_contourVal, - m_caseIdsIndexer, - m_fcnView, - m_coordsViews); + ComputeFacets_Util cfu(m_contourVal, m_caseIdsIndexer, m_fcnView, m_coordsViews); auto gen_for_parent_cell = AXOM_LAMBDA(axom::IndexType crossingId) { diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp index 00544dcc46..55758ac743 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp @@ -24,9 +24,7 @@ namespace detail { namespace marching_cubes { - -MarchingCubesSingleDomain::MarchingCubesSingleDomain( - MarchingCubes& mc) +MarchingCubesSingleDomain::MarchingCubesSingleDomain(MarchingCubes& mc) : m_mc(mc) , m_runtimePolicy(mc.m_runtimePolicy) , m_allocatorID(mc.m_allocatorID) @@ -42,10 +40,9 @@ MarchingCubesSingleDomain::MarchingCubesSingleDomain( return; } -void MarchingCubesSingleDomain::initialize( - const conduit::Node& dom, - const std::string& topologyName, - const std::string& maskField) +void MarchingCubesSingleDomain::initialize(const conduit::Node& dom, + const std::string& topologyName, + const std::string& maskField) { m_topologyName = topologyName; @@ -100,30 +97,34 @@ MarchingCubesSingleDomain::newMarchingCubesImpl() { impl = m_ndim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::SEQ_EXEC, axom::SEQ_EXEC>(m_mc.m_allocatorID, - m_mc.m_caseIdsFlat, - m_mc.m_crossingFlags, - m_mc.m_scannedFlags)) + new MarchingCubesImpl<2, axom::SEQ_EXEC, axom::SEQ_EXEC>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)) : std::unique_ptr( - new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>(m_mc.m_allocatorID, - m_mc.m_caseIdsFlat, - m_mc.m_crossingFlags, - m_mc.m_scannedFlags)); + new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)); } #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::omp) { impl = m_ndim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::OMP_EXEC, axom::SEQ_EXEC>(m_mc.m_allocatorID, - m_mc.m_caseIdsFlat, - m_mc.m_crossingFlags, - m_mc.m_scannedFlags)) + new MarchingCubesImpl<2, axom::OMP_EXEC, axom::SEQ_EXEC>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)) : std::unique_ptr( - new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>(m_mc.m_allocatorID, - m_mc.m_caseIdsFlat, - m_mc.m_crossingFlags, - m_mc.m_scannedFlags)); + new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_CUDA @@ -131,17 +132,17 @@ MarchingCubesSingleDomain::newMarchingCubesImpl() { impl = m_ndim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( - m_mc.m_allocatorID, - m_mc.m_caseIdsFlat, - m_mc.m_crossingFlags, - m_mc.m_scannedFlags)) + new MarchingCubesImpl<2, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)) : std::unique_ptr( - new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( - m_mc.m_allocatorID, - m_mc.m_caseIdsFlat, - m_mc.m_crossingFlags, - m_mc.m_scannedFlags)); + new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_HIP @@ -149,17 +150,17 @@ MarchingCubesSingleDomain::newMarchingCubesImpl() { impl = m_ndim == 2 ? std::unique_ptr( - new MarchingCubesImpl<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( - m_mc.m_allocatorID, - m_mc.m_caseIdsFlat, - m_mc.m_crossingFlags, - m_mc.m_scannedFlags)) + new MarchingCubesImpl<2, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)) : std::unique_ptr( - new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( - m_mc.m_allocatorID, - m_mc.m_caseIdsFlat, - m_mc.m_crossingFlags, - m_mc.m_scannedFlags)); + new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( + m_mc.m_allocatorID, + m_mc.m_caseIdsFlat, + m_mc.m_crossingFlags, + m_mc.m_scannedFlags)); } #endif else @@ -171,8 +172,7 @@ MarchingCubesSingleDomain::newMarchingCubesImpl() return impl; } -int32_t MarchingCubesSingleDomain::getDomainId( - int32_t defaultId) const +int32_t MarchingCubesSingleDomain::getDomainId(int32_t defaultId) const { int rval = defaultId; if(m_dom->has_path("state/domain_id")) @@ -182,7 +182,7 @@ int32_t MarchingCubesSingleDomain::getDomainId( return rval; } -} // end namespace quest -} // end namespace axom +} // namespace marching_cubes +} // namespace detail } // end namespace quest } // end namespace axom diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index 1958339a0a..a61526d5d5 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -62,9 +62,9 @@ class MarchingCubesSingleDomain * with \c runtimePolicy. See \c esecution_space. * \param [in] dataPar Choice of data-parallel implementation. */ - MarchingCubesSingleDomain(MarchingCubes& mc); + MarchingCubesSingleDomain(MarchingCubes &mc); - ~MarchingCubesSingleDomain() {} + ~MarchingCubesSingleDomain() { } /*! @brief Intitialize object to a domain. @@ -86,9 +86,9 @@ class MarchingCubesSingleDomain requirement may be relaxed, possibly at the cost of a transformation and storage of the temporary contiguous layout. */ - void initialize( const conduit::Node &dom, - const std::string &topologyName, - const std::string &maskfield); + void initialize(const conduit::Node &dom, + const std::string &topologyName, + const std::string &maskfield); int spatialDimension() const { return m_ndim; } @@ -217,14 +217,11 @@ class MarchingCubesSingleDomain axom::IndexType m_facetIndexOffset = -1; }; - ImplBase& getImpl() - { - return *m_impl; - } + ImplBase &getImpl() { return *m_impl; } private: //!@brief Multi-somain implementation this object is under. - MarchingCubes& m_mc; + MarchingCubes &m_mc; RuntimePolicy m_runtimePolicy; int m_allocatorID = axom::INVALID_ALLOCATOR_ID; diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 459430eb83..0a024afad8 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -199,7 +199,8 @@ struct Input ->capture_default_str(); app.add_option("--contourGenReps", contourGenCount) - ->description("Number of contour repetitions to run for each MarchingCubes object") + ->description( + "Number of contour repetitions to run for each MarchingCubes object") ->capture_default_str(); app.get_formatter()->column_width(60); @@ -532,7 +533,7 @@ struct BlueprintStructuredMesh template void moveMeshDataToNewMemorySpace(const std::string& path, int allocId) { - AXOM_PERF_MARK_FUNCTION("moveMeshDataToNewMemorySpace"); // For reference + AXOM_PERF_MARK_FUNCTION("moveMeshDataToNewMemorySpace"); // For reference for(auto& dom : _mdMesh.children()) { moveConduitDataToNewMemorySpace(dom, path, allocId); @@ -762,7 +763,7 @@ struct ContourTestBase } #endif std::unique_ptr mcPtr; - for(int j=0; j(params.policy, @@ -777,9 +778,14 @@ struct ContourTestBase MPI_Barrier(MPI_COMM_WORLD); #endif computeTimer.start(); - for(int i=0; i Date: Mon, 19 Feb 2024 10:46:59 -0800 Subject: [PATCH 32/61] Move translation of data parallelism byPolicy out of the way. --- src/axom/quest/detail/MarchingCubesImpl.hpp | 38 +++++++++++-------- .../detail/MarchingCubesSingleDomain.hpp | 5 +-- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 284c3578c4..2dcfcaa4a6 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -116,6 +116,27 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase } } + AXOM_HOST void setDataParallelism(MarchingCubesDataParallelism dataPar) override + { + constexpr MarchingCubesDataParallelism autoPolicy = + std::is_same::value + ? MarchingCubesDataParallelism::hybridParallel + : +#if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) + std::is_same::value + ? MarchingCubesDataParallelism::hybridParallel + : +#endif + MarchingCubesDataParallelism::fullParallel; + + m_dataParallelism = dataPar; + + if(m_dataParallelism == axom::quest::MarchingCubesDataParallelism::byPolicy) + { + m_dataParallelism = autoPolicy; + } + } + /*! @brief Set the scale field name @param fcnFieldName Name of nodal function is in dom @@ -351,26 +372,13 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase void scanCrossings() override { AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::scanCrossings"); - constexpr MarchingCubesDataParallelism autoPolicy = - std::is_same::value - ? MarchingCubesDataParallelism::hybridParallel - : -#if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) - std::is_same::value - ? MarchingCubesDataParallelism::hybridParallel - : -#endif - MarchingCubesDataParallelism::fullParallel; - if(m_dataParallelism == - axom::quest::MarchingCubesDataParallelism::hybridParallel || - (m_dataParallelism == axom::quest::MarchingCubesDataParallelism::byPolicy && - autoPolicy == MarchingCubesDataParallelism::hybridParallel)) + axom::quest::MarchingCubesDataParallelism::hybridParallel) { AXOM_PERF_MARK_SECTION("MarchingCubesImpl::scanCrossings:hybridParallel", scanCrossings_hybridParallel();); } - else + else if(m_dataParallelism == axom::quest::MarchingCubesDataParallelism::fullParallel) { AXOM_PERF_MARK_SECTION("MarchingCubesImpl::scanCrossings:fullParallel", scanCrossings_fullParallel();); diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index a61526d5d5..a1fb0f388f 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -170,10 +170,7 @@ class MarchingCubesSingleDomain virtual void setFunctionField(const std::string &fcnFieldName) = 0; virtual void setContourValue(double contourVal) = 0; - void setDataParallelism(MarchingCubesDataParallelism dataPar) - { - m_dataParallelism = dataPar; - } + virtual void setDataParallelism(MarchingCubesDataParallelism dataPar) = 0; //@{ //!@name Distinct phases in contour generation. From 3476d612eabae9a7da677122c5c8000ab947f85f Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 19 Feb 2024 12:17:57 -0800 Subject: [PATCH 33/61] Use shared buffer for facetIncrs. --- src/axom/quest/MarchingCubes.cpp | 1 + src/axom/quest/MarchingCubes.hpp | 3 ++- src/axom/quest/detail/MarchingCubesImpl.hpp | 21 ++++++++-------- .../detail/MarchingCubesSingleDomain.cpp | 24 ++++++++++++------- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 906d0840a6..ac30955467 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -40,6 +40,7 @@ MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, , m_caseIdsFlat(0, 0, m_allocatorID) , m_crossingFlags(0, 0, m_allocatorID) , m_scannedFlags(0, 0, m_allocatorID) + , m_facetIncrs(0, 0, m_allocatorID) , m_facetNodeIds(twoZeros, m_allocatorID) , m_facetNodeCoords(twoZeros, m_allocatorID) , m_facetParentIds(0, 0, m_allocatorID) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index fd9e90e1f9..3c07683fe7 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -313,8 +313,9 @@ class MarchingCubes //!@name Scratch space from m_allocatorID, shared among singles // Memory alloc is slow on CUDA, so this optimizes space AND time. axom::Array m_caseIdsFlat; - axom::Array m_crossingFlags; + axom::Array m_crossingFlags; axom::Array m_scannedFlags; + axom::Array m_facetIncrs; //@} //@{ diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 2dcfcaa4a6..a53022ae5d 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -65,8 +65,9 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase AXOM_HOST MarchingCubesImpl(int allocatorID, axom::Array& caseIdsFlat, - axom::Array& crossingFlags, - axom::Array& scannedFlags) + axom::Array& crossingFlags, + axom::Array& scannedFlags, + axom::Array& facetIncrs) : m_allocatorID(allocatorID) , m_caseIds() , m_caseIdsIndexer() @@ -75,7 +76,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase , m_crossingFlags(crossingFlags) , m_scannedFlags(scannedFlags) , m_crossingParentIds(0, 0, m_allocatorID) - , m_facetIncrs(0, 0, m_allocatorID) + , m_facetIncrs(facetIncrs) // (0, 0, m_allocatorID) , m_firstFacetIds(0, 0, m_allocatorID) { SLIC_ASSERT(caseIdsFlat.getAllocatorID() == allocatorID); @@ -102,6 +103,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { // Time this due to potentially slow memory allocation AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::initialize"); + clear(); SLIC_ASSERT(conduit::blueprint::mesh::topology::dims(dom.fetch_existing( axom::fmt::format("topologies/{}", topologyName))) == DIM); @@ -486,7 +488,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::copy(&m_facetCount, m_firstFacetIds.data() + m_firstFacetIds.size() - 1, sizeof(axom::IndexType)); - m_firstFacetIds.resize(m_crossingCount); + // m_firstFacetIds.resize(m_crossingCount); } void scanCrossings_hybridParallel() @@ -580,17 +582,15 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::copy(&m_facetCount, m_firstFacetIds.data() + m_firstFacetIds.size() - 1, sizeof(axom::IndexType)); - m_firstFacetIds.resize(m_crossingCount); + // m_firstFacetIds.resize(m_crossingCount); } void computeFacets() override { AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::computeFacets"); - const auto facetIncrsView = m_facetIncrs.view(); const auto firstFacetIdsView = m_firstFacetIds.view(); const auto crossingParentIdsView = m_crossingParentIds.view(); const auto crossingCasesView = m_crossingCases.view(); - const auto caseIdsView = m_caseIds; // Internal contour mesh data to populate axom::ArrayView facetNodeIdsView = m_facetNodeIds; @@ -604,12 +604,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { auto parentCellId = crossingParentIdsView[crossingId]; auto caseId = crossingCasesView[crossingId]; - // auto caseId = caseIdsView.flatIndex(parentCellId); Point cornerCoords[CELL_CORNER_COUNT]; double cornerValues[CELL_CORNER_COUNT]; cfu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); - auto additionalFacets = facetIncrsView[crossingId]; + auto additionalFacets = firstFacetIdsView[crossingId+1] - firstFacetIdsView[crossingId]; auto firstFacetId = facetIndexOffset + firstFacetIdsView[crossingId]; for(axom::IndexType fId = 0; fId < additionalFacets; ++fId) @@ -954,7 +953,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::Array m_crossingCases; //!@brief Whether a parent cell crosses the contour. - axom::Array& m_crossingFlags; + axom::Array& m_crossingFlags; //!@brief Prefix sum of m_crossingFlags axom::Array& m_scannedFlags; @@ -970,7 +969,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::Array m_crossingParentIds; //!@brief Number of surface mesh facets added by each crossing. - axom::Array m_facetIncrs; + axom::Array& m_facetIncrs; //!@brief First index of facets for each crossing. axom::Array m_firstFacetIds; diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp index 55758ac743..517e4f89ea 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp @@ -101,13 +101,15 @@ MarchingCubesSingleDomain::newMarchingCubesImpl() m_mc.m_allocatorID, m_mc.m_caseIdsFlat, m_mc.m_crossingFlags, - m_mc.m_scannedFlags)) + m_mc.m_scannedFlags, + m_mc.m_facetIncrs)) : std::unique_ptr( new MarchingCubesImpl<3, axom::SEQ_EXEC, axom::SEQ_EXEC>( m_mc.m_allocatorID, m_mc.m_caseIdsFlat, m_mc.m_crossingFlags, - m_mc.m_scannedFlags)); + m_mc.m_scannedFlags, + m_mc.m_facetIncrs)); } #ifdef AXOM_RUNTIME_POLICY_USE_OPENMP else if(m_runtimePolicy == MarchingCubes::RuntimePolicy::omp) @@ -118,13 +120,15 @@ MarchingCubesSingleDomain::newMarchingCubesImpl() m_mc.m_allocatorID, m_mc.m_caseIdsFlat, m_mc.m_crossingFlags, - m_mc.m_scannedFlags)) + m_mc.m_scannedFlags, + m_mc.m_facetIncrs)) : std::unique_ptr( new MarchingCubesImpl<3, axom::OMP_EXEC, axom::SEQ_EXEC>( m_mc.m_allocatorID, m_mc.m_caseIdsFlat, m_mc.m_crossingFlags, - m_mc.m_scannedFlags)); + m_mc.m_scannedFlags, + m_mc.m_facetIncrs)); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_CUDA @@ -136,13 +140,15 @@ MarchingCubesSingleDomain::newMarchingCubesImpl() m_mc.m_allocatorID, m_mc.m_caseIdsFlat, m_mc.m_crossingFlags, - m_mc.m_scannedFlags)) + m_mc.m_scannedFlags, + m_mc.m_facetIncrs)) : std::unique_ptr( new MarchingCubesImpl<3, axom::CUDA_EXEC<256>, axom::CUDA_EXEC<1>>( m_mc.m_allocatorID, m_mc.m_caseIdsFlat, m_mc.m_crossingFlags, - m_mc.m_scannedFlags)); + m_mc.m_scannedFlags, + m_mc.m_facetIncrs)); } #endif #ifdef AXOM_RUNTIME_POLICY_USE_HIP @@ -154,13 +160,15 @@ MarchingCubesSingleDomain::newMarchingCubesImpl() m_mc.m_allocatorID, m_mc.m_caseIdsFlat, m_mc.m_crossingFlags, - m_mc.m_scannedFlags)) + m_mc.m_scannedFlags, + m_mc.m_facetIncrs)) : std::unique_ptr( new MarchingCubesImpl<3, axom::HIP_EXEC<256>, axom::HIP_EXEC<1>>( m_mc.m_allocatorID, m_mc.m_caseIdsFlat, m_mc.m_crossingFlags, - m_mc.m_scannedFlags)); + m_mc.m_scannedFlags, + m_mc.m_facetIncrs)); } #endif else From 0146b1e1563679b90dd665d339f5853e61427fdc Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 19 Feb 2024 14:05:47 -0800 Subject: [PATCH 34/61] Reuse =MarchingCubes= object for better performance. --- .../examples/quest_marching_cubes_example.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 0a024afad8..f28d4c9d3b 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -762,14 +762,21 @@ struct ContourTestBase } } #endif + std::unique_ptr mcPtr; for(int j = 0; j < params.objectRepCount; ++j) { - // Create marching cubes algorithm object and set some parameters - mcPtr = std::make_unique(params.policy, - s_allocatorId, - params.dataParallelism); + // Clear and re-use MarchingCubes object. + if(!mcPtr) + { + mcPtr = std::make_unique(params.policy, + s_allocatorId, + params.dataParallelism); + } + mcPtr->clear(); + auto& mc = *mcPtr; + mc.initialize(computationalMesh.asConduitNode(), "mesh"); mc.setFunctionField(functionName()); From 6a2ad712368f357ad7e56326b53ffb3475aee89c Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 19 Feb 2024 15:58:28 -0800 Subject: [PATCH 35/61] Autoformat. --- src/axom/quest/detail/MarchingCubesImpl.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index a53022ae5d..2832fd36d4 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -76,7 +76,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase , m_crossingFlags(crossingFlags) , m_scannedFlags(scannedFlags) , m_crossingParentIds(0, 0, m_allocatorID) - , m_facetIncrs(facetIncrs) // (0, 0, m_allocatorID) + , m_facetIncrs(facetIncrs) // (0, 0, m_allocatorID) , m_firstFacetIds(0, 0, m_allocatorID) { SLIC_ASSERT(caseIdsFlat.getAllocatorID() == allocatorID); @@ -375,12 +375,13 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase { AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::scanCrossings"); if(m_dataParallelism == - axom::quest::MarchingCubesDataParallelism::hybridParallel) + axom::quest::MarchingCubesDataParallelism::hybridParallel) { AXOM_PERF_MARK_SECTION("MarchingCubesImpl::scanCrossings:hybridParallel", scanCrossings_hybridParallel();); } - else if(m_dataParallelism == axom::quest::MarchingCubesDataParallelism::fullParallel) + else if(m_dataParallelism == + axom::quest::MarchingCubesDataParallelism::fullParallel) { AXOM_PERF_MARK_SECTION("MarchingCubesImpl::scanCrossings:fullParallel", scanCrossings_fullParallel();); @@ -608,7 +609,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase double cornerValues[CELL_CORNER_COUNT]; cfu.get_corner_coords_and_values(parentCellId, cornerCoords, cornerValues); - auto additionalFacets = firstFacetIdsView[crossingId+1] - firstFacetIdsView[crossingId]; + auto additionalFacets = + firstFacetIdsView[crossingId + 1] - firstFacetIdsView[crossingId]; auto firstFacetId = facetIndexOffset + firstFacetIdsView[crossingId]; for(axom::IndexType fId = 0; fId < additionalFacets; ++fId) From 3b8e9977d62c5bff0bc1fa67a3767de383b6f1d3 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 19 Feb 2024 23:03:49 -0800 Subject: [PATCH 36/61] Time all reps. --- .../examples/quest_marching_cubes_example.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index f28d4c9d3b..7b601403d7 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -764,6 +764,8 @@ struct ContourTestBase #endif std::unique_ptr mcPtr; + axom::utilities::Timer repsTimer(false); + repsTimer.start(); for(int j = 0; j < params.objectRepCount; ++j) { // Clear and re-use MarchingCubes object. @@ -780,14 +782,12 @@ struct ContourTestBase mc.initialize(computationalMesh.asConduitNode(), "mesh"); mc.setFunctionField(functionName()); - axom::utilities::Timer computeTimer(false); #ifdef AXOM_USE_MPI MPI_Barrier(MPI_COMM_WORLD); #endif - computeTimer.start(); for(int i = 0; i < params.contourGenCount; ++i) { - SLIC_INFO(axom::fmt::format( + SLIC_DEBUG(axom::fmt::format( "MarchingCubes object rep {} of {}, contour run {} of {}:", j, params.objectRepCount, @@ -796,11 +796,14 @@ struct ContourTestBase mc.clear(); mc.computeIsocontour(params.contourVal); } - computeTimer.stop(); - // printTimingStats(computeTimer, name() + " contour"); - - printRunStats(mc); } + repsTimer.stop(); + SLIC_INFO(axom::fmt::format("Finished {} object reps x {} contour reps", + params.objectRepCount, params.contourGenCount)); + printTimingStats(repsTimer, name() + " contour"); + + auto& mc = *mcPtr; + printRunStats(mc); // Return conduit data to host memory. if(s_allocatorId != axom::execution_space::allocatorID()) @@ -825,7 +828,6 @@ struct ContourTestBase DIM, DIM == 2 ? mint::CellType::SEGMENT : mint::CellType::TRIANGLE, meshGroup); - auto& mc = *mcPtr; axom::utilities::Timer extractTimer(false); extractTimer.start(); mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); From 779de44458989c5cad385d9dbe644c34c71f4007 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 21 Feb 2024 15:32:37 -0800 Subject: [PATCH 37/61] Temporary work-around for un-related bug being addressed by PR #1271. --- .../examples/quest_marching_cubes_example.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 7b601403d7..21055bdbb8 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -799,7 +799,8 @@ struct ContourTestBase } repsTimer.stop(); SLIC_INFO(axom::fmt::format("Finished {} object reps x {} contour reps", - params.objectRepCount, params.contourGenCount)); + params.objectRepCount, + params.contourGenCount)); printTimingStats(repsTimer, name() + " contour"); auto& mc = *mcPtr; @@ -823,11 +824,11 @@ struct ContourTestBase // Put contour mesh in a mint object for error checking and output. std::string sidreGroupName = name() + "_mesh"; sidre::DataStore objectDS; + // While awaiting fix for PR #1271, don't use Sidre storage in contourMesh. sidre::Group* meshGroup = objectDS.getRoot()->createGroup(sidreGroupName); axom::mint::UnstructuredMesh contourMesh( DIM, - DIM == 2 ? mint::CellType::SEGMENT : mint::CellType::TRIANGLE, - meshGroup); + DIM == 2 ? mint::CellType::SEGMENT : mint::CellType::TRIANGLE); axom::utilities::Timer extractTimer(false); extractTimer.start(); mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); @@ -846,10 +847,14 @@ struct ContourTestBase checkCellsContainingContour(computationalMesh, contourMesh); } - // Write contour mesh to file. - std::string outputName = name() + "_contour_mesh"; - saveMesh(*meshGroup, outputName); - SLIC_INFO(axom::fmt::format("Wrote {} contour in {}", name(), outputName)); + if(contourMesh.hasSidreGroup()) + { + assert(contourMesh.getSidreGroup() == meshGroup); + // Write contour mesh to file. + std::string outputName = name() + "_contour_mesh"; + saveMesh(*contourMesh.getSidreGroup(), outputName); + SLIC_INFO(axom::fmt::format("Wrote {} contour in {}", name(), outputName)); + } objectDS.getRoot()->destroyGroupAndData(sidreGroupName); From cb66d9fc575edba0b57f1d39857c3d947eefe933 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 21 Feb 2024 18:17:28 -0800 Subject: [PATCH 38/61] Help compiler select the right Array::resize overload method. When IndexType is 64 bits, and a 2D Array is resized given a StackArray and a 64-bit value, compiler gags trying to cast the StackArray into an integral type when it should just instantiate a resize method that takes the StackArray. --- src/axom/core/Array.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/axom/core/Array.hpp b/src/axom/core/Array.hpp index aaf9f81dc3..ad6ececbf0 100644 --- a/src/axom/core/Array.hpp +++ b/src/axom/core/Array.hpp @@ -723,7 +723,10 @@ class Array : public ArrayBase> * * \note Reallocation is done if the new size will exceed the capacity. */ - template > + template < + typename... Args, + typename Enable = typename std::enable_if< + sizeof...(Args) == DIM && detail::all_types_are_integral::value>::type> void resize(Args... args) { static_assert(std::is_default_constructible::value, @@ -735,7 +738,10 @@ class Array : public ArrayBase> } /// \overload - template > + template < + typename... Args, + typename Enable = typename std::enable_if< + sizeof...(Args) == DIM && detail::all_types_are_integral::value>::type> void resize(ArrayOptions::Uninitialized, Args... args) { const StackArray dims {{static_cast(args)...}}; From 1422a6e72092a8b0166f1a482085fde3521e88cf Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 21 Feb 2024 22:51:38 -0800 Subject: [PATCH 39/61] Restore check that facets aren't too far inside parent cell. Because parent cells smaller than the tolerance get inverted when contracted, I had disabled this test. Now, I have logic to limit the contraction to prevent inversion. --- src/axom/primal/geometry/Vector.hpp | 44 ++++++++++++++++++- .../detail/MarchingCubesSingleDomain.hpp | 1 - .../examples/quest_marching_cubes_example.cpp | 32 +++++++++++--- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/axom/primal/geometry/Vector.hpp b/src/axom/primal/geometry/Vector.hpp index 2f1afc8362..08b000ab67 100644 --- a/src/axom/primal/geometry/Vector.hpp +++ b/src/axom/primal/geometry/Vector.hpp @@ -126,13 +126,31 @@ AXOM_HOST_DEVICE Vector operator*(const T scalar, /*! * \brief Scalar division of vector; Scalar on rhs. * \param [in] vec vector instance - * \param [in]n scalar user-supplied scalar. + * \param [in] scalar user-supplied scalar. * \return C resulting vector, \f$ \ni: C_i = vec_i / scalar, \forall i\f$ * \pre scalar != 0.0 */ template Vector operator/(const Vector& vec, const T scalar); +/*! + * \brief Element-wise < operator. + * \param [in] vec vector instance + * \param [in] scalar user-supplied scalar. + * \return Whether all vector elements are < a scalar. + */ +template +bool operator<(const Vector& vec, const T scalar); + +/*! + * \brief Element-wise >= operator. + * \param [in] vec vector instance + * \param [in] scalar user-supplied scalar. + * \return Whether all vector elements are >= a scalar. + */ +template +bool operator>=(const Vector& vec, const T scalar); + /*! * \brief Overloaded output operator for vectors * \param [in] os C++ output stream @@ -698,6 +716,30 @@ std::ostream& operator<<(std::ostream& os, const Vector& vec) return os; } +//------------------------------------------------------------------------------ +template +inline bool operator<(const Vector& vec, const T scalar) +{ + bool result(true); + for(int d = 0; d < NDIMS; ++d) + { + result &= vec[d] < scalar; + } + return result; +} + +//------------------------------------------------------------------------------ +template +inline bool operator>=(const Vector& vec, const T scalar) +{ + bool result(true); + for(int d = 0; d < NDIMS; ++d) + { + result &= vec[d] >= scalar; + } + return result; +} + //------------------------------------------------------------------------------ template inline Vector Vector::make_vector(const T& x, diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index a1fb0f388f..7285134c9b 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -49,7 +49,6 @@ class MarchingCubesImpl; class MarchingCubesSingleDomain { public: - using DomainIdType = int; using RuntimePolicy = axom::runtime_policy::Policy; /*! * \brief Constructor for applying algorithm in a single domain. diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 21055bdbb8..d3eb7f839e 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -32,6 +32,7 @@ #include "axom/quest/MeshViewUtil.hpp" #include "axom/sidre.hpp" #include "axom/core/Types.hpp" +#include "axom/core/numerics/floating_point_limits.hpp" #include "conduit_blueprint.hpp" #include "conduit_relay_io_blueprint.hpp" @@ -828,7 +829,8 @@ struct ContourTestBase sidre::Group* meshGroup = objectDS.getRoot()->createGroup(sidreGroupName); axom::mint::UnstructuredMesh contourMesh( DIM, - DIM == 2 ? mint::CellType::SEGMENT : mint::CellType::TRIANGLE); + DIM == 2 ? mint::CellType::SEGMENT : mint::CellType::TRIANGLE, + meshGroup); axom::utilities::Timer extractTimer(false); extractTimer.start(); mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); @@ -847,7 +849,7 @@ struct ContourTestBase checkCellsContainingContour(computationalMesh, contourMesh); } - if(contourMesh.hasSidreGroup()) + if (contourMesh.hasSidreGroup()) { assert(contourMesh.getSidreGroup() == meshGroup); // Write contour mesh to file. @@ -1183,9 +1185,16 @@ struct ContourTestBase upper[d] = coordsViews[d][upperIdx]; } axom::primal::BoundingBox parentCellBox(lower, upper); - double tol = errorTolerance(); + auto tol = axom::numerics::floating_point_limits::epsilon(); axom::primal::BoundingBox big(parentCellBox); big.expand(tol); + axom::primal::BoundingBox small(parentCellBox); + auto range = parentCellBox.range(); + bool checkSmall = range >= tol; + if (checkSmall) + { + small.expand(-tol); + } axom::IndexType* cellNodeIds = contourMesh.getCellNodeIDs(contourCellNum); const axom::IndexType cellNodeCount = @@ -1202,7 +1211,18 @@ struct ContourTestBase SLIC_INFO_IF( params.isVerbose(), axom::fmt::format("checkContourCellLimits: node {} at {} " - "is not on parent cell boundary.", + "too far outside parent cell boundary.", + cellNodeIds[nn], + nodeCoords)); + } + + if(checkSmall && small.contains(nodeCoords)) + { + ++errCount; + SLIC_INFO_IF( + params.isVerbose(), + axom::fmt::format("checkContourCellLimits: node {} at {} " + "too far inside parent cell boundary.", cellNodeIds[nn], nodeCoords)); } @@ -1530,7 +1550,9 @@ struct PlanarContourTest return std::string("dist_to_plane"); } - double errorTolerance() const override { return 1e-15; } + double errorTolerance() const override { + return axom::numerics::floating_point_limits::epsilon(); + } }; /// Utility function to transform blueprint node storage. From a2cf9d69fefc6ca53854cb46ad345f87b28e8092 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 27 Feb 2024 16:24:40 -0800 Subject: [PATCH 40/61] Store facet domain ids, as a prereq for supporting AMR. --- src/axom/quest/MarchingCubes.cpp | 49 +++++++++++--------------------- src/axom/quest/MarchingCubes.hpp | 14 ++++++--- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index ac30955467..ced9618343 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -44,6 +44,7 @@ MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, , m_facetNodeIds(twoZeros, m_allocatorID) , m_facetNodeCoords(twoZeros, m_allocatorID) , m_facetParentIds(0, 0, m_allocatorID) + , m_facetDomainIds(0, 0, m_allocatorID) { } // Set the object up for a blueprint mesh state. @@ -129,6 +130,17 @@ void MarchingCubes::computeIsocontour(double contourVal) { m_singles[d]->computeFacets(); } + + for(axom::IndexType d = 0; d < m_singles.size(); ++d) + { + const auto domainId = m_singles[d]->getDomainId(d); + const auto domainFacetCount = + ( d < m_singles.size() - 1 ? m_facetIndexOffsets[d+1] : m_facetCount ) + - m_facetIndexOffsets[d]; + m_facetDomainIds.fill(domainId, + domainFacetCount, + m_facetIndexOffsets[d]); + } } axom::IndexType MarchingCubes::getContourNodeCount() const @@ -141,35 +153,6 @@ axom::IndexType MarchingCubes::getContourNodeCount() const return contourNodeCount; } -/* - Domain ids are provided as a new Array instead of ArrayView because - we don't store it internally. -*/ -template -axom::Array MarchingCubes::getContourFacetDomainIds(int allocatorID) const -{ - // Put parent domain ids into a new Array. - const axom::IndexType len = getContourCellCount(); - axom::Array rval( - len, - len, - allocatorID != axom::INVALID_ALLOCATOR_ID ? allocatorID : m_allocatorID); - for(int d = 0; d < m_singles.size(); ++d) - { - DomainIdType domainId = - static_cast(m_singles[d]->getDomainId(d)); - axom::IndexType contourCellCount = m_singles[d]->getContourCellCount(); - axom::IndexType offset = m_facetIndexOffsets[d]; - axom::detail::ArrayOps::fill( - rval.data(), - offset, - contourCellCount, - allocatorID, - domainId); - } - return rval; -} - void MarchingCubes::clear() { for(int d = 0; d < m_singles.size(); ++d) @@ -260,11 +243,9 @@ void MarchingCubes::populateContourMesh( // Put parent domain ids into the mesh. auto* domainIdPtr = mesh.getFieldPtr(domainIdField, axom::mint::CELL_CENTERED); - auto tmpContourFacetDomainIds = - getContourFacetDomainIds(hostAllocatorId); axom::copy(domainIdPtr, - tmpContourFacetDomainIds.data(), - m_facetCount * sizeof(DomainIdType)); + m_facetDomainIds.data(), + m_facetCount * sizeof(axom::IndexType)); } } } @@ -284,6 +265,8 @@ void MarchingCubes::allocateOutputBuffers() 0.0); m_facetParentIds.resize(axom::StackArray {m_facetCount}, 0); + m_facetDomainIds.resize(axom::StackArray {m_facetCount}, + 0); } } diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 3c07683fe7..e6c20e4d15 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -115,7 +115,7 @@ class MarchingCubes The simplest policy is RuntimePolicy::seq, which specifies running sequentially on the CPU. \param [in] allocatorID Data allocator ID. Choose something compatible - with \c runtimePolicy. See \c esecution_space. + with \c runtimePolicy. See \c execution_space. \param [in] dataParallelism Data parallel implementation choice. \param [in] bpMesh Blueprint multi-domain mesh containing scalar field. \param [in] topologyName Name of Blueprint topology to use in \a bpMesh. @@ -237,9 +237,10 @@ class MarchingCubes The buffer size is getContourCellCount(). */ - template - axom::Array getContourFacetDomainIds( - int allocatorID = axom::INVALID_ALLOCATOR_ID) const; + axom::ArrayView getContourFacetDomainIds() const + { + return m_facetDomainIds.view(); + } #if 1 // Is there a use case for this? @@ -337,6 +338,11 @@ class MarchingCubes @see allocateOutputBuffers(). */ axom::Array m_facetParentIds; + + /*! + @brief Domain ids of facets. + */ + axom::Array m_facetDomainIds; //@} //!@brief Allocate output buffers corresponding to runtime policy. From effab869250d5e484e4fdb67272d6f9a0e422d18 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 27 Feb 2024 17:07:30 -0800 Subject: [PATCH 41/61] Change logic to allow building contour mesh from multiple inputs. This is part of the support for meshes represented by multiple conduit nodes (such as SAMR). However this support is still under development and not tested. --- src/axom/quest/MarchingCubes.cpp | 31 ++++++++++++------- src/axom/quest/MarchingCubes.hpp | 31 ++++++++++++++----- src/axom/quest/detail/MarchingCubesImpl.hpp | 10 +++--- .../detail/MarchingCubesSingleDomain.cpp | 8 ++--- .../detail/MarchingCubesSingleDomain.hpp | 14 ++++----- .../examples/quest_marching_cubes_example.cpp | 8 ++--- 6 files changed, 62 insertions(+), 40 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index ced9618343..4083e5039c 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -48,15 +48,15 @@ MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy, { } // Set the object up for a blueprint mesh state. -void MarchingCubes::initialize(const conduit::Node& bpMesh, - const std::string& topologyName, - const std::string& maskField) +void MarchingCubes::setMesh(const conduit::Node& bpMesh, + const std::string& topologyName, + const std::string& maskField) { SLIC_ASSERT_MSG( conduit::blueprint::mesh::is_multi_domain(bpMesh), "MarchingCubes class input mesh must be in multidomain format."); - clear(); + clearMesh(); m_topologyName = topologyName; m_maskFieldName = maskField; @@ -64,9 +64,9 @@ void MarchingCubes::initialize(const conduit::Node& bpMesh, /* To avoid slow memory allocations (especially on GPUs) keep the single-domain objects around and just re-initialize them. Arrays - will be cleared, but not deallocated. To really deallocate - memory, deallocate the MarchingCubes object. The actual number - of domains is m_domainCount, not m_singles.size(). + will be cleared, but not deallocated. The actual number of + domains is m_domainCount, not m_singles.size(). To *really* + deallocate memory, deallocate the MarchingCubes object. */ auto newDomainCount = conduit::blueprint::mesh::number_of_domains(bpMesh); @@ -79,7 +79,7 @@ void MarchingCubes::initialize(const conduit::Node& bpMesh, for(int d = 0; d < newDomainCount; ++d) { const auto& dom = bpMesh.child(d); - m_singles[d]->initialize(dom, m_topologyName, maskField); + m_singles[d]->setDomain(dom, m_topologyName, maskField); } m_domainCount = newDomainCount; @@ -98,10 +98,10 @@ void MarchingCubes::setFunctionField(const std::string& fcnField) void MarchingCubes::computeIsocontour(double contourVal) { AXOM_PERF_MARK_FUNCTION("MarchingCubes::computeIsoContour"); + // Mark and scan domains while adding up their // facet counts to get the total facet counts. m_facetIndexOffsets.resize(m_singles.size()); - m_facetCount = 0; for(axom::IndexType d = 0; d < m_singles.size(); ++d) { auto& single = *m_singles[d]; @@ -153,11 +153,11 @@ axom::IndexType MarchingCubes::getContourNodeCount() const return contourNodeCount; } -void MarchingCubes::clear() +void MarchingCubes::clearMesh() { for(int d = 0; d < m_singles.size(); ++d) { - m_singles[d]->getImpl().clear(); + m_singles[d]->getImpl().clearDomain(); } m_domainCount = 0; m_facetNodeIds.clear(); @@ -165,6 +165,15 @@ void MarchingCubes::clear() m_facetParentIds.clear(); } +void MarchingCubes::clearOutput() +{ + m_facetCount = 0; + m_facetNodeIds.clear(); + m_facetNodeCoords.clear(); + m_facetParentIds.clear(); + m_facetDomainIds.clear(); +} + void MarchingCubes::populateContourMesh( axom::mint::UnstructuredMesh& mesh, const std::string& cellIdField, diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index e6c20e4d15..2eefd45860 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -143,9 +143,14 @@ class MarchingCubes int allocatorId, MarchingCubesDataParallelism dataParallelism); - void initialize(const conduit::Node &bpMesh, - const std::string &topologyName, - const std::string &maskField = {}); + /*! + @brief Set the input mesh. + + @see clearMesh() + */ + void setMesh(const conduit::Node &bpMesh, + const std::string &topologyName, + const std::string &maskField = {}); /*! @brief Set the field containing the nodal function. @@ -156,7 +161,10 @@ class MarchingCubes /*! \brief Computes the isocontour. \param [in] contourVal isocontour value - */ + + Each computeIsocontour call adds to previously computed contour + mesh. + */ void computeIsocontour(double contourVal = 0.0); //!@brief Get number of cells in the generated contour mesh. @@ -268,15 +276,22 @@ class MarchingCubes //@} /*! - @brief Clear computed data. + @brief Clear the input mesh data. - After clearing, you have to call initialize as if it - was a new object. + The contour mesh is *not* cleared. See clearOutput() for this. + + After clearing, you have to call setMesh() as if it was a new + object. @internal For good GPU performance, memory is not deallocated. To really deallocate memory, destruct this object and use another. */ - void clear(); + void clearMesh(); + + /*! + @brief Clear the computed contour mesh. + */ + void clearOutput(); // Allow single-domain code to share common scratch space. friend detail::marching_cubes::MarchingCubesSingleDomain; diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 2832fd36d4..5a6940fbf7 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -97,13 +97,13 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase The above data from the domain MUST be in a memory space compatible with ExecSpace. */ - AXOM_HOST void initialize(const conduit::Node& dom, - const std::string& topologyName, - const std::string& maskFieldName) override + AXOM_HOST void setDomain(const conduit::Node& dom, + const std::string& topologyName, + const std::string& maskFieldName) override { // Time this due to potentially slow memory allocation AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::initialize"); - clear(); + clearDomain(); SLIC_ASSERT(conduit::blueprint::mesh::topology::dims(dom.fetch_existing( axom::fmt::format("topologies/{}", topologyName))) == DIM); @@ -902,7 +902,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase After clearing, you can change the field, contour value and recompute the contour. */ - void clear() override + void clearDomain() override { m_caseIdsFlat.clear(); m_crossingFlags.clear(); diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp index 517e4f89ea..ae711c8462 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp @@ -40,9 +40,9 @@ MarchingCubesSingleDomain::MarchingCubesSingleDomain(MarchingCubes& mc) return; } -void MarchingCubesSingleDomain::initialize(const conduit::Node& dom, - const std::string& topologyName, - const std::string& maskField) +void MarchingCubesSingleDomain::setDomain(const conduit::Node& dom, + const std::string& topologyName, + const std::string& maskField) { m_topologyName = topologyName; @@ -80,7 +80,7 @@ void MarchingCubesSingleDomain::initialize(const conduit::Node& dom, m_impl = newMarchingCubesImpl(); - m_impl->initialize(dom, topologyName, maskField); + m_impl->setDomain(dom, topologyName, maskField); m_impl->setDataParallelism(m_dataParallelism); } diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index 7285134c9b..24045d2325 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -85,9 +85,9 @@ class MarchingCubesSingleDomain requirement may be relaxed, possibly at the cost of a transformation and storage of the temporary contiguous layout. */ - void initialize(const conduit::Node &dom, - const std::string &topologyName, - const std::string &maskfield); + void setDomain(const conduit::Node &dom, + const std::string &topologyName, + const std::string &maskfield); int spatialDimension() const { return m_ndim; } @@ -162,9 +162,9 @@ class MarchingCubesSingleDomain Put in here codes that can't be in MarchingCubesSingleDomain due to template use (DIM and ExecSpace). */ - virtual void initialize(const conduit::Node &dom, - const std::string &topologyName, - const std::string &maskPath = {}) = 0; + virtual void setDomain(const conduit::Node &dom, + const std::string &topologyName, + const std::string &maskPath = {}) = 0; virtual void setFunctionField(const std::string &fcnFieldName) = 0; virtual void setContourValue(double contourVal) = 0; @@ -201,7 +201,7 @@ class MarchingCubesSingleDomain virtual ~ImplBase() { } - virtual void clear() = 0; + virtual void clearDomain() = 0; MarchingCubesDataParallelism m_dataParallelism = MarchingCubesDataParallelism::byPolicy; diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index d3eb7f839e..fd01afea6f 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -769,18 +769,16 @@ struct ContourTestBase repsTimer.start(); for(int j = 0; j < params.objectRepCount; ++j) { - // Clear and re-use MarchingCubes object. if(!mcPtr) { mcPtr = std::make_unique(params.policy, s_allocatorId, params.dataParallelism); } - mcPtr->clear(); - auto& mc = *mcPtr; - mc.initialize(computationalMesh.asConduitNode(), "mesh"); + // Clear and set MarchingCubes object for a "new" mesh. + mc.setMesh(computationalMesh.asConduitNode(), "mesh"); mc.setFunctionField(functionName()); #ifdef AXOM_USE_MPI @@ -794,7 +792,7 @@ struct ContourTestBase params.objectRepCount, i, params.contourGenCount)); - mc.clear(); + mc.clearOutput(); mc.computeIsocontour(params.contourVal); } } From d38eb1e0e2185c0b6b2a0dbebbcb62c478c46641 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 27 Feb 2024 19:13:16 -0800 Subject: [PATCH 42/61] Switch from functors to strategy pattern. --- .../examples/quest_marching_cubes_example.cpp | 307 +++++++----------- 1 file changed, 114 insertions(+), 193 deletions(-) diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index fd01afea6f..165b7d96b0 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -674,40 +674,67 @@ static void addToStackArray(axom::StackArray& a, U b) } } +/*! + @brief Strategy pattern for supporting different contour types. +*/ +template +struct ContourTestStrategy +{ + using PointType = axom::primal::Point; + //!@brief Return test name. + virtual std::string testName() const = 0; + + //!@brief Return field name for storing nodal function. + virtual std::string functionName() const = 0; + + //!@brief Return error tolerance for contour mesh accuracy check. + virtual double errorTolerance() const = 0; + + //!@brief Return the analytical value of the scalar field. + virtual double valueAt(const PointType& pt) const = 0; + + virtual ~ContourTestStrategy() {} +}; + /** ValueFunctorType is a copy-able functor that returns the scalar function value. It should have operator(const PointType &) return a double. */ -template +template struct ContourTestBase { static constexpr auto MemorySpace = axom::execution_space::memory_space; using PointType = axom::primal::Point; - ContourTestBase(const ValueFunctorType& valueFunctor) - : m_parentCellIdField("parentCellIds") + ContourTestBase(const std::shared_ptr>& testStrategy) + : m_testStrategy(testStrategy) + , m_parentCellIdField("parentCellIds") , m_domainIdField("domainIdField") - , m_valueFunctor(valueFunctor) { } virtual ~ContourTestBase() { } //!@brief Return field name for storing nodal function. - virtual std::string name() const = 0; + virtual std::string testName() const { return m_testStrategy->testName(); } //!@brief Return field name for storing nodal function. - virtual std::string functionName() const = 0; + virtual std::string functionName() const { return m_testStrategy->functionName(); } //!@brief Return error tolerance for contour mesh accuracy check. - virtual double errorTolerance() const = 0; + virtual double errorTolerance() const { return m_testStrategy->errorTolerance(); } + std::shared_ptr> getTestStrategy() const + { + return m_testStrategy; + } + + std::shared_ptr> m_testStrategy; const std::string m_parentCellIdField; const std::string m_domainIdField; - ValueFunctorType m_valueFunctor; int runTest(BlueprintStructuredMesh& computationalMesh) { - SLIC_INFO(banner(axom::fmt::format("Testing {} contour.", name()))); + SLIC_INFO(banner(axom::fmt::format("Testing {} contour.", testName()))); // Conduit data is in host memory, move to devices for testing. if(s_allocatorId != axom::execution_space::allocatorID()) @@ -800,7 +827,7 @@ struct ContourTestBase SLIC_INFO(axom::fmt::format("Finished {} object reps x {} contour reps", params.objectRepCount, params.contourGenCount)); - printTimingStats(repsTimer, name() + " contour"); + printTimingStats(repsTimer, testName() + " contour"); auto& mc = *mcPtr; printRunStats(mc); @@ -821,7 +848,7 @@ struct ContourTestBase } // Put contour mesh in a mint object for error checking and output. - std::string sidreGroupName = name() + "_mesh"; + std::string sidreGroupName = testName() + "_mesh"; sidre::DataStore objectDS; // While awaiting fix for PR #1271, don't use Sidre storage in contourMesh. sidre::Group* meshGroup = objectDS.getRoot()->createGroup(sidreGroupName); @@ -833,7 +860,7 @@ struct ContourTestBase extractTimer.start(); mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); extractTimer.stop(); - printTimingStats(extractTimer, name() + " extract"); + printTimingStats(extractTimer, testName() + " extract"); int localErrCount = 0; if(params.checkResults) @@ -851,9 +878,9 @@ struct ContourTestBase { assert(contourMesh.getSidreGroup() == meshGroup); // Write contour mesh to file. - std::string outputName = name() + "_contour_mesh"; + std::string outputName = testName() + "_contour_mesh"; saveMesh(*contourMesh.getSidreGroup(), outputName); - SLIC_INFO(axom::fmt::format("Wrote {} contour in {}", name(), outputName)); + SLIC_INFO(axom::fmt::format("Wrote {} contour in {}", testName(), outputName)); } objectDS.getRoot()->destroyGroupAndData(sidreGroupName); @@ -925,7 +952,6 @@ struct ContourTestBase } #if defined(AXOM_USE_RAJA) - auto valueFunctor = m_valueFunctor; RAJA::RangeSegment iRange(0, fieldShape[0]); RAJA::RangeSegment jRange(0, fieldShape[1]); using EXEC_POL = @@ -938,7 +964,7 @@ struct ContourTestBase { pt[d] = coordsViews[d](i, j); } - fieldView(i, j) = valueFunctor(pt); + fieldView(i, j) = m_testStrategy->valueAt(pt); }); #else for(axom::IndexType j = 0; j < fieldShape[1]; ++j) @@ -950,7 +976,7 @@ struct ContourTestBase { pt[d] = coordsViews[d](i, j); } - fieldView(i, j) = m_valueFunctor(pt); + fieldView(i, j) = m_testStrategy->valueAt(pt); } } #endif @@ -969,7 +995,6 @@ struct ContourTestBase } #if defined(AXOM_USE_RAJA) - auto valueFunctor = m_valueFunctor; RAJA::RangeSegment iRange(0, fieldShape[0]); RAJA::RangeSegment jRange(0, fieldShape[1]); RAJA::RangeSegment kRange(0, fieldShape[2]); @@ -983,7 +1008,7 @@ struct ContourTestBase { pt[d] = coordsViews[d](i, j, k); } - fieldView(i, j, k) = valueFunctor(pt); + fieldView(i, j, k) = m_testStrategy->valueAt(pt); }); #else for(axom::IndexType k = 0; k < fieldShape[2]; ++k) @@ -997,7 +1022,7 @@ struct ContourTestBase { pt[d] = coordsViews[d](i, j, k); } - fieldView(i, j, k) = m_valueFunctor(pt); + fieldView(i, j, k) = m_testStrategy->valueAt(pt); } } } @@ -1034,7 +1059,7 @@ struct ContourTestBase for(axom::IndexType i = 0; i < nodeCount; ++i) { contourMesh.getNode(i, pt.data()); - double analyticalVal = m_valueFunctor(pt); + double analyticalVal = m_testStrategy->valueAt(pt); double diff = std::abs(analyticalVal - contourVal); if(diffPtr) { @@ -1375,75 +1400,72 @@ struct ContourTestBase } }; -/*! - @brief Function providing distance from a point. -*/ template -struct RoundFunctor -{ +struct PlanarTestStrategy : public ContourTestStrategy { using PointType = axom::primal::Point; - const axom::primal::Sphere _sphere; - RoundFunctor(const PointType& center) : _sphere(center, 0.0) { } - AXOM_HOST_DEVICE double operator()(const PointType& pt) const + PlanarTestStrategy(const axom::primal::Vector& perpDir, + const PointType& inPlane) + : ContourTestStrategy() + , _plane(perpDir.unitVector(), inPlane) + , _errTol(axom::numerics::floating_point_limits::epsilon()) + {} + virtual std::string testName() const override { return std::string("planar"); } + virtual std::string functionName() const override { - return _sphere.computeSignedDistance(pt); + return std::string("dist_to_plane"); } -}; -template -struct RoundContourTest - : public ContourTestBase> -{ - static constexpr auto MemorySpace = - axom::execution_space::memory_space; - using PointType = axom::primal::Point; - using FunctorType = RoundFunctor; - /*! - @brief Constructor. - - @param center [in] Center of ring or sphere - */ - RoundContourTest(const PointType& center) - : ContourTestBase(FunctorType(center)) - , _sphere(center, 0.0) - , _roundFunctor(center) - , _errTol(1e-3) - { } - virtual ~RoundContourTest() { } - const axom::primal::Sphere _sphere; - FunctorType _roundFunctor; + double errorTolerance() const override { return _errTol; } + virtual double valueAt(const PointType& pt) const override + { + return _plane.signedDistance(pt); + } + const axom::primal::Plane _plane; double _errTol; +}; - virtual std::string name() const override { return std::string("round"); } - +template +struct RoundTestStrategy : public ContourTestStrategy { + using PointType = axom::primal::Point; + RoundTestStrategy(const PointType& center) + : ContourTestStrategy() + , _sphere(center, 0.0) + , _errTol(1e-3) + {} + virtual std::string testName() const override { return std::string("round"); } virtual std::string functionName() const override { return std::string("dist_to_center"); } - double errorTolerance() const override { return _errTol; } - + virtual double valueAt(const PointType& pt) const override + { + return _sphere.computeSignedDistance(pt); + } void setToleranceByLongestEdge(const BlueprintStructuredMesh& bsm) { // Heuristic of appropriate error tolerance. double maxSpacing = bsm.maxSpacing(); _errTol = 0.1 * maxSpacing; } + const axom::primal::Sphere _sphere; + double _errTol; }; -/*! - @brief Function for approximate gyroid surface -*/ template -struct GyroidFunctor -{ +struct GyroidTestStrategy : public ContourTestStrategy { using PointType = axom::primal::Point; - const PointType _scale; - const double _offset; - GyroidFunctor(const PointType& scale, double offset) - : _scale(scale) - , _offset(offset) - { } - AXOM_HOST_DEVICE double operator()(const PointType& pt) const + GyroidTestStrategy(const PointType& scale, double offset) + : ContourTestStrategy() + , _scale(scale) + , _offset(offset) + {} + virtual std::string testName() const override { return std::string("gyroid"); } + virtual std::string functionName() const override + { + return std::string("gyroid_fcn"); + } + double errorTolerance() const override { return _errTol; } + virtual double valueAt(const PointType& pt) const override { if(DIM == 3) { @@ -1458,40 +1480,6 @@ struct GyroidFunctor sin(pt[1] * _scale[1]) + _offset; } } -}; -template -struct GyroidContourTest - : public ContourTestBase> -{ - static constexpr auto MemorySpace = - axom::execution_space::memory_space; - using PointType = axom::primal::Point; - using FunctorType = GyroidFunctor; - /*! - @brief Constructor. - - @param scale [in] Gyroid function scaling factors - */ - GyroidContourTest(const PointType& scale, double offset) - : ContourTestBase(FunctorType(scale, offset)) - , _scale(scale) - , _gyroidFunctor(scale, offset) - , _errTol(1e-3) - { } - virtual ~GyroidContourTest() { } - const PointType _scale; - FunctorType _gyroidFunctor; - double _errTol; - - virtual std::string name() const override { return std::string("gyroid"); } - - virtual std::string functionName() const override - { - return std::string("gyroid_fcn"); - } - - double errorTolerance() const override { return _errTol; } - void setToleranceByLongestEdge(const BlueprintStructuredMesh& bsm) { // Heuristic of appropriate error tolerance. @@ -1499,81 +1487,11 @@ struct GyroidContourTest axom::primal::Vector v(_scale); _errTol = 0.1 * v.norm() * maxSpacing; } + const PointType _scale; + const double _offset; + double _errTol; }; -/*! - @brief Function providing signed distance from a plane. -*/ -template -struct PlanarFunctor -{ - using PointType = axom::primal::Point; - const axom::primal::Plane _plane; - PlanarFunctor(const axom::primal::Vector& perpDir, - const PointType& inPlane) - : _plane(perpDir.unitVector(), inPlane) - { } - AXOM_HOST_DEVICE double operator()(const PointType& pt) const - { - return _plane.signedDistance(pt); - } -}; -template -struct PlanarContourTest - : public ContourTestBase> -{ - static constexpr auto MemorySpace = - axom::execution_space::memory_space; - using PointType = axom::primal::Point; - using FunctorType = PlanarFunctor; - /*! - @brief Constructor. - - @param inPlane [in] A point in the plane. - @param perpDir [in] Perpendicular direction on positive side. - */ - PlanarContourTest(const PointType& inPlane, - const axom::primal::Vector& perpDir) - : ContourTestBase( - FunctorType(perpDir.unitVector(), inPlane)) - , _plane(perpDir.unitVector(), inPlane) - { } - virtual ~PlanarContourTest() { } - const axom::primal::Plane _plane; - - virtual std::string name() const override { return std::string("planar"); } - - virtual std::string functionName() const override - { - return std::string("dist_to_plane"); - } - - double errorTolerance() const override { - return axom::numerics::floating_point_limits::epsilon(); - } -}; - -/// Utility function to transform blueprint node storage. -void makeCoordsContiguous(conduit::Node& coordValues) -{ - bool isInterleaved = conduit::blueprint::mcarray::is_interleaved(coordValues); - if(isInterleaved) - { - conduit::Node oldValues = coordValues; - conduit::blueprint::mcarray::to_contiguous(oldValues, coordValues); - } -} - -/// Utility function to transform blueprint node storage. -void makeCoordsInterleaved(conduit::Node& coordValues) -{ - bool isInterleaved = conduit::blueprint::mcarray::is_interleaved(coordValues); - if(!isInterleaved) - { - conduit::Node oldValues = coordValues; - conduit::blueprint::mcarray::to_interleaved(oldValues, coordValues); - } -} /// int allocatorIdToTest(axom::runtime_policy::Policy policy) @@ -1662,32 +1580,35 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) // params specify which tests to run. //--------------------------------------------------------------------------- - std::shared_ptr> roundTest; - std::shared_ptr> gyroidTest; - std::shared_ptr> planarTest; + std::shared_ptr> planarTest; + std::shared_ptr> roundTest; + std::shared_ptr> gyroidTest; + + if(params.usingPlanar()) + { + auto strat = std::make_shared>(params.planeNormal(), + params.inplanePoint()); + planarTest = std::make_shared>(strat); + planarTest->computeNodalDistance(computationalMesh); + } if(params.usingRound()) { - roundTest = std::make_shared>( - params.roundContourCenter()); - roundTest->setToleranceByLongestEdge(computationalMesh); + auto strat = std::make_shared>(params.roundContourCenter()); + strat->setToleranceByLongestEdge(computationalMesh); + roundTest = std::make_shared>(strat); roundTest->computeNodalDistance(computationalMesh); } + if(params.usingGyroid()) { - gyroidTest = std::make_shared>( - params.gyroidScaleFactor(), - params.contourVal); - gyroidTest->setToleranceByLongestEdge(computationalMesh); + auto strat = std::make_shared>(params.gyroidScaleFactor(), + params.contourVal); + strat->setToleranceByLongestEdge(computationalMesh); + gyroidTest = std::make_shared>(strat); gyroidTest->computeNodalDistance(computationalMesh); } - if(params.usingPlanar()) - { - planarTest = std::make_shared>( - params.inplanePoint(), - params.planeNormal()); - planarTest->computeNodalDistance(computationalMesh); - } + if(params.isVerbose()) { computationalMesh.printMeshInfo(); From 250c36e8b1161dbcf95a366e7244b505665b66e8 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 27 Feb 2024 20:48:19 -0800 Subject: [PATCH 43/61] Combine surface meshes to test logic for supporting SAMR. --- .../examples/quest_marching_cubes_example.cpp | 315 ++++++++++++------ 1 file changed, 214 insertions(+), 101 deletions(-) diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 165b7d96b0..dfe401b4a6 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -707,13 +707,15 @@ struct ContourTestBase static constexpr auto MemorySpace = axom::execution_space::memory_space; using PointType = axom::primal::Point; - ContourTestBase(const std::shared_ptr>& testStrategy) - : m_testStrategy(testStrategy) + // ContourTestBase(const std::shared_ptr>& testStrategy) + ContourTestBase() + : m_testStrategies() , m_parentCellIdField("parentCellIds") , m_domainIdField("domainIdField") { } virtual ~ContourTestBase() { } +#if 0 //!@brief Return field name for storing nodal function. virtual std::string testName() const { return m_testStrategy->testName(); } @@ -727,14 +729,24 @@ struct ContourTestBase { return m_testStrategy; } +#endif + void addTestStrategy( const std::shared_ptr>& testStrategy ) + { + m_testStrategies.push_back(testStrategy); + SLIC_INFO(axom::fmt::format("Add test {}.", testStrategy->testName())); + } - std::shared_ptr> m_testStrategy; + axom::Array>> m_testStrategies; const std::string m_parentCellIdField; const std::string m_domainIdField; int runTest(BlueprintStructuredMesh& computationalMesh) { - SLIC_INFO(banner(axom::fmt::format("Testing {} contour.", testName()))); + // Compute the nodal distance functions. + for( const auto& strategy : m_testStrategies ) + { + computeNodalDistance(computationalMesh, *strategy); + } // Conduit data is in host memory, move to devices for testing. if(s_allocatorId != axom::execution_space::allocatorID()) @@ -746,9 +758,12 @@ struct ContourTestBase computationalMesh.coordsetPath() + "/values/" + axes[d], s_allocatorId); } - computationalMesh.moveMeshDataToNewMemorySpace( - axom::fmt::format("fields/{}/values", functionName()), - s_allocatorId); + for( const auto& strategy : m_testStrategies ) + { + computationalMesh.moveMeshDataToNewMemorySpace( + axom::fmt::format("fields/{}/values", strategy->functionName()), + s_allocatorId); + } } #if defined(AXOM_USE_UMPIRE) @@ -760,33 +775,36 @@ struct ContourTestBase { std::string resourceName = "HOST"; umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); - const std::string dataPath = - axom::fmt::format("fields/{}/values", functionName()); - void* dataPtr = - computationalMesh.domain(0).fetch_existing(dataPath).data_ptr(); - bool dataFromUmpire = rm.hasAllocator(dataPtr); - if(dataFromUmpire) - { - umpire::Allocator allocator = rm.getAllocator(dataPtr); - resourceName = allocator.getName(); - } - SLIC_INFO( - axom::fmt::format("Testing with policy {} and function data on {}", - params.policy, - resourceName)); - if(params.policy == axom::runtime_policy::Policy::seq) + for( const auto& strategy : m_testStrategies ) { - SLIC_ASSERT(resourceName == "HOST"); - } - #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - else if(params.policy == axom::runtime_policy::Policy::omp) - { - SLIC_ASSERT(resourceName == "HOST"); - } - #endif - else - { - SLIC_ASSERT(resourceName == "DEVICE"); + const std::string dataPath = + axom::fmt::format("fields/{}/values", strategy->functionName()); + void* dataPtr = + computationalMesh.domain(0).fetch_existing(dataPath).data_ptr(); + bool dataFromUmpire = rm.hasAllocator(dataPtr); + if(dataFromUmpire) + { + umpire::Allocator allocator = rm.getAllocator(dataPtr); + resourceName = allocator.getName(); + } + SLIC_INFO( + axom::fmt::format("Testing with policy {} and function data on {}", + params.policy, + resourceName)); + if(params.policy == axom::runtime_policy::Policy::seq) + { + SLIC_ASSERT(resourceName == "HOST"); + } +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + else if(params.policy == axom::runtime_policy::Policy::omp) + { + SLIC_ASSERT(resourceName == "HOST"); + } +#endif + else + { + SLIC_ASSERT(resourceName == "DEVICE"); + } } } #endif @@ -806,7 +824,7 @@ struct ContourTestBase // Clear and set MarchingCubes object for a "new" mesh. mc.setMesh(computationalMesh.asConduitNode(), "mesh"); - mc.setFunctionField(functionName()); + mc.setFunctionField(m_testStrategies[0]->functionName()); #ifdef AXOM_USE_MPI MPI_Barrier(MPI_COMM_WORLD); @@ -827,7 +845,7 @@ struct ContourTestBase SLIC_INFO(axom::fmt::format("Finished {} object reps x {} contour reps", params.objectRepCount, params.contourGenCount)); - printTimingStats(repsTimer, testName() + " contour"); + printTimingStats(repsTimer, "contour"); auto& mc = *mcPtr; printRunStats(mc); @@ -842,13 +860,16 @@ struct ContourTestBase computationalMesh.coordsetPath() + "/values/" + axes[d], axom::execution_space::allocatorID()); } - computationalMesh.moveMeshDataToNewMemorySpace( - axom::fmt::format("fields/{}/values", functionName()), - axom::execution_space::allocatorID()); + for( const auto& strategy : m_testStrategies ) + { + computationalMesh.moveMeshDataToNewMemorySpace( + axom::fmt::format("fields/{}/values", strategy->functionName()), + axom::execution_space::allocatorID()); + } } // Put contour mesh in a mint object for error checking and output. - std::string sidreGroupName = testName() + "_mesh"; + std::string sidreGroupName = "contour_mesh"; sidre::DataStore objectDS; // While awaiting fix for PR #1271, don't use Sidre storage in contourMesh. sidre::Group* meshGroup = objectDS.getRoot()->createGroup(sidreGroupName); @@ -860,27 +881,33 @@ struct ContourTestBase extractTimer.start(); mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); extractTimer.stop(); - printTimingStats(extractTimer, testName() + " extract"); + printTimingStats(extractTimer, "extract"); int localErrCount = 0; if(params.checkResults) { +#if 0 + // Temporarily disable until logic to check multiple strategies. localErrCount += checkContourSurface(contourMesh, params.contourVal, "diff"); +#endif localErrCount += checkContourCellLimits(computationalMesh, contourMesh); +#if 0 + // Temporarily disable until logic to check multiple strategies. localErrCount += checkCellsContainingContour(computationalMesh, contourMesh); +#endif } if (contourMesh.hasSidreGroup()) { assert(contourMesh.getSidreGroup() == meshGroup); // Write contour mesh to file. - std::string outputName = testName() + "_contour_mesh"; + std::string outputName = "contour_mesh"; saveMesh(*contourMesh.getSidreGroup(), outputName); - SLIC_INFO(axom::fmt::format("Wrote {} contour in {}", testName(), outputName)); + SLIC_INFO(axom::fmt::format("Wrote contour mesh to {}", outputName)); } objectDS.getRoot()->destroyGroupAndData(sidreGroupName); @@ -907,7 +934,8 @@ struct ContourTestBase mc.getContourNodeCount())); } - void computeNodalDistance(BlueprintStructuredMesh& bpMesh) + void computeNodalDistance(BlueprintStructuredMesh& bpMesh, + ContourTestStrategy& strat) { SLIC_ASSERT(bpMesh.dimension() == DIM); for(int domId = 0; domId < bpMesh.domainCount(); ++domId) @@ -917,7 +945,7 @@ struct ContourTestBase mvu(dom, "mesh"); // Create nodal function data with ghosts like node coords. - mvu.createField(functionName(), + mvu.createField(strat.functionName(), "vertex", conduit::DataType::float64(mvu.getCoordsCountWithGhosts()), mvu.getCoordsStrides(), @@ -931,19 +959,20 @@ struct ContourTestBase mvu(dom, "mesh"); const auto coordsViews = mvu.getConstCoordsViews(false); axom::ArrayView fieldView = - mvu.template getFieldView(functionName(), false); + mvu.template getFieldView(strat.functionName(), false); for(int d = 0; d < DIM; ++d) { SLIC_ASSERT(coordsViews[d].shape() == fieldView.shape()); } - populateNodalDistance(coordsViews, fieldView); + populateNodalDistance(coordsViews, fieldView, strat); } } template typename std::enable_if::type populateNodalDistance( const axom::StackArray, DIM>& coordsViews, - axom::ArrayView& fieldView) + axom::ArrayView& fieldView, + ContourTestStrategy& strat) { const auto& fieldShape = fieldView.shape(); for(int d = 0; d < DIM; ++d) @@ -951,22 +980,6 @@ struct ContourTestBase SLIC_ASSERT(coordsViews[d].shape() == fieldShape); } -#if defined(AXOM_USE_RAJA) - RAJA::RangeSegment iRange(0, fieldShape[0]); - RAJA::RangeSegment jRange(0, fieldShape[1]); - using EXEC_POL = - typename axom::mint::internal::structured_exec::loop2d_policy; - RAJA::kernel( - RAJA::make_tuple(iRange, jRange), - AXOM_LAMBDA(axom::IndexType i, axom::IndexType j) { - PointType pt; - for(int d = 0; d < DIM; ++d) - { - pt[d] = coordsViews[d](i, j); - } - fieldView(i, j) = m_testStrategy->valueAt(pt); - }); -#else for(axom::IndexType j = 0; j < fieldShape[1]; ++j) { for(axom::IndexType i = 0; i < fieldShape[0]; ++i) @@ -976,17 +989,17 @@ struct ContourTestBase { pt[d] = coordsViews[d](i, j); } - fieldView(i, j) = m_testStrategy->valueAt(pt); + fieldView(i, j) = strat.valueAt(pt); } } -#endif } template typename std::enable_if::type populateNodalDistance( const axom::StackArray, DIM>& coordsViews, - axom::ArrayView& fieldView) + axom::ArrayView& fieldView, + ContourTestStrategy& strat) { const auto& fieldShape = fieldView.shape(); for(int d = 0; d < DIM; ++d) @@ -994,23 +1007,6 @@ struct ContourTestBase SLIC_ASSERT(coordsViews[d].shape() == fieldShape); } -#if defined(AXOM_USE_RAJA) - RAJA::RangeSegment iRange(0, fieldShape[0]); - RAJA::RangeSegment jRange(0, fieldShape[1]); - RAJA::RangeSegment kRange(0, fieldShape[2]); - using EXEC_POL = - typename axom::mint::internal::structured_exec::loop3d_policy; - RAJA::kernel( - RAJA::make_tuple(iRange, jRange, kRange), - AXOM_LAMBDA(axom::IndexType i, axom::IndexType j, axom::IndexType k) { - PointType pt; - for(int d = 0; d < DIM; ++d) - { - pt[d] = coordsViews[d](i, j, k); - } - fieldView(i, j, k) = m_testStrategy->valueAt(pt); - }); -#else for(axom::IndexType k = 0; k < fieldShape[2]; ++k) { for(axom::IndexType j = 0; j < fieldShape[1]; ++j) @@ -1022,13 +1018,13 @@ struct ContourTestBase { pt[d] = coordsViews[d](i, j, k); } - fieldView(i, j, k) = m_testStrategy->valueAt(pt); + fieldView(i, j, k) = strat.valueAt(pt); } } } -#endif } +#if 0 /* TODO: Additional tests: - surface points should lie on computational mesh edges. @@ -1086,6 +1082,7 @@ struct ContourTestBase tol)); return errCount; } +#endif //!@brief Get view of output domain id data. axom::ArrayView getDomainIdView( @@ -1259,6 +1256,7 @@ struct ContourTestBase return errCount; } +#if 0 /*! Check that computational cells that contain the contour value have at least one contour mesh cell. @@ -1398,6 +1396,7 @@ struct ContourTestBase errCount)); return errCount; } +#endif }; template @@ -1493,6 +1492,106 @@ struct GyroidTestStrategy : public ContourTestStrategy { }; +#if 0 +template +void populateNodalDistance( + const axom::StackArray, DIM>& coordsViews, + axom::ArrayView& fieldView, + const ContourTestStrategy& strat); + +template +void computeNodalDistance(BlueprintStructuredMesh& bpMesh, + const ContourTestStrategy& strat) +{ + SLIC_ASSERT(bpMesh.dimension() == DIM); + for(int domId = 0; domId < bpMesh.domainCount(); ++domId) + { + conduit::Node& dom = bpMesh.domain(domId); + axom::quest::MeshViewUtil::memory_space> + mvu(dom, "mesh"); + + // Create nodal function data with ghosts like node coords. + mvu.createField(strat.functionName(), + "vertex", + conduit::DataType::float64(mvu.getCoordsCountWithGhosts()), + mvu.getCoordsStrides(), + mvu.getCoordsOffsets()); + } + + for(int domId = 0; domId < bpMesh.domainCount(); ++domId) + { + conduit::Node& dom = bpMesh.domain(domId); + axom::quest::MeshViewUtil::memory_space> + mvu(dom, "mesh"); + const auto coordsViews = mvu.getConstCoordsViews(false); + axom::ArrayView::memory_space> fieldView = + mvu.template getFieldView(strat.functionName(), false); + for(int d = 0; d < DIM; ++d) + { + SLIC_ASSERT(coordsViews[d].shape() == fieldView.shape()); + } + populateNodalDistance(coordsViews, fieldView, strat); + } +} + +template <> +void populateNodalDistance<2>( + const axom::StackArray::memory_space>, 2>& coordsViews, + axom::ArrayView& fieldView, + const ContourTestStrategy<2>& strat) +{ + using PointType = axom::primal::Point; + const auto& fieldShape = fieldView.shape(); + for(int d = 0; d < 2; ++d) + { + SLIC_ASSERT(coordsViews[d].shape() == fieldShape); + } + + for(axom::IndexType j = 0; j < fieldShape[1]; ++j) + { + for(axom::IndexType i = 0; i < fieldShape[0]; ++i) + { + PointType pt; + for(int d = 0; d < 2; ++d) + { + pt[d] = coordsViews[d](i, j); + } + fieldView(i, j) = strat.valueAt(pt); + } + } +} + +template <> +void populateNodalDistance<3>( + const axom::StackArray::memory_space>, 3>& coordsViews, + axom::ArrayView& fieldView, + const ContourTestStrategy<3>& strat) +{ + using PointType = axom::primal::Point; + const auto& fieldShape = fieldView.shape(); + for(int d = 0; d < 3; ++d) + { + SLIC_ASSERT(coordsViews[d].shape() == fieldShape); + } + + for(axom::IndexType k = 0; k < fieldShape[2]; ++k) + { + for(axom::IndexType j = 0; j < fieldShape[1]; ++j) + { + for(axom::IndexType i = 0; i < fieldShape[0]; ++i) + { + PointType pt; + for(int d = 0; d < 3; ++d) + { + pt[d] = coordsViews[d](i, j, k); + } + fieldView(i, j, k) = strat.valueAt(pt); + } + } + } +} +#endif + /// int allocatorIdToTest(axom::runtime_policy::Policy policy) { @@ -1583,30 +1682,41 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) std::shared_ptr> planarTest; std::shared_ptr> roundTest; std::shared_ptr> gyroidTest; + std::shared_ptr> planarStrat; + std::shared_ptr> roundStrat; + std::shared_ptr> gyroidStrat; + + ContourTestBase contourTest; if(params.usingPlanar()) { - auto strat = std::make_shared>(params.planeNormal(), - params.inplanePoint()); - planarTest = std::make_shared>(strat); - planarTest->computeNodalDistance(computationalMesh); + planarStrat = std::make_shared>(params.planeNormal(), + params.inplanePoint()); + planarTest = std::make_shared>(); + planarTest->addTestStrategy(planarStrat); + contourTest.addTestStrategy(planarStrat); + // computeNodalDistance(computationalMesh, *planarStrat); } if(params.usingRound()) { - auto strat = std::make_shared>(params.roundContourCenter()); - strat->setToleranceByLongestEdge(computationalMesh); - roundTest = std::make_shared>(strat); - roundTest->computeNodalDistance(computationalMesh); + roundStrat = std::make_shared>(params.roundContourCenter()); + roundStrat->setToleranceByLongestEdge(computationalMesh); + roundTest = std::make_shared>(); + roundTest->addTestStrategy(roundStrat); + contourTest.addTestStrategy(roundStrat); + // computeNodalDistance(computationalMesh, *roundStrat); } if(params.usingGyroid()) { - auto strat = std::make_shared>(params.gyroidScaleFactor(), - params.contourVal); - strat->setToleranceByLongestEdge(computationalMesh); - gyroidTest = std::make_shared>(strat); - gyroidTest->computeNodalDistance(computationalMesh); + gyroidStrat = std::make_shared>(params.gyroidScaleFactor(), + params.contourVal); + gyroidStrat->setToleranceByLongestEdge(computationalMesh); + gyroidTest = std::make_shared>(); + gyroidTest->addTestStrategy(gyroidStrat); + contourTest.addTestStrategy(gyroidStrat); + // computeNodalDistance(computationalMesh, *gyroidStrat); } if(params.isVerbose()) @@ -1618,24 +1728,27 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) saveMesh(computationalMesh.asConduitNode(), params.fieldsFile); int localErrCount = 0; + localErrCount += contourTest.runTest(computationalMesh); +#if 0 if(planarTest) { - localErrCount += planarTest->runTest(computationalMesh); + localErrCount += contourTest.runTest(computationalMesh); } slic::flushStreams(); if(roundTest) { - localErrCount += roundTest->runTest(computationalMesh); + localErrCount += contourTest.runTest(computationalMesh); } slic::flushStreams(); if(gyroidTest) { - localErrCount += gyroidTest->runTest(computationalMesh); + localErrCount += contourTest.runTest(computationalMesh); } slic::flushStreams(); +#endif // Check results From eef4deb7e28d34884f1b9009e72fa4cacc5ab9f1 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 28 Feb 2024 11:48:41 -0800 Subject: [PATCH 44/61] Update correctness tests to work on the combined contour output. --- src/axom/quest/ArrayIndexer.hpp | 30 + src/axom/quest/MarchingCubes.cpp | 7 +- src/axom/quest/MarchingCubes.hpp | 2 + src/axom/quest/MeshViewUtil.hpp | 24 +- .../examples/quest_marching_cubes_example.cpp | 594 +++++++----------- 5 files changed, 286 insertions(+), 371 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index 0cefe603d3..bd493d134e 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -59,6 +59,21 @@ class ArrayIndexer initializeShape(shape, slowestDirs); } + /*! + @brief Constructor for a given shape with the ordering of an + existing ArrayIndexer. + + @param [in] shape Shape of the array + @param [in] orderSource: ArrayIndex to copy stride order + from. + */ + ArrayIndexer( + const axom::StackArray& shape, + const axom::ArrayIndexer& orderSource) + { + initializeShape(shape, orderSource); + } + /*! @brief Constructor for arbitrary-stride indexing. @@ -142,6 +157,21 @@ class ArrayIndexer } } + /*! + @brief Initialize for a given shape with the ordering of an + existing ArrayIndexer. + + @param [in] shape Shape of the array + @param [in] orderSource: ArrayIndex to copy stride order + from. + */ + inline AXOM_HOST_DEVICE void initializeShape( + const axom::StackArray& shape, + const axom::ArrayIndexer& orderSource) + { + initializeShape(shape, orderSource.slowestDirs()); + } + /*! @brief Initialize for arbitrary-stride indexing. diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 4083e5039c..163ec92b47 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -145,11 +145,8 @@ void MarchingCubes::computeIsocontour(double contourVal) axom::IndexType MarchingCubes::getContourNodeCount() const { - axom::IndexType contourNodeCount = 0; - for(int dId = 0; dId < m_singles.size(); ++dId) - { - contourNodeCount += m_singles[dId]->getContourNodeCount(); - } + axom::IndexType contourNodeCount = + m_singles.empty() ? 0 : m_facetCount * m_singles[0]->spatialDimension(); return contourNodeCount; } diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 2eefd45860..629b01d43c 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -169,6 +169,8 @@ class MarchingCubes //!@brief Get number of cells in the generated contour mesh. axom::IndexType getContourCellCount() const { return m_facetCount; } + //!@brief Get number of cells in the generated contour mesh. + axom::IndexType getContourFacetCount() const { return m_facetCount; } //!@brief Get number of nodes in the generated contour mesh. axom::IndexType getContourNodeCount() const; diff --git a/src/axom/quest/MeshViewUtil.hpp b/src/axom/quest/MeshViewUtil.hpp index ad68fc0b44..0db7104ebf 100644 --- a/src/axom/quest/MeshViewUtil.hpp +++ b/src/axom/quest/MeshViewUtil.hpp @@ -181,13 +181,13 @@ static void stridesAndOffsetsToShapes(const axom::StackArray& realSh \brief Utility for high-level access into a blueprint mesh, for structured mesh with explicit coordinates. - Note: This class was written for a specific use and is not as - general as it may seem. + Note: This class was written for a specific use and supports + only structured domains with explicit node values. - Blueprint mesh data is sufficient but sparse, leaving users - to compute a number of intermediate data to get to high-level - data of interest, such as views into array data. This class - encapsulates those common utilities. + Blueprint mesh data is sufficient but sparse, leaving users to + compute some intermediate data to get to high-level data of + interest, such as views into array data. This class encapsulates + those common functions. Views are single-domain-specific. They don't apply to multi-domain meshes. They are also topology specific, with the topology name @@ -202,7 +202,7 @@ static void stridesAndOffsetsToShapes(const axom::StackArray& realSh TODO: Figure out if there's a better place for this utility. It's only in axom/quest because the initial need was there. */ -template +template class MeshViewUtil { public: @@ -676,13 +676,17 @@ class MeshViewUtil const MdIndices& strides, const MdIndices& offsets) { + SLIC_ERROR_IF( + m_dom == nullptr, + axom::fmt::format("Cannot create field {}." + " MeshViewUtil was not constructed with a mutable domain.", fieldName)); SLIC_ERROR_IF( m_dom->has_path("fields/" + fieldName), axom::fmt::format("Cannot create field {}. It already exists.", fieldName)); SLIC_ERROR_IF( association != "vertex" && association != "element", - axom::fmt::format("Not yet supporting association '{}'.", association)); + axom::fmt::format("MeshViewUtil doesn't support association '{}' yet.", association)); const auto& realShape = getRealExtents(association); MdIndices loPads, hiPads, paddedShape, strideOrder; @@ -752,6 +756,10 @@ class MeshViewUtil const MdIndices& hiPads, const MdIndices& strideOrder) { + SLIC_ERROR_IF( + m_dom == nullptr, + axom::fmt::format("Cannot create field {}." + " MeshViewUtil was not constructed with a mutable domain.", fieldName)); SLIC_ERROR_IF( m_dom->has_path("fields/" + fieldName), axom::fmt::format("Cannot create field {}. It already exists.", fieldName)); diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index dfe401b4a6..b75df0e378 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -311,7 +311,8 @@ struct BlueprintStructuredMesh public: explicit BlueprintStructuredMesh(const std::string& meshFile, const std::string& topologyName) - : _topologyPath("topologies/" + topologyName) + : _topologyName(topologyName) + , _topologyPath("topologies/" + topologyName) { readBlueprintMesh(meshFile); for(int d = 0; d < _mdMesh.number_of_children(); ++d) @@ -345,6 +346,18 @@ struct BlueprintStructuredMesh return _mdMesh.child(domainIdx); } + template + axom::quest::MeshViewUtil getDomainView(axom::IndexType domainId) + { + return axom::quest::MeshViewUtil(domain(domainId), _topologyName); + } + + template + axom::quest::MeshViewUtil getDomainView(axom::IndexType domainId) const + { + return axom::quest::MeshViewUtil(domain(domainId), _topologyName); + } + /*! @brief Get the number of cells in each direction of a blueprint single domain. @@ -546,6 +559,7 @@ struct BlueprintStructuredMesh conduit::Node _mdMesh; axom::IndexType _domCount; bool _coordsAreStrided = false; + const std::string _topologyName; const std::string _topologyPath; std::string _coordsetPath; double _maxSpacing = -1.0; @@ -675,12 +689,16 @@ static void addToStackArray(axom::StackArray& a, U b) } /*! - @brief Strategy pattern for supporting different contour types. + @brief Strategy pattern for supporting a variety of contour types. + + The strategy encapsulates the scalar functions and things related to + it. */ template struct ContourTestStrategy { using PointType = axom::primal::Point; + //!@brief Return test name. virtual std::string testName() const = 0; @@ -696,11 +714,6 @@ struct ContourTestStrategy virtual ~ContourTestStrategy() {} }; -/** - ValueFunctorType is a copy-able functor that returns the scalar - function value. It should have operator(const PointType &) return - a double. -*/ template struct ContourTestBase { @@ -715,21 +728,6 @@ struct ContourTestBase { } virtual ~ContourTestBase() { } -#if 0 - //!@brief Return field name for storing nodal function. - virtual std::string testName() const { return m_testStrategy->testName(); } - - //!@brief Return field name for storing nodal function. - virtual std::string functionName() const { return m_testStrategy->functionName(); } - - //!@brief Return error tolerance for contour mesh accuracy check. - virtual double errorTolerance() const { return m_testStrategy->errorTolerance(); } - - std::shared_ptr> getTestStrategy() const - { - return m_testStrategy; - } -#endif void addTestStrategy( const std::shared_ptr>& testStrategy ) { m_testStrategies.push_back(testStrategy); @@ -737,6 +735,9 @@ struct ContourTestBase } axom::Array>> m_testStrategies; + //!@brief Prefix sum of facet counts from test strategies. + axom::Array m_strategyFacetPrefixSum; + const std::string m_parentCellIdField; const std::string m_domainIdField; @@ -824,7 +825,6 @@ struct ContourTestBase // Clear and set MarchingCubes object for a "new" mesh. mc.setMesh(computationalMesh.asConduitNode(), "mesh"); - mc.setFunctionField(m_testStrategies[0]->functionName()); #ifdef AXOM_USE_MPI MPI_Barrier(MPI_COMM_WORLD); @@ -838,7 +838,14 @@ struct ContourTestBase i, params.contourGenCount)); mc.clearOutput(); - mc.computeIsocontour(params.contourVal); + m_strategyFacetPrefixSum.clear(); + m_strategyFacetPrefixSum.push_back(0); + for( const auto& strategy : m_testStrategies ) + { + mc.setFunctionField(strategy->functionName()); + mc.computeIsocontour(params.contourVal); + m_strategyFacetPrefixSum.push_back(mc.getContourFacetCount()); + } } } repsTimer.stop(); @@ -886,19 +893,13 @@ struct ContourTestBase int localErrCount = 0; if(params.checkResults) { -#if 0 - // Temporarily disable until logic to check multiple strategies. localErrCount += checkContourSurface(contourMesh, params.contourVal, "diff"); -#endif localErrCount += checkContourCellLimits(computationalMesh, contourMesh); -#if 0 - // Temporarily disable until logic to check multiple strategies. localErrCount += checkCellsContainingContour(computationalMesh, contourMesh); -#endif } if (contourMesh.hasSidreGroup()) @@ -940,26 +941,22 @@ struct ContourTestBase SLIC_ASSERT(bpMesh.dimension() == DIM); for(int domId = 0; domId < bpMesh.domainCount(); ++domId) { - conduit::Node& dom = bpMesh.domain(domId); - axom::quest::MeshViewUtil::memory_space> - mvu(dom, "mesh"); + auto domainView = bpMesh.getDomainView(domId); // Create nodal function data with ghosts like node coords. - mvu.createField(strat.functionName(), - "vertex", - conduit::DataType::float64(mvu.getCoordsCountWithGhosts()), - mvu.getCoordsStrides(), - mvu.getCoordsOffsets()); + domainView.createField(strat.functionName(), + "vertex", + conduit::DataType::float64(domainView.getCoordsCountWithGhosts()), + domainView.getCoordsStrides(), + domainView.getCoordsOffsets()); } for(int domId = 0; domId < bpMesh.domainCount(); ++domId) { - conduit::Node& dom = bpMesh.domain(domId); - axom::quest::MeshViewUtil::memory_space> - mvu(dom, "mesh"); - const auto coordsViews = mvu.getConstCoordsViews(false); - axom::ArrayView fieldView = - mvu.template getFieldView(strat.functionName(), false); + auto domainView = bpMesh.getDomainView(domId); + const auto coordsViews = domainView.getConstCoordsViews(false); + axom::ArrayView fieldView = + domainView.template getFieldView(strat.functionName(), false); for(int d = 0; d < DIM; ++d) { SLIC_ASSERT(coordsViews[d].shape() == fieldView.shape()); @@ -969,9 +966,9 @@ struct ContourTestBase } template typename std::enable_if::type populateNodalDistance( - const axom::StackArray, DIM>& + const axom::StackArray, DIM>& coordsViews, - axom::ArrayView& fieldView, + axom::ArrayView& fieldView, ContourTestStrategy& strat) { const auto& fieldShape = fieldView.shape(); @@ -996,9 +993,9 @@ struct ContourTestBase template typename std::enable_if::type populateNodalDistance( - const axom::StackArray, DIM>& + const axom::StackArray, DIM>& coordsViews, - axom::ArrayView& fieldView, + axom::ArrayView& fieldView, ContourTestStrategy& strat) { const auto& fieldShape = fieldView.shape(); @@ -1024,13 +1021,6 @@ struct ContourTestBase } } -#if 0 - /* - TODO: Additional tests: - - surface points should lie on computational mesh edges. - - computational cells stradling contourVal should be - parents to at least 1 contour mesh cell. - */ /** Check for errors in the surface contour mesh. - analytical scalar value at surface points should be @@ -1048,41 +1038,47 @@ struct ContourTestBase contourMesh.createField(diffField, axom::mint::NODE_CENTERED); } - double tol = errorTolerance(); int errCount = 0; - const axom::IndexType nodeCount = contourMesh.getNumberOfNodes(); - PointType pt; - for(axom::IndexType i = 0; i < nodeCount; ++i) + for(axom::IndexType iStrat = 0; iStrat < m_testStrategies.size(); ++iStrat) { - contourMesh.getNode(i, pt.data()); - double analyticalVal = m_testStrategy->valueAt(pt); - double diff = std::abs(analyticalVal - contourVal); - if(diffPtr) - { - diffPtr[i] = diff; - } - if(diff > tol) + auto contourNodeBegin = DIM * m_strategyFacetPrefixSum[iStrat]; + auto contourNodeEnd = DIM * m_strategyFacetPrefixSum[iStrat + 1]; + + auto& strategy = *m_testStrategies[iStrat]; + double tol = strategy.errorTolerance(); + + PointType pt; + for(axom::IndexType iNode = contourNodeBegin; iNode < contourNodeEnd; ++iNode) { - ++errCount; - SLIC_INFO_IF( - params.isVerbose(), - axom::fmt::format( - "checkContourSurface: node {} at {} has dist {}, off by {}", - i, - pt, - analyticalVal, - diff)); + contourMesh.getNode(iNode, pt.data()); + double analyticalVal = strategy.valueAt(pt); + double diff = std::abs(analyticalVal - contourVal); + if(diffPtr) + { + diffPtr[iNode] = diff; + } + if(diff > tol) + { + ++errCount; + SLIC_INFO_IF( + params.isVerbose(), + axom::fmt::format( + "checkContourSurface: node {} at {} has dist {}, off by {}", + iNode, + pt, + analyticalVal, + diff)); + } } + SLIC_INFO_IF( + params.isVerbose(), + axom::fmt::format( + "checkContourSurface: found {} errors outside tolerance of {}", + errCount, + tol)); } - SLIC_INFO_IF( - params.isVerbose(), - axom::fmt::format( - "checkContourSurface: found {} errors outside tolerance of {}", - errCount, - tol)); return errCount; } -#endif //!@brief Get view of output domain id data. axom::ArrayView getDomainIdView( @@ -1140,33 +1136,31 @@ struct ContourTestBase axom::mint::UnstructuredMesh& contourMesh) { int errCount = 0; - const axom::IndexType cellCount = contourMesh.getNumberOfCells(); auto parentCellIdView = get_parent_cell_id_view(contourMesh); auto domainIdView = getDomainIdView(contourMesh); const axom::IndexType domainCount = computationalMesh.domainCount(); - axom::Array::ConstCoordsViewsType> + axom::Array::ConstCoordsViewsType> allCoordsViews(domainCount); - for(int n = 0; n < domainCount; ++n) + for(int iDomain = 0; iDomain < domainCount; ++iDomain) { - const auto& dom = computationalMesh.domain(n); - axom::quest::MeshViewUtil mvu(dom, "mesh"); - allCoordsViews[n] = mvu.getConstCoordsViews(false); + auto domainView = computationalMesh.getDomainView(iDomain); + allCoordsViews[iDomain] = domainView.getConstCoordsViews(false); } std::map domainIdToContiguousId; - for(int n = 0; n < domainCount; ++n) + for(int iDomain = 0; iDomain < domainCount; ++iDomain) { - const auto& dom = computationalMesh.domain(n); - axom::quest::MarchingCubes::DomainIdType domainId = n; + const auto& dom = computationalMesh.domain(iDomain); + axom::quest::MarchingCubes::DomainIdType domainId = iDomain; if(dom.has_path("state/domain_id")) { domainId = dom.fetch_existing("state/domain_id").value(); } - domainIdToContiguousId[domainId] = n; + domainIdToContiguousId[domainId] = iDomain; } // Indexers to transltate between flat and multidim indices. @@ -1181,70 +1175,75 @@ struct ContourTestBase .slowestDirs()); } - for(axom::IndexType contourCellNum = 0; contourCellNum < cellCount; - ++contourCellNum) + for(axom::IndexType iStrat = 0; iStrat < m_testStrategies.size(); ++iStrat) { - axom::quest::MarchingCubes::DomainIdType domainId = - domainIdView[contourCellNum]; - axom::quest::MarchingCubes::DomainIdType contiguousIndex = - domainIdToContiguousId[domainId]; - typename axom::quest::MeshViewUtil::ConstCoordsViewsType& - coordsViews = allCoordsViews[contiguousIndex]; - - axom::IndexType parentCellId = parentCellIdView[contourCellNum]; - - axom::StackArray parentCellIdx = - indexers[contiguousIndex].toMultiIndex(parentCellId); - axom::StackArray upperIdx = parentCellIdx; - addToStackArray(upperIdx, 1); - - PointType lower, upper; - for(int d = 0; d < DIM; ++d) - { - lower[d] = coordsViews[d][parentCellIdx]; - upper[d] = coordsViews[d][upperIdx]; - } - axom::primal::BoundingBox parentCellBox(lower, upper); - auto tol = axom::numerics::floating_point_limits::epsilon(); - axom::primal::BoundingBox big(parentCellBox); - big.expand(tol); - axom::primal::BoundingBox small(parentCellBox); - auto range = parentCellBox.range(); - bool checkSmall = range >= tol; - if (checkSmall) + auto contourCellBegin = m_strategyFacetPrefixSum[iStrat]; + auto contourCellEnd = m_strategyFacetPrefixSum[iStrat + 1]; + for(axom::IndexType iContourCell = contourCellBegin; iContourCell < contourCellEnd; + ++iContourCell) { - small.expand(-tol); - } + axom::quest::MarchingCubes::DomainIdType domainId = + domainIdView[iContourCell]; + axom::quest::MarchingCubes::DomainIdType contiguousIndex = + domainIdToContiguousId[domainId]; + typename axom::quest::MeshViewUtil::ConstCoordsViewsType& + coordsViews = allCoordsViews[contiguousIndex]; - axom::IndexType* cellNodeIds = contourMesh.getCellNodeIDs(contourCellNum); - const axom::IndexType cellNodeCount = - contourMesh.getNumberOfCellNodes(contourCellNum); + axom::IndexType parentCellId = parentCellIdView[iContourCell]; - for(axom::IndexType nn = 0; nn < cellNodeCount; ++nn) - { - PointType nodeCoords; - contourMesh.getNode(cellNodeIds[nn], nodeCoords.data()); + axom::StackArray parentCellIdx = + indexers[contiguousIndex].toMultiIndex(parentCellId); + axom::StackArray upperIdx = parentCellIdx; + addToStackArray(upperIdx, 1); - if(!big.contains(nodeCoords)) + PointType lower, upper; + for(int d = 0; d < DIM; ++d) { - ++errCount; - SLIC_INFO_IF( - params.isVerbose(), - axom::fmt::format("checkContourCellLimits: node {} at {} " - "too far outside parent cell boundary.", - cellNodeIds[nn], - nodeCoords)); + lower[d] = coordsViews[d][parentCellIdx]; + upper[d] = coordsViews[d][upperIdx]; + } + axom::primal::BoundingBox parentCellBox(lower, upper); + auto tol = axom::numerics::floating_point_limits::epsilon(); + axom::primal::BoundingBox big(parentCellBox); + big.expand(tol); + axom::primal::BoundingBox small(parentCellBox); + auto range = parentCellBox.range(); + bool checkSmall = range >= tol; + if (checkSmall) + { + small.expand(-tol); } - if(checkSmall && small.contains(nodeCoords)) + axom::IndexType* cellNodeIds = contourMesh.getCellNodeIDs(iContourCell); + const axom::IndexType cellNodeCount = + contourMesh.getNumberOfCellNodes(iContourCell); + + for(axom::IndexType nn = 0; nn < cellNodeCount; ++nn) { - ++errCount; - SLIC_INFO_IF( - params.isVerbose(), - axom::fmt::format("checkContourCellLimits: node {} at {} " - "too far inside parent cell boundary.", - cellNodeIds[nn], - nodeCoords)); + PointType nodeCoords; + contourMesh.getNode(cellNodeIds[nn], nodeCoords.data()); + + if(!big.contains(nodeCoords)) + { + ++errCount; + SLIC_INFO_IF( + params.isVerbose(), + axom::fmt::format("checkContourCellLimits: node {} at {} " + "too far outside parent cell boundary.", + cellNodeIds[nn], + nodeCoords)); + } + + if(checkSmall && small.contains(nodeCoords)) + { + ++errCount; + SLIC_INFO_IF( + params.isVerbose(), + axom::fmt::format("checkContourCellLimits: node {} at {} " + "too far inside parent cell boundary.", + cellNodeIds[nn], + nodeCoords)); + } } } } @@ -1256,7 +1255,6 @@ struct ContourTestBase return errCount; } -#if 0 /*! Check that computational cells that contain the contour value have at least one contour mesh cell. @@ -1266,137 +1264,150 @@ struct ContourTestBase axom::mint::UnstructuredMesh& contourMesh) { int errCount = 0; - const axom::IndexType cellCount = contourMesh.getNumberOfCells(); auto parentCellIdView = get_parent_cell_id_view(contourMesh); auto domainIdView = getDomainIdView(contourMesh); const axom::IndexType domainCount = computationalMesh.domainCount(); + // + // Compute mapping to look up domains from the domain id. + // + std::map domainIdToContiguousId; + for(axom::quest::MarchingCubes::DomainIdType iDomain = 0; iDomain < domainCount; ++iDomain) + { + const auto& dom = computationalMesh.domain(iDomain); + axom::quest::MarchingCubes::DomainIdType domainId = iDomain; + if(dom.has_path("state/domain_id")) + { + domainId = dom.fetch_existing("state/domain_id").value(); + } + domainIdToContiguousId[domainId] = iDomain; + } + /* - Space to store function views and whether a computational cell - contains the contour. We set these up for all domains ahead - of time for accessing as needed later. cellIndexers are for - converting between flat and multidim indices. + If strategy iStrat creates a contour through cell cellId of + domain contiguousDomainId, then set bit iStrat in hasContours: + hasContours[contiguousDomainId][cellId] & (1 < iStrat). */ axom::Array> fcnViews( domainCount); axom::Array> cellIndexers( domainCount); - axom::Array> hasContours(domainCount); + axom::Array> hasContours(domainCount); for(axom::IndexType domId = 0; domId < domainCount; ++domId) { - const auto& dom = computationalMesh.domain(domId); - axom::quest::MeshViewUtil mvu(dom, "mesh"); + axom::quest::MeshViewUtil domainView = computationalMesh.getDomainView(domId); - const axom::IndexType cellCount = mvu.getCellCount(); + const axom::IndexType cellCount = domainView.getCellCount(); - axom::Array& hasContour = hasContours[domId]; - hasContour.resize(cellCount, false); - - fcnViews[domId] = - mvu.template getConstFieldView(functionName(), false); - cellIndexers[domId].initializeShape( - mvu.getCellShape(), - axom::ArrayIndexer(fcnViews[domId].strides()) - .slowestDirs()); + axom::Array& hasContour = hasContours[domId]; + hasContour.resize(cellCount, 0); } - std::map domainIdToContiguousId; - for(axom::quest::MarchingCubes::DomainIdType n = 0; n < domainCount; ++n) + for(int iStrat = 0; iStrat < m_testStrategies.size(); ++iStrat) { - const auto& dom = computationalMesh.domain(n); - axom::quest::MarchingCubes::DomainIdType domainId = n; - if(dom.has_path("state/domain_id")) + auto contourCellBegin = m_strategyFacetPrefixSum[iStrat]; + auto contourCellEnd = m_strategyFacetPrefixSum[iStrat + 1]; + axom::IndexType bitFlag = (1 << iStrat); + for(axom::IndexType iContourCell = contourCellBegin; iContourCell < contourCellEnd; + ++iContourCell) { - domainId = dom.fetch_existing("state/domain_id").value(); + axom::quest::MarchingCubes::DomainIdType domainId = + domainIdView[iContourCell]; + axom::quest::MarchingCubes::DomainIdType contiguousId = + domainIdToContiguousId[domainId]; + const axom::IndexType parentCellId = parentCellIdView[iContourCell]; + hasContours[contiguousId][parentCellId] |= bitFlag; } - domainIdToContiguousId[domainId] = n; - } - - for(axom::IndexType contourCellNum = 0; contourCellNum < cellCount; - ++contourCellNum) - { - axom::quest::MarchingCubes::DomainIdType domainId = - domainIdView[contourCellNum]; - axom::quest::MarchingCubes::DomainIdType contiguousId = - domainIdToContiguousId[domainId]; - const axom::IndexType parentCellId = parentCellIdView[contourCellNum]; - hasContours[contiguousId][parentCellId] = true; } // Verify that cells marked by hasContours touches the contour // and other cells don't. for(axom::IndexType domId = 0; domId < domainCount; ++domId) { - const auto& dom = computationalMesh.domain(domId); - axom::quest::MeshViewUtil mvu(dom, "mesh"); + auto domainView = computationalMesh.getDomainView(domId); axom::StackArray domLengths; computationalMesh.domainLengths(domId, domLengths); - assert(domLengths == mvu.getRealExtents("element")); + assert(domLengths == domainView.getRealExtents("element")); - const auto& fcnView = fcnViews[domId]; + const axom::IndexType parentCellCount = domainView.getCellCount(); + // axom::Array hasContour(parentCellCount, parentCellCount); - const axom::ArrayIndexer& cellIndexer = - cellIndexers[domId]; - const axom::IndexType cellCount = product(domLengths); - for(axom::IndexType cellId = 0; cellId < cellCount; ++cellId) + for(axom::IndexType parentCellId = 0; parentCellId < parentCellCount; ++parentCellId) { - axom::StackArray cellIdx = - cellIndexer.toMultiIndex(cellId); - - // Compute min and max function value in the cell. - double minFcnValue = std::numeric_limits::max(); - double maxFcnValue = std::numeric_limits::min(); - constexpr short int cornerCount = - (1 << DIM); // Number of nodes in a cell. - for(short int cornerId = 0; cornerId < cornerCount; ++cornerId) + const axom::IndexType hasContourBits = hasContours[domId][parentCellId]; + + for(axom::IndexType iStrat = 0; iStrat < m_testStrategies.size(); ++iStrat) { - // Compute multidim index of current corner of parent cell. - axom::StackArray cornerIdx = cellIdx; - for(int d = 0; d < DIM; ++d) + auto& strategy = *m_testStrategies[iStrat]; + const axom::IndexType iStratBit = (1 << iStrat); + + const auto& fcnView = + domainView.template getConstFieldView(strategy.functionName(), false); + + axom::ArrayIndexer + cellIndexer(domLengths, + axom::ArrayIndexer(fcnView.strides())); + + axom::StackArray parentCellIdx = + cellIndexer.toMultiIndex(parentCellId); + + // Compute min and max function values in the cell. + double minFcnValue = std::numeric_limits::max(); + double maxFcnValue = std::numeric_limits::min(); + constexpr short int cornerCount = + (1 << DIM); // Number of nodes in a cell. + for(short int cornerId = 0; cornerId < cornerCount; ++cornerId) { - if(cornerId & (1 << d)) + // Compute multidim index of current corner of parent cell. + axom::StackArray cornerIdx = parentCellIdx; + for(int d = 0; d < DIM; ++d) { - ++cornerIdx[d]; + if(cornerId & (1 << d)) + { + ++cornerIdx[d]; + } } + + double fcnValue = fcnView[cornerIdx]; + minFcnValue = std::min(minFcnValue, fcnValue); + maxFcnValue = std::max(maxFcnValue, fcnValue); } - double fcnValue = fcnView[cornerIdx]; - minFcnValue = std::min(minFcnValue, fcnValue); - maxFcnValue = std::max(maxFcnValue, fcnValue); - } + const bool hasContour = hasContourBits & iStratBit; - // If the min or max values in the cell is close to params.contourVal - // touchesContour and hasCont can go either way. So don't check. - if(minFcnValue != params.contourVal && maxFcnValue != params.contourVal) - { - const bool touchesContour = (minFcnValue <= params.contourVal && - maxFcnValue >= params.contourVal); - const bool hasCont = hasContours[domId][cellId]; - if(touchesContour != hasCont) + bool touchesContour = (minFcnValue <= params.contourVal && + maxFcnValue >= params.contourVal); + // If the min or max values in the cell is close to params.contourVal + // touchesContour and hasCont can go either way. So give it a pass. + if(minFcnValue == params.contourVal || maxFcnValue == params.contourVal) + { + touchesContour = hasContour; + } + + if(touchesContour != hasContour) { ++errCount; SLIC_INFO_IF(params.isVerbose(), axom::fmt::format( "checkCellsContainingContour: cell {}: hasContour " - "({}) and touchesContour ({}) don't agree.", - cellIdx, - hasCont, - touchesContour)); + "({}) and touchesContour ({}) don't agree for strategy {}.", + parentCellIdx, + hasContour, + touchesContour, + strategy.testName())); } } } } - SLIC_INFO_IF(params.isVerbose(), axom::fmt::format("checkCellsContainingContour: found {} " "misrepresented computational cells.", errCount)); return errCount; } -#endif }; template @@ -1491,107 +1502,6 @@ struct GyroidTestStrategy : public ContourTestStrategy { double _errTol; }; - -#if 0 -template -void populateNodalDistance( - const axom::StackArray, DIM>& coordsViews, - axom::ArrayView& fieldView, - const ContourTestStrategy& strat); - -template -void computeNodalDistance(BlueprintStructuredMesh& bpMesh, - const ContourTestStrategy& strat) -{ - SLIC_ASSERT(bpMesh.dimension() == DIM); - for(int domId = 0; domId < bpMesh.domainCount(); ++domId) - { - conduit::Node& dom = bpMesh.domain(domId); - axom::quest::MeshViewUtil::memory_space> - mvu(dom, "mesh"); - - // Create nodal function data with ghosts like node coords. - mvu.createField(strat.functionName(), - "vertex", - conduit::DataType::float64(mvu.getCoordsCountWithGhosts()), - mvu.getCoordsStrides(), - mvu.getCoordsOffsets()); - } - - for(int domId = 0; domId < bpMesh.domainCount(); ++domId) - { - conduit::Node& dom = bpMesh.domain(domId); - axom::quest::MeshViewUtil::memory_space> - mvu(dom, "mesh"); - const auto coordsViews = mvu.getConstCoordsViews(false); - axom::ArrayView::memory_space> fieldView = - mvu.template getFieldView(strat.functionName(), false); - for(int d = 0; d < DIM; ++d) - { - SLIC_ASSERT(coordsViews[d].shape() == fieldView.shape()); - } - populateNodalDistance(coordsViews, fieldView, strat); - } -} - -template <> -void populateNodalDistance<2>( - const axom::StackArray::memory_space>, 2>& coordsViews, - axom::ArrayView& fieldView, - const ContourTestStrategy<2>& strat) -{ - using PointType = axom::primal::Point; - const auto& fieldShape = fieldView.shape(); - for(int d = 0; d < 2; ++d) - { - SLIC_ASSERT(coordsViews[d].shape() == fieldShape); - } - - for(axom::IndexType j = 0; j < fieldShape[1]; ++j) - { - for(axom::IndexType i = 0; i < fieldShape[0]; ++i) - { - PointType pt; - for(int d = 0; d < 2; ++d) - { - pt[d] = coordsViews[d](i, j); - } - fieldView(i, j) = strat.valueAt(pt); - } - } -} - -template <> -void populateNodalDistance<3>( - const axom::StackArray::memory_space>, 3>& coordsViews, - axom::ArrayView& fieldView, - const ContourTestStrategy<3>& strat) -{ - using PointType = axom::primal::Point; - const auto& fieldShape = fieldView.shape(); - for(int d = 0; d < 3; ++d) - { - SLIC_ASSERT(coordsViews[d].shape() == fieldShape); - } - - for(axom::IndexType k = 0; k < fieldShape[2]; ++k) - { - for(axom::IndexType j = 0; j < fieldShape[1]; ++j) - { - for(axom::IndexType i = 0; i < fieldShape[0]; ++i) - { - PointType pt; - for(int d = 0; d < 3; ++d) - { - pt[d] = coordsViews[d](i, j, k); - } - fieldView(i, j, k) = strat.valueAt(pt); - } - } - } -} -#endif - /// int allocatorIdToTest(axom::runtime_policy::Policy policy) { @@ -1679,9 +1589,6 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) // params specify which tests to run. //--------------------------------------------------------------------------- - std::shared_ptr> planarTest; - std::shared_ptr> roundTest; - std::shared_ptr> gyroidTest; std::shared_ptr> planarStrat; std::shared_ptr> roundStrat; std::shared_ptr> gyroidStrat; @@ -1692,20 +1599,14 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) { planarStrat = std::make_shared>(params.planeNormal(), params.inplanePoint()); - planarTest = std::make_shared>(); - planarTest->addTestStrategy(planarStrat); contourTest.addTestStrategy(planarStrat); - // computeNodalDistance(computationalMesh, *planarStrat); } if(params.usingRound()) { roundStrat = std::make_shared>(params.roundContourCenter()); roundStrat->setToleranceByLongestEdge(computationalMesh); - roundTest = std::make_shared>(); - roundTest->addTestStrategy(roundStrat); contourTest.addTestStrategy(roundStrat); - // computeNodalDistance(computationalMesh, *roundStrat); } if(params.usingGyroid()) @@ -1713,10 +1614,7 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) gyroidStrat = std::make_shared>(params.gyroidScaleFactor(), params.contourVal); gyroidStrat->setToleranceByLongestEdge(computationalMesh); - gyroidTest = std::make_shared>(); - gyroidTest->addTestStrategy(gyroidStrat); contourTest.addTestStrategy(gyroidStrat); - // computeNodalDistance(computationalMesh, *gyroidStrat); } if(params.isVerbose()) @@ -1730,26 +1628,6 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) int localErrCount = 0; localErrCount += contourTest.runTest(computationalMesh); -#if 0 - if(planarTest) - { - localErrCount += contourTest.runTest(computationalMesh); - } - slic::flushStreams(); - - if(roundTest) - { - localErrCount += contourTest.runTest(computationalMesh); - } - slic::flushStreams(); - - if(gyroidTest) - { - localErrCount += contourTest.runTest(computationalMesh); - } - slic::flushStreams(); -#endif - // Check results int errCount = 0; From fcfb99e69668669125b36c3c03b55519ae1e4223 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 29 Feb 2024 10:45:43 -0800 Subject: [PATCH 45/61] Let ArrayIndexer handle non-unit fastest strides. --- src/axom/quest/ArrayIndexer.hpp | 22 ++++++---- src/axom/quest/tests/quest_array_indexer.cpp | 45 +++++++------------- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index bd493d134e..f4a797a8f9 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -17,10 +17,10 @@ namespace axom { struct ArrayStrideOrder { - static constexpr int NEITHER = 0; // Neither row nor column + static constexpr int ARBITRARY = 0; // Neither row nor column static constexpr int ROW = 1; // Row-major static constexpr int COLUMN = 2; // Column-major - static constexpr int BOTH = ROW | COLUMN; // Only for 1D arrays + static constexpr int BOTH = ROW | COLUMN; // 1D arrays are both }; /*! @@ -40,10 +40,13 @@ class ArrayIndexer @brief Constructor for row- or column-major indexing. @param [in] shape Shape of the array @param [in] order: c is column major; r is row major. + @param [in] fastestStrideLength: Stride in the fastest + direction. */ - ArrayIndexer(const axom::StackArray& shape, int order) + ArrayIndexer(const axom::StackArray& shape, int order, + int fastestStrideLength = 1) { - initializeShape(shape, order); + initializeShape(shape, order, fastestStrideLength); } /*! @@ -101,9 +104,12 @@ class ArrayIndexer @brief Initialize for row- or column-major indexing. @param [in] shape Shape of the array @param [in] order: c is column major; r is row major. + @param [in] fastestStrideLength: Stride in the fastest + direction. */ inline AXOM_HOST_DEVICE void initializeShape(const axom::StackArray& shape, - int order) + int order, + int fastestStrideLength = 1) { SLIC_ASSERT(order == ArrayStrideOrder::COLUMN || order == ArrayStrideOrder::ROW); @@ -113,7 +119,7 @@ class ArrayIndexer { m_slowestDirs[d] = DIM - 1 - d; } - m_strides[0] = 1; + m_strides[0] = fastestStrideLength; for(int d = 1; d < DIM; ++d) { m_strides[d] = m_strides[d - 1] * shape[d - 1]; @@ -125,14 +131,12 @@ class ArrayIndexer { m_slowestDirs[d] = d; } - m_strides[DIM - 1] = 1; + m_strides[DIM - 1] = fastestStrideLength; for(int d = DIM - 2; d >= 0; --d) { m_strides[d] = m_strides[d + 1] * shape[d + 1]; } } - SLIC_ASSERT((DIM == 1 && getStrideOrder() == ArrayStrideOrder::BOTH) || - (getStrideOrder() == order)); } /*! diff --git a/src/axom/quest/tests/quest_array_indexer.cpp b/src/axom/quest/tests/quest_array_indexer.cpp index 4e3b3b4172..c637c7d0bf 100644 --- a/src/axom/quest/tests/quest_array_indexer.cpp +++ b/src/axom/quest/tests/quest_array_indexer.cpp @@ -20,8 +20,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::StackArray lengths {2}; axom::ArrayIndexer colIndexer( - lengths, - int(axom::ArrayStrideOrder::COLUMN)); + lengths, axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); axom::StackArray colSlowestDirs {0}; axom::StackArray colStrides {1}; @@ -35,8 +34,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) (axom::ArrayIndexer(lengths, colSlowestDirs))); axom::ArrayIndexer rowIndexer( - lengths, - int(axom::ArrayStrideOrder::ROW)); + lengths, axom::ArrayStrideOrder::ROW); EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); axom::StackArray rowSlowestDirs {0}; axom::StackArray rowStrides {1}; @@ -55,8 +53,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::StackArray lengths {3, 2}; axom::ArrayIndexer colIndexer( - lengths, - int(axom::ArrayStrideOrder::COLUMN)); + lengths, axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::StackArray colSlowestDirs {0, 1}; axom::StackArray colStrides {2, 1}; @@ -67,8 +64,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) } axom::ArrayIndexer rowIndexer( - lengths, - int(axom::ArrayStrideOrder::ROW)); + lengths, axom::ArrayStrideOrder::ROW); EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::StackArray rowSlowestDirs {1, 0}; axom::StackArray rowStrides {1, 3}; @@ -84,8 +80,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer colIndexer( - lengths, - int(axom::ArrayStrideOrder::COLUMN)); + lengths, axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::StackArray colSlowestDirs {0, 1, 2}; axom::StackArray colStrides {6, 2, 1}; @@ -96,8 +91,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) } axom::ArrayIndexer rowIndexer( - lengths, - int(axom::ArrayStrideOrder::ROW)); + lengths, axom::ArrayStrideOrder::ROW); EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::StackArray rowSlowestDirs {2, 1, 0}; axom::StackArray rowStrides {1, 5, 15}; @@ -117,10 +111,8 @@ TEST(quest_array_indexer, quest_col_major_offset) constexpr int DIM = 1; axom::StackArray lengths {3}; axom::ArrayIndexer ai( - lengths, - int(axom::ArrayStrideOrder::COLUMN)); - EXPECT_EQ(ai.getStrideOrder(), - int(axom::ArrayStrideOrder::COLUMN) | axom::ArrayStrideOrder::ROW); + lengths, axom::ArrayStrideOrder::COLUMN); + EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -135,8 +127,7 @@ TEST(quest_array_indexer, quest_col_major_offset) constexpr int DIM = 2; axom::StackArray lengths {3, 2}; axom::ArrayIndexer ai( - lengths, - int(axom::ArrayStrideOrder::COLUMN)); + lengths, axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) @@ -154,8 +145,7 @@ TEST(quest_array_indexer, quest_col_major_offset) // 3D axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer ai( - lengths, - int(axom::ArrayStrideOrder::COLUMN)); + lengths, axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) @@ -182,9 +172,8 @@ TEST(quest_array_indexer, quest_row_major_offset) constexpr int DIM = 1; axom::StackArray lengths {3}; axom::ArrayIndexer ai(lengths, - int(axom::ArrayStrideOrder::ROW)); - EXPECT_EQ(ai.getStrideOrder(), - int(axom::ArrayStrideOrder::ROW) | axom::ArrayStrideOrder::COLUMN); + axom::ArrayStrideOrder::ROW); + EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -199,7 +188,7 @@ TEST(quest_array_indexer, quest_row_major_offset) constexpr int DIM = 2; axom::StackArray lengths {3, 2}; axom::ArrayIndexer ai(lengths, - int(axom::ArrayStrideOrder::ROW)); + axom::ArrayStrideOrder::ROW); EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::IndexType offset = 0; for(int j = 0; j < lengths[1]; ++j) @@ -218,7 +207,7 @@ TEST(quest_array_indexer, quest_row_major_offset) constexpr int DIM = 3; axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer ai(lengths, - int(axom::ArrayStrideOrder::ROW)); + axom::ArrayStrideOrder::ROW); EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::IndexType offset = 0; for(int k = 0; k < lengths[2]; ++k) @@ -408,8 +397,7 @@ TEST(quest_array_indexer, quest_array_match) axom::StackArray lengths {3, 2}; axom::Array a(lengths); axom::ArrayIndexer ai( - lengths, - int(axom::ArrayStrideOrder::COLUMN)); + lengths, axom::ArrayStrideOrder::COLUMN); for(axom::IndexType i = 0; i < lengths[0]; ++i) { for(axom::IndexType j = 0; j < lengths[1]; ++j) @@ -424,8 +412,7 @@ TEST(quest_array_indexer, quest_array_match) axom::StackArray lengths {5, 3, 2}; axom::Array a(lengths); axom::ArrayIndexer ai( - lengths, - int(axom::ArrayStrideOrder::COLUMN)); + lengths, axom::ArrayStrideOrder::COLUMN); for(axom::IndexType i = 0; i < lengths[0]; ++i) { for(axom::IndexType j = 0; j < lengths[1]; ++j) From cec01a0b743802acb635b078e3fc1537faa62212 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 29 Feb 2024 15:39:31 -0800 Subject: [PATCH 46/61] Update comments for MarchingCubes constructor and setMesh. --- src/axom/quest/MarchingCubes.hpp | 33 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 629b01d43c..c11c57ee05 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -106,10 +106,10 @@ class MarchingCubes { public: using RuntimePolicy = axom::runtime_policy::Policy; - using DomainIdType = int; + using DomainIdType = axom::IndexType; /*! - \brief Constructor sets up computational mesh and data for running the - marching cubes algorithm. + \brief Constructor sets up runtime preferences for the marching + cubes implementation. \param [in] runtimePolicy A value from RuntimePolicy. The simplest policy is RuntimePolicy::seq, which specifies @@ -117,34 +117,29 @@ class MarchingCubes \param [in] allocatorID Data allocator ID. Choose something compatible with \c runtimePolicy. See \c execution_space. \param [in] dataParallelism Data parallel implementation choice. + */ + MarchingCubes(RuntimePolicy runtimePolicy, + int allocatorId, + MarchingCubesDataParallelism dataParallelism); + + /*! + @brief Set the input mesh. \param [in] bpMesh Blueprint multi-domain mesh containing scalar field. \param [in] topologyName Name of Blueprint topology to use in \a bpMesh. \param [in] maskField Cell-based std::int32_t mask field. If provided, cells where this field evaluates to false are skipped. Array data in \a dom must be accessible in the \a runtimePolicy - environment. It's an error if not, e.g., using CPU memory with - a GPU policy. - - Some metadata from \a bpMesh may be cached by the constructor. - Any change to it after the constructor leads to undefined behavior. + environment specified in the constructor. It's an error if not, + e.g., using CPU memory with a GPU policy. - The mesh coordinates should be contiguous. See - conduit::blueprint::is_contiguous(). In the future, this - requirement may be relaxed, possibly at the cost of a - transformation and storage of the temporary contiguous layout. + Some metadata from \a bpMesh may be cached. Any change to it + after setMesh() leads to undefined behavior. Blueprint allows users to specify ids for the domains. If "state/domain_id" exists in the domains, it is used as the domain id. Otherwise, the domain's interation index within the multidomain mesh is used. - */ - MarchingCubes(RuntimePolicy runtimePolicy, - int allocatorId, - MarchingCubesDataParallelism dataParallelism); - - /*! - @brief Set the input mesh. @see clearMesh() */ From f34ef4fdcaa99f0ac1e60157f0631315b51ecafe Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 1 Mar 2024 15:08:46 -0800 Subject: [PATCH 47/61] Add domain ids to the data relinquished. --- src/axom/quest/MarchingCubes.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index c11c57ee05..834e3f7f06 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -247,8 +247,6 @@ class MarchingCubes return m_facetDomainIds.view(); } - #if 1 - // Is there a use case for this? /*! @brief Give caller posession of the contour data. @@ -256,20 +254,24 @@ class MarchingCubes to stay in scope after the MarchingCubes object is deleted. @pre isoContour() must have been called. - @post outputs can no longer be accessed from object. + @post outputs can no longer be accessed from object, as though + clearOutput() has been called. */ void relinquishContourData(axom::Array &facetNodeIds, axom::Array &facetNodeCoords, - axom::Array &facetParentIds) + axom::Array &facetParentIds, + axom::Array& facetDomainIds) { + facetNodeIds.clear(); + facetNodeCoords.clear(); + facetParentIds.clear(); + facetDomainIds.clear(); + facetNodeIds.swap(m_facetNodeIds); facetNodeCoords.swap(m_facetNodeCoords); facetParentIds.swap(m_facetParentIds); - m_facetNodeIds.clear(); - m_facetNodeCoords.clear(); - m_facetParentIds.clear(); + facetDomainIds.swap(m_facetDomainIds); } - #endif //@} /*! From ab4ab927027a9511bfc75fb3dca80b3944aca58f Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sat, 2 Mar 2024 17:56:20 -0800 Subject: [PATCH 48/61] Zero out m_facetCount after relinquishing output data. Test for zero otuput. Silence waring. --- .../mint/mesh/internal/ConnectivityArrayHelpers.hpp | 4 ++-- src/axom/quest/MarchingCubes.hpp | 3 ++- .../quest/examples/quest_marching_cubes_example.cpp | 12 ++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/axom/mint/mesh/internal/ConnectivityArrayHelpers.hpp b/src/axom/mint/mesh/internal/ConnectivityArrayHelpers.hpp index cfbe757be2..eac075d62f 100644 --- a/src/axom/mint/mesh/internal/ConnectivityArrayHelpers.hpp +++ b/src/axom/mint/mesh/internal/ConnectivityArrayHelpers.hpp @@ -137,8 +137,8 @@ inline CellType initializeFromGroup( "sidre::Group " << group->getPathName() << " does not conform to mesh blueprint."); - sidre::View* type_view = elems_group->getView("types"); - m_types = std::make_unique>(type_view); + sidre::View* elem_type_view = elems_group->getView("types"); + m_types = std::make_unique>(elem_type_view); } return cell_type; diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 834e3f7f06..6e8fda8064 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -253,7 +253,7 @@ class MarchingCubes This efficiently turns the generated contour data to the caller, to stay in scope after the MarchingCubes object is deleted. - @pre isoContour() must have been called. + @pre computeIsocontour() must have been called. @post outputs can no longer be accessed from object, as though clearOutput() has been called. */ @@ -266,6 +266,7 @@ class MarchingCubes facetNodeCoords.clear(); facetParentIds.clear(); facetDomainIds.clear(); + m_facetCount = 0; facetNodeIds.swap(m_facetNodeIds); facetNodeCoords.swap(m_facetNodeCoords); diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index b75df0e378..7d95745044 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -890,6 +890,18 @@ struct ContourTestBase extractTimer.stop(); printTimingStats(extractTimer, "extract"); + { + axom::Array facetNodeIds; + axom::Array facetNodeCoords; + axom::Array facetParentIds; + axom::Array facetDomainIds; + mc.relinquishContourData( facetNodeIds, + facetNodeCoords, + facetParentIds, + facetDomainIds ); + SLIC_ASSERT(mc.getContourFacetCount() == 0); + } + int localErrCount = 0; if(params.checkResults) { From 44dc828d57295381cb636d2e832182cf62583fce Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 4 Mar 2024 22:01:28 -0800 Subject: [PATCH 49/61] Autoformat. --- src/axom/quest/ArrayIndexer.hpp | 8 +- src/axom/quest/MarchingCubes.cpp | 8 +- src/axom/quest/MarchingCubes.hpp | 4 +- src/axom/quest/MeshViewUtil.hpp | 21 ++- src/axom/quest/detail/MarchingCubesImpl.hpp | 4 +- .../examples/quest_marching_cubes_example.cpp | 156 ++++++++++-------- src/axom/quest/tests/quest_array_indexer.cpp | 38 +++-- 7 files changed, 133 insertions(+), 106 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index f4a797a8f9..9f36ff3c34 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -43,7 +43,8 @@ class ArrayIndexer @param [in] fastestStrideLength: Stride in the fastest direction. */ - ArrayIndexer(const axom::StackArray& shape, int order, + ArrayIndexer(const axom::StackArray& shape, + int order, int fastestStrideLength = 1) { initializeShape(shape, order, fastestStrideLength); @@ -70,9 +71,8 @@ class ArrayIndexer @param [in] orderSource: ArrayIndex to copy stride order from. */ - ArrayIndexer( - const axom::StackArray& shape, - const axom::ArrayIndexer& orderSource) + ArrayIndexer(const axom::StackArray& shape, + const axom::ArrayIndexer& orderSource) { initializeShape(shape, orderSource); } diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 163ec92b47..6aa7e49815 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -135,11 +135,9 @@ void MarchingCubes::computeIsocontour(double contourVal) { const auto domainId = m_singles[d]->getDomainId(d); const auto domainFacetCount = - ( d < m_singles.size() - 1 ? m_facetIndexOffsets[d+1] : m_facetCount ) - - m_facetIndexOffsets[d]; - m_facetDomainIds.fill(domainId, - domainFacetCount, - m_facetIndexOffsets[d]); + (d < m_singles.size() - 1 ? m_facetIndexOffsets[d + 1] : m_facetCount) - + m_facetIndexOffsets[d]; + m_facetDomainIds.fill(domainId, domainFacetCount, m_facetIndexOffsets[d]); } } diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index 6e8fda8064..c09f43be6b 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -171,7 +171,7 @@ class MarchingCubes axom::IndexType getContourNodeCount() const; //@{ - //!@name Output methods + //!@name Output methods (experimental interface, subject to change) /*! @brief Put generated contour in a mint::UnstructuredMesh. @param mesh Output contour mesh @@ -260,7 +260,7 @@ class MarchingCubes void relinquishContourData(axom::Array &facetNodeIds, axom::Array &facetNodeCoords, axom::Array &facetParentIds, - axom::Array& facetDomainIds) + axom::Array &facetDomainIds) { facetNodeIds.clear(); facetNodeCoords.clear(); diff --git a/src/axom/quest/MeshViewUtil.hpp b/src/axom/quest/MeshViewUtil.hpp index 0db7104ebf..9b64fab789 100644 --- a/src/axom/quest/MeshViewUtil.hpp +++ b/src/axom/quest/MeshViewUtil.hpp @@ -676,17 +676,19 @@ class MeshViewUtil const MdIndices& strides, const MdIndices& offsets) { - SLIC_ERROR_IF( - m_dom == nullptr, - axom::fmt::format("Cannot create field {}." - " MeshViewUtil was not constructed with a mutable domain.", fieldName)); + SLIC_ERROR_IF(m_dom == nullptr, + axom::fmt::format( + "Cannot create field {}." + " MeshViewUtil was not constructed with a mutable domain.", + fieldName)); SLIC_ERROR_IF( m_dom->has_path("fields/" + fieldName), axom::fmt::format("Cannot create field {}. It already exists.", fieldName)); SLIC_ERROR_IF( association != "vertex" && association != "element", - axom::fmt::format("MeshViewUtil doesn't support association '{}' yet.", association)); + axom::fmt::format("MeshViewUtil doesn't support association '{}' yet.", + association)); const auto& realShape = getRealExtents(association); MdIndices loPads, hiPads, paddedShape, strideOrder; @@ -756,10 +758,11 @@ class MeshViewUtil const MdIndices& hiPads, const MdIndices& strideOrder) { - SLIC_ERROR_IF( - m_dom == nullptr, - axom::fmt::format("Cannot create field {}." - " MeshViewUtil was not constructed with a mutable domain.", fieldName)); + SLIC_ERROR_IF(m_dom == nullptr, + axom::fmt::format( + "Cannot create field {}." + " MeshViewUtil was not constructed with a mutable domain.", + fieldName)); SLIC_ERROR_IF( m_dom->has_path("fields/" + fieldName), axom::fmt::format("Cannot create field {}. It already exists.", fieldName)); diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 5a6940fbf7..6e90497daa 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -98,8 +98,8 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase compatible with ExecSpace. */ AXOM_HOST void setDomain(const conduit::Node& dom, - const std::string& topologyName, - const std::string& maskFieldName) override + const std::string& topologyName, + const std::string& maskFieldName) override { // Time this due to potentially slow memory allocation AXOM_PERF_MARK_FUNCTION("MarchingCubesImpl::initialize"); diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 7d95745044..99c58ec5b1 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -694,7 +694,7 @@ static void addToStackArray(axom::StackArray& a, U b) The strategy encapsulates the scalar functions and things related to it. */ -template +template struct ContourTestStrategy { using PointType = axom::primal::Point; @@ -711,7 +711,7 @@ struct ContourTestStrategy //!@brief Return the analytical value of the scalar field. virtual double valueAt(const PointType& pt) const = 0; - virtual ~ContourTestStrategy() {} + virtual ~ContourTestStrategy() { } }; template @@ -728,7 +728,7 @@ struct ContourTestBase { } virtual ~ContourTestBase() { } - void addTestStrategy( const std::shared_ptr>& testStrategy ) + void addTestStrategy(const std::shared_ptr>& testStrategy) { m_testStrategies.push_back(testStrategy); SLIC_INFO(axom::fmt::format("Add test {}.", testStrategy->testName())); @@ -744,7 +744,7 @@ struct ContourTestBase int runTest(BlueprintStructuredMesh& computationalMesh) { // Compute the nodal distance functions. - for( const auto& strategy : m_testStrategies ) + for(const auto& strategy : m_testStrategies) { computeNodalDistance(computationalMesh, *strategy); } @@ -759,7 +759,7 @@ struct ContourTestBase computationalMesh.coordsetPath() + "/values/" + axes[d], s_allocatorId); } - for( const auto& strategy : m_testStrategies ) + for(const auto& strategy : m_testStrategies) { computationalMesh.moveMeshDataToNewMemorySpace( axom::fmt::format("fields/{}/values", strategy->functionName()), @@ -776,7 +776,7 @@ struct ContourTestBase { std::string resourceName = "HOST"; umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); - for( const auto& strategy : m_testStrategies ) + for(const auto& strategy : m_testStrategies) { const std::string dataPath = axom::fmt::format("fields/{}/values", strategy->functionName()); @@ -796,12 +796,12 @@ struct ContourTestBase { SLIC_ASSERT(resourceName == "HOST"); } -#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) else if(params.policy == axom::runtime_policy::Policy::omp) { SLIC_ASSERT(resourceName == "HOST"); } -#endif + #endif else { SLIC_ASSERT(resourceName == "DEVICE"); @@ -840,7 +840,7 @@ struct ContourTestBase mc.clearOutput(); m_strategyFacetPrefixSum.clear(); m_strategyFacetPrefixSum.push_back(0); - for( const auto& strategy : m_testStrategies ) + for(const auto& strategy : m_testStrategies) { mc.setFunctionField(strategy->functionName()); mc.computeIsocontour(params.contourVal); @@ -867,7 +867,7 @@ struct ContourTestBase computationalMesh.coordsetPath() + "/values/" + axes[d], axom::execution_space::allocatorID()); } - for( const auto& strategy : m_testStrategies ) + for(const auto& strategy : m_testStrategies) { computationalMesh.moveMeshDataToNewMemorySpace( axom::fmt::format("fields/{}/values", strategy->functionName()), @@ -895,10 +895,10 @@ struct ContourTestBase axom::Array facetNodeCoords; axom::Array facetParentIds; axom::Array facetDomainIds; - mc.relinquishContourData( facetNodeIds, - facetNodeCoords, - facetParentIds, - facetDomainIds ); + mc.relinquishContourData(facetNodeIds, + facetNodeCoords, + facetParentIds, + facetDomainIds); SLIC_ASSERT(mc.getContourFacetCount() == 0); } @@ -914,7 +914,7 @@ struct ContourTestBase checkCellsContainingContour(computationalMesh, contourMesh); } - if (contourMesh.hasSidreGroup()) + if(contourMesh.hasSidreGroup()) { assert(contourMesh.getSidreGroup() == meshGroup); // Write contour mesh to file. @@ -956,11 +956,12 @@ struct ContourTestBase auto domainView = bpMesh.getDomainView(domId); // Create nodal function data with ghosts like node coords. - domainView.createField(strat.functionName(), - "vertex", - conduit::DataType::float64(domainView.getCoordsCountWithGhosts()), - domainView.getCoordsStrides(), - domainView.getCoordsOffsets()); + domainView.createField( + strat.functionName(), + "vertex", + conduit::DataType::float64(domainView.getCoordsCountWithGhosts()), + domainView.getCoordsStrides(), + domainView.getCoordsOffsets()); } for(int domId = 0; domId < bpMesh.domainCount(); ++domId) @@ -978,8 +979,7 @@ struct ContourTestBase } template typename std::enable_if::type populateNodalDistance( - const axom::StackArray, DIM>& - coordsViews, + const axom::StackArray, DIM>& coordsViews, axom::ArrayView& fieldView, ContourTestStrategy& strat) { @@ -1005,8 +1005,7 @@ struct ContourTestBase template typename std::enable_if::type populateNodalDistance( - const axom::StackArray, DIM>& - coordsViews, + const axom::StackArray, DIM>& coordsViews, axom::ArrayView& fieldView, ContourTestStrategy& strat) { @@ -1060,7 +1059,8 @@ struct ContourTestBase double tol = strategy.errorTolerance(); PointType pt; - for(axom::IndexType iNode = contourNodeBegin; iNode < contourNodeEnd; ++iNode) + for(axom::IndexType iNode = contourNodeBegin; iNode < contourNodeEnd; + ++iNode) { contourMesh.getNode(iNode, pt.data()); double analyticalVal = strategy.valueAt(pt); @@ -1191,15 +1191,16 @@ struct ContourTestBase { auto contourCellBegin = m_strategyFacetPrefixSum[iStrat]; auto contourCellEnd = m_strategyFacetPrefixSum[iStrat + 1]; - for(axom::IndexType iContourCell = contourCellBegin; iContourCell < contourCellEnd; + for(axom::IndexType iContourCell = contourCellBegin; + iContourCell < contourCellEnd; ++iContourCell) { axom::quest::MarchingCubes::DomainIdType domainId = domainIdView[iContourCell]; axom::quest::MarchingCubes::DomainIdType contiguousIndex = domainIdToContiguousId[domainId]; - typename axom::quest::MeshViewUtil::ConstCoordsViewsType& - coordsViews = allCoordsViews[contiguousIndex]; + typename axom::quest::MeshViewUtil::ConstCoordsViewsType& coordsViews = + allCoordsViews[contiguousIndex]; axom::IndexType parentCellId = parentCellIdView[iContourCell]; @@ -1221,7 +1222,7 @@ struct ContourTestBase axom::primal::BoundingBox small(parentCellBox); auto range = parentCellBox.range(); bool checkSmall = range >= tol; - if (checkSmall) + if(checkSmall) { small.expand(-tol); } @@ -1286,7 +1287,9 @@ struct ContourTestBase // Compute mapping to look up domains from the domain id. // std::map domainIdToContiguousId; - for(axom::quest::MarchingCubes::DomainIdType iDomain = 0; iDomain < domainCount; ++iDomain) + for(axom::quest::MarchingCubes::DomainIdType iDomain = 0; + iDomain < domainCount; + ++iDomain) { const auto& dom = computationalMesh.domain(iDomain); axom::quest::MarchingCubes::DomainIdType domainId = iDomain; @@ -1309,7 +1312,8 @@ struct ContourTestBase axom::Array> hasContours(domainCount); for(axom::IndexType domId = 0; domId < domainCount; ++domId) { - axom::quest::MeshViewUtil domainView = computationalMesh.getDomainView(domId); + axom::quest::MeshViewUtil domainView = + computationalMesh.getDomainView(domId); const axom::IndexType cellCount = domainView.getCellCount(); @@ -1322,7 +1326,8 @@ struct ContourTestBase auto contourCellBegin = m_strategyFacetPrefixSum[iStrat]; auto contourCellEnd = m_strategyFacetPrefixSum[iStrat + 1]; axom::IndexType bitFlag = (1 << iStrat); - for(axom::IndexType iContourCell = contourCellBegin; iContourCell < contourCellEnd; + for(axom::IndexType iContourCell = contourCellBegin; + iContourCell < contourCellEnd; ++iContourCell) { axom::quest::MarchingCubes::DomainIdType domainId = @@ -1347,7 +1352,8 @@ struct ContourTestBase const axom::IndexType parentCellCount = domainView.getCellCount(); // axom::Array hasContour(parentCellCount, parentCellCount); - for(axom::IndexType parentCellId = 0; parentCellId < parentCellCount; ++parentCellId) + for(axom::IndexType parentCellId = 0; parentCellId < parentCellCount; + ++parentCellId) { const axom::IndexType hasContourBits = hasContours[domId][parentCellId]; @@ -1356,12 +1362,13 @@ struct ContourTestBase auto& strategy = *m_testStrategies[iStrat]; const axom::IndexType iStratBit = (1 << iStrat); - const auto& fcnView = - domainView.template getConstFieldView(strategy.functionName(), false); + const auto& fcnView = domainView.template getConstFieldView( + strategy.functionName(), + false); - axom::ArrayIndexer - cellIndexer(domLengths, - axom::ArrayIndexer(fcnView.strides())); + axom::ArrayIndexer cellIndexer( + domLengths, + axom::ArrayIndexer(fcnView.strides())); axom::StackArray parentCellIdx = cellIndexer.toMultiIndex(parentCellId); @@ -1402,14 +1409,15 @@ struct ContourTestBase if(touchesContour != hasContour) { ++errCount; - SLIC_INFO_IF(params.isVerbose(), - axom::fmt::format( - "checkCellsContainingContour: cell {}: hasContour " - "({}) and touchesContour ({}) don't agree for strategy {}.", - parentCellIdx, - hasContour, - touchesContour, - strategy.testName())); + SLIC_INFO_IF( + params.isVerbose(), + axom::fmt::format( + "checkCellsContainingContour: cell {}: hasContour " + "({}) and touchesContour ({}) don't agree for strategy {}.", + parentCellIdx, + hasContour, + touchesContour, + strategy.testName())); } } } @@ -1423,15 +1431,19 @@ struct ContourTestBase }; template -struct PlanarTestStrategy : public ContourTestStrategy { +struct PlanarTestStrategy : public ContourTestStrategy +{ using PointType = axom::primal::Point; PlanarTestStrategy(const axom::primal::Vector& perpDir, const PointType& inPlane) - : ContourTestStrategy() - , _plane(perpDir.unitVector(), inPlane) - , _errTol(axom::numerics::floating_point_limits::epsilon()) - {} - virtual std::string testName() const override { return std::string("planar"); } + : ContourTestStrategy() + , _plane(perpDir.unitVector(), inPlane) + , _errTol(axom::numerics::floating_point_limits::epsilon()) + { } + virtual std::string testName() const override + { + return std::string("planar"); + } virtual std::string functionName() const override { return std::string("dist_to_plane"); @@ -1446,13 +1458,14 @@ struct PlanarTestStrategy : public ContourTestStrategy { }; template -struct RoundTestStrategy : public ContourTestStrategy { +struct RoundTestStrategy : public ContourTestStrategy +{ using PointType = axom::primal::Point; RoundTestStrategy(const PointType& center) - : ContourTestStrategy() - , _sphere(center, 0.0) - , _errTol(1e-3) - {} + : ContourTestStrategy() + , _sphere(center, 0.0) + , _errTol(1e-3) + { } virtual std::string testName() const override { return std::string("round"); } virtual std::string functionName() const override { @@ -1474,14 +1487,18 @@ struct RoundTestStrategy : public ContourTestStrategy { }; template -struct GyroidTestStrategy : public ContourTestStrategy { +struct GyroidTestStrategy : public ContourTestStrategy +{ using PointType = axom::primal::Point; GyroidTestStrategy(const PointType& scale, double offset) - : ContourTestStrategy() - , _scale(scale) - , _offset(offset) - {} - virtual std::string testName() const override { return std::string("gyroid"); } + : ContourTestStrategy() + , _scale(scale) + , _offset(offset) + { } + virtual std::string testName() const override + { + return std::string("gyroid"); + } virtual std::string functionName() const override { return std::string("gyroid_fcn"); @@ -1609,22 +1626,25 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) if(params.usingPlanar()) { - planarStrat = std::make_shared>(params.planeNormal(), - params.inplanePoint()); + planarStrat = + std::make_shared>(params.planeNormal(), + params.inplanePoint()); contourTest.addTestStrategy(planarStrat); } if(params.usingRound()) { - roundStrat = std::make_shared>(params.roundContourCenter()); + roundStrat = + std::make_shared>(params.roundContourCenter()); roundStrat->setToleranceByLongestEdge(computationalMesh); contourTest.addTestStrategy(roundStrat); } if(params.usingGyroid()) { - gyroidStrat = std::make_shared>(params.gyroidScaleFactor(), - params.contourVal); + gyroidStrat = + std::make_shared>(params.gyroidScaleFactor(), + params.contourVal); gyroidStrat->setToleranceByLongestEdge(computationalMesh); contourTest.addTestStrategy(gyroidStrat); } diff --git a/src/axom/quest/tests/quest_array_indexer.cpp b/src/axom/quest/tests/quest_array_indexer.cpp index c637c7d0bf..70d6b601b2 100644 --- a/src/axom/quest/tests/quest_array_indexer.cpp +++ b/src/axom/quest/tests/quest_array_indexer.cpp @@ -20,7 +20,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::StackArray lengths {2}; axom::ArrayIndexer colIndexer( - lengths, axom::ArrayStrideOrder::COLUMN); + lengths, + axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); axom::StackArray colSlowestDirs {0}; axom::StackArray colStrides {1}; @@ -34,7 +35,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) (axom::ArrayIndexer(lengths, colSlowestDirs))); axom::ArrayIndexer rowIndexer( - lengths, axom::ArrayStrideOrder::ROW); + lengths, + axom::ArrayStrideOrder::ROW); EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); axom::StackArray rowSlowestDirs {0}; axom::StackArray rowStrides {1}; @@ -53,7 +55,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::StackArray lengths {3, 2}; axom::ArrayIndexer colIndexer( - lengths, axom::ArrayStrideOrder::COLUMN); + lengths, + axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::StackArray colSlowestDirs {0, 1}; axom::StackArray colStrides {2, 1}; @@ -64,7 +67,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) } axom::ArrayIndexer rowIndexer( - lengths, axom::ArrayStrideOrder::ROW); + lengths, + axom::ArrayStrideOrder::ROW); EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::StackArray rowSlowestDirs {1, 0}; axom::StackArray rowStrides {1, 3}; @@ -80,7 +84,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer colIndexer( - lengths, axom::ArrayStrideOrder::COLUMN); + lengths, + axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::StackArray colSlowestDirs {0, 1, 2}; axom::StackArray colStrides {6, 2, 1}; @@ -91,7 +96,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) } axom::ArrayIndexer rowIndexer( - lengths, axom::ArrayStrideOrder::ROW); + lengths, + axom::ArrayStrideOrder::ROW); EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); axom::StackArray rowSlowestDirs {2, 1, 0}; axom::StackArray rowStrides {1, 5, 15}; @@ -110,8 +116,8 @@ TEST(quest_array_indexer, quest_col_major_offset) // 1D constexpr int DIM = 1; axom::StackArray lengths {3}; - axom::ArrayIndexer ai( - lengths, axom::ArrayStrideOrder::COLUMN); + axom::ArrayIndexer ai(lengths, + axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) @@ -126,8 +132,8 @@ TEST(quest_array_indexer, quest_col_major_offset) // 2D constexpr int DIM = 2; axom::StackArray lengths {3, 2}; - axom::ArrayIndexer ai( - lengths, axom::ArrayStrideOrder::COLUMN); + axom::ArrayIndexer ai(lengths, + axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) @@ -144,8 +150,8 @@ TEST(quest_array_indexer, quest_col_major_offset) { // 3D axom::StackArray lengths {5, 3, 2}; - axom::ArrayIndexer ai( - lengths, axom::ArrayStrideOrder::COLUMN); + axom::ArrayIndexer ai(lengths, + axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) @@ -396,8 +402,8 @@ TEST(quest_array_indexer, quest_array_match) constexpr int DIM = 2; axom::StackArray lengths {3, 2}; axom::Array a(lengths); - axom::ArrayIndexer ai( - lengths, axom::ArrayStrideOrder::COLUMN); + axom::ArrayIndexer ai(lengths, + axom::ArrayStrideOrder::COLUMN); for(axom::IndexType i = 0; i < lengths[0]; ++i) { for(axom::IndexType j = 0; j < lengths[1]; ++j) @@ -411,8 +417,8 @@ TEST(quest_array_indexer, quest_array_match) constexpr int DIM = 3; axom::StackArray lengths {5, 3, 2}; axom::Array a(lengths); - axom::ArrayIndexer ai( - lengths, axom::ArrayStrideOrder::COLUMN); + axom::ArrayIndexer ai(lengths, + axom::ArrayStrideOrder::COLUMN); for(axom::IndexType i = 0; i < lengths[0]; ++i) { for(axom::IndexType j = 0; j < lengths[1]; ++j) From dfef1574c7c82b0e35b7ca6a6c49635899dcb96a Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 5 Mar 2024 08:55:02 -0800 Subject: [PATCH 50/61] Re-disable plotting of test mesh, which I enabled for debugging. --- .../examples/quest_marching_cubes_example.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 99c58ec5b1..8eaa754b0a 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -743,12 +743,6 @@ struct ContourTestBase int runTest(BlueprintStructuredMesh& computationalMesh) { - // Compute the nodal distance functions. - for(const auto& strategy : m_testStrategies) - { - computeNodalDistance(computationalMesh, *strategy); - } - // Conduit data is in host memory, move to devices for testing. if(s_allocatorId != axom::execution_space::allocatorID()) { @@ -882,8 +876,7 @@ struct ContourTestBase sidre::Group* meshGroup = objectDS.getRoot()->createGroup(sidreGroupName); axom::mint::UnstructuredMesh contourMesh( DIM, - DIM == 2 ? mint::CellType::SEGMENT : mint::CellType::TRIANGLE, - meshGroup); + DIM == 2 ? mint::CellType::SEGMENT : mint::CellType::TRIANGLE); axom::utilities::Timer extractTimer(false); extractTimer.start(); mc.populateContourMesh(contourMesh, m_parentCellIdField, m_domainIdField); @@ -1031,6 +1024,13 @@ struct ContourTestBase } } } + void computeNodalDistance(BlueprintStructuredMesh& bpMesh) + { + for( auto& strategy : m_testStrategies ) + { + computeNodalDistance(bpMesh, *strategy); + } + } /** Check for errors in the surface contour mesh. @@ -1649,6 +1649,8 @@ int testNdimInstance(BlueprintStructuredMesh& computationalMesh) contourTest.addTestStrategy(gyroidStrat); } + contourTest.computeNodalDistance(computationalMesh); + if(params.isVerbose()) { computationalMesh.printMeshInfo(); From 02fab158245716b24ac58248513cd202e25b647c Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 5 Mar 2024 08:55:40 -0800 Subject: [PATCH 51/61] Fix an error failing 64-bit tests. --- src/axom/quest/examples/quest_marching_cubes_example.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 8eaa754b0a..a615fb5bb8 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -1170,7 +1170,7 @@ struct ContourTestBase axom::quest::MarchingCubes::DomainIdType domainId = iDomain; if(dom.has_path("state/domain_id")) { - domainId = dom.fetch_existing("state/domain_id").value(); + domainId = dom.fetch_existing("state/domain_id").to_value(); } domainIdToContiguousId[domainId] = iDomain; } @@ -1295,7 +1295,7 @@ struct ContourTestBase axom::quest::MarchingCubes::DomainIdType domainId = iDomain; if(dom.has_path("state/domain_id")) { - domainId = dom.fetch_existing("state/domain_id").value(); + domainId = dom.fetch_existing("state/domain_id").to_value(); } domainIdToContiguousId[domainId] = iDomain; } From 2b0b8b3667399de272f3cf104ca6eb89da46321b Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 5 Mar 2024 21:10:17 -0800 Subject: [PATCH 52/61] Autoformat. --- src/axom/quest/examples/quest_marching_cubes_example.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index a615fb5bb8..09aaf29a2c 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -1026,7 +1026,7 @@ struct ContourTestBase } void computeNodalDistance(BlueprintStructuredMesh& bpMesh) { - for( auto& strategy : m_testStrategies ) + for(auto& strategy : m_testStrategies) { computeNodalDistance(bpMesh, *strategy); } From e252b06d2c21853e915282d0445f644210f026fd Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 5 Mar 2024 21:48:36 -0800 Subject: [PATCH 53/61] Update release notes. --- RELEASE-NOTES.md | 5 +++++ src/axom/quest/MarchingCubes.hpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e11aaefd22..1f37407379 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -36,6 +36,11 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ hexahedral meshes using either a BVH or Implicit Grid spatial index ### Changed +- `MarchingCubes` has optimizations to improve GPU performance, particularly for + repeated computations. The constructor has changed and a new `setMesh` method + is added to set (or change) the mesh. New accessors present output data + without moving them from device to host. These accessors are an interim + solution and likely to be updated in the future. - `DistributedClosestPoint` outputs are now controlled by the `setOutput` method. - `MarchingCubes` allows user to select the underlying data-parallel implementation - `fullParallel` works best on GPUs. diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index c09f43be6b..bd8b459aea 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -171,7 +171,7 @@ class MarchingCubes axom::IndexType getContourNodeCount() const; //@{ - //!@name Output methods (experimental interface, subject to change) + //!@name Output methods (interim interfaces, subject to change) /*! @brief Put generated contour in a mint::UnstructuredMesh. @param mesh Output contour mesh @@ -188,7 +188,7 @@ class MarchingCubes Important: mint::UnstructuredMesh only supports host memory, so regardless of the allocator ID, this method always deep-copies data to host memory. To access the data without deep-copying, see - the other output methods. + the other output methods in this name group. */ void populateContourMesh( axom::mint::UnstructuredMesh &mesh, From b4bb2e265a866e3ea2439d2340a47b6dbcecd45a Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 6 Mar 2024 11:31:18 -0800 Subject: [PATCH 54/61] Improve ArrayIndexer documentation by code review comments. --- src/axom/quest/ArrayIndexer.hpp | 54 +++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index 9f36ff3c34..760fcc9304 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -15,6 +15,13 @@ namespace axom { +/*! + @brief Indicator for stride ordering. + + Multidimensional array data can be in row-major order, column-major order, + or some arbitrarily permuted order. Row and column major ordering are the + same thing if the array is 1D. +*/ struct ArrayStrideOrder { static constexpr int ARBITRARY = 0; // Neither row nor column @@ -39,21 +46,22 @@ class ArrayIndexer /*! @brief Constructor for row- or column-major indexing. @param [in] shape Shape of the array - @param [in] order: c is column major; r is row major. - @param [in] fastestStrideLength: Stride in the fastest + @param [in] arrayStrideOrder A order indicator from + ArrayStrideOrder. + @param [in] fastestStrideLength Stride in the fastest direction. */ ArrayIndexer(const axom::StackArray& shape, - int order, + int arrayStrideOrder, int fastestStrideLength = 1) { - initializeShape(shape, order, fastestStrideLength); + initializeShape(shape, arrayStrideOrder, fastestStrideLength); } /*! @brief Constructor for a given order permutation. @param [in] shape Shape of the array - @param [in] slowestDirs: permutation vector, where + @param [in] slowestDirs permutation vector, where slowestDirs[0] is the slowest direction and slowestDirs[DIM-1] is the fastest. */ @@ -68,7 +76,7 @@ class ArrayIndexer existing ArrayIndexer. @param [in] shape Shape of the array - @param [in] orderSource: ArrayIndex to copy stride order + @param [in] orderSource ArrayIndex to copy stride order from. */ ArrayIndexer(const axom::StackArray& shape, @@ -83,9 +91,9 @@ class ArrayIndexer @param [i] strides Strides. Must be unique when DIM > 1. If not unique, use default constructor and initializeStrides(). - @internal We could add the order preference to this constructor to - handle the degenerate case of non-unique strides. But that would - clash with the more prevalent constructor taking the array's + @internal We could add the ArrayStrideOrder preference to this constructor + to handle the degenerate case of non-unique strides. But that would + clash with the more prevalent usage of constructing from the array's shape. */ ArrayIndexer(const axom::StackArray& strides) : m_strides(strides) @@ -103,17 +111,19 @@ class ArrayIndexer /*! @brief Initialize for row- or column-major indexing. @param [in] shape Shape of the array - @param [in] order: c is column major; r is row major. - @param [in] fastestStrideLength: Stride in the fastest + @param [in] arrayStrideOrder An order indicator from + ArrayStrideOrder. + @param [in] fastestStrideLength Stride in the fastest direction. */ inline AXOM_HOST_DEVICE void initializeShape(const axom::StackArray& shape, - int order, + int arrayStrideOrder, int fastestStrideLength = 1) { - SLIC_ASSERT(order == ArrayStrideOrder::COLUMN || - order == ArrayStrideOrder::ROW); - if(order == ArrayStrideOrder::ROW) + SLIC_ASSERT(arrayStrideOrder == ArrayStrideOrder::COLUMN || + arrayStrideOrder == ArrayStrideOrder::ROW || + (DIM == 1 && arrayStrideOrder == ArrayStrideOrder::BOTH)); + if(arrayStrideOrder == ArrayStrideOrder::ROW) { for(int d = 0; d < DIM; ++d) { @@ -142,7 +152,7 @@ class ArrayIndexer /*! @brief Initialize for a given order permutation. @param [in] shape Shape of the array - @param [in] slowestDirs: permutation vector, where + @param [in] slowestDirs permutation vector, where slowestDirs[0] is the slowest direction and slowestDirs[DIM-1] is the fastest. */ @@ -166,7 +176,7 @@ class ArrayIndexer existing ArrayIndexer. @param [in] shape Shape of the array - @param [in] orderSource: ArrayIndex to copy stride order + @param [in] orderSource ArrayIndex to copy stride order from. */ inline AXOM_HOST_DEVICE void initializeShape( @@ -212,8 +222,8 @@ class ArrayIndexer with ordering preference for non-unique strides. @param [i] strides Strides. - @param [i] orderPref Ordering preference if strides - are non-unique. + @param [i] orderPref Ordering preference value + (from ArrayStrideOrder) if strides are non-unique. */ inline AXOM_HOST_DEVICE void initializeStrides( const axom::StackArray& strides, @@ -246,7 +256,7 @@ class ArrayIndexer static AXOM_HOST_DEVICE bool stridesAreUnique(const axom::StackArray& strides) { bool repeats = false; - for(int d = 0; d < DIM; ++d) + for(int d = 1; d < DIM; ++d) { for(int e = 0; e < d; ++e) { @@ -300,8 +310,8 @@ class ArrayIndexer /*! @brief Get the stride order (row- or column-major, or something else). - @return 1 if row major, 2 if column major, 0 if neither - and, DIM == 1, 3 (satisfying both row and column ordering). + @return Value from ArrayStrideOrder, indicating column order, + row order, both column and row (1D only) or arbitrary order. */ inline AXOM_HOST_DEVICE int getStrideOrder() const { From ae0ceec5e1e8de4f7af7340842710f8218674d1f Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 7 Mar 2024 08:27:14 -0800 Subject: [PATCH 55/61] Implement comments from code review and change some variable names for consistency. --- src/axom/primal/geometry/Vector.hpp | 42 ------------------- src/axom/quest/ArrayIndexer.hpp | 2 +- src/axom/quest/MeshViewUtil.hpp | 30 ++++++------- src/axom/quest/detail/MarchingCubesImpl.hpp | 23 +++++----- .../detail/MarchingCubesSingleDomain.hpp | 35 +++++----------- .../examples/quest_marching_cubes_example.cpp | 16 +++++-- src/axom/quest/tests/quest_mesh_view_util.cpp | 17 ++++---- 7 files changed, 60 insertions(+), 105 deletions(-) diff --git a/src/axom/primal/geometry/Vector.hpp b/src/axom/primal/geometry/Vector.hpp index 08b000ab67..9c00380b7f 100644 --- a/src/axom/primal/geometry/Vector.hpp +++ b/src/axom/primal/geometry/Vector.hpp @@ -133,24 +133,6 @@ AXOM_HOST_DEVICE Vector operator*(const T scalar, template Vector operator/(const Vector& vec, const T scalar); -/*! - * \brief Element-wise < operator. - * \param [in] vec vector instance - * \param [in] scalar user-supplied scalar. - * \return Whether all vector elements are < a scalar. - */ -template -bool operator<(const Vector& vec, const T scalar); - -/*! - * \brief Element-wise >= operator. - * \param [in] vec vector instance - * \param [in] scalar user-supplied scalar. - * \return Whether all vector elements are >= a scalar. - */ -template -bool operator>=(const Vector& vec, const T scalar); - /*! * \brief Overloaded output operator for vectors * \param [in] os C++ output stream @@ -716,30 +698,6 @@ std::ostream& operator<<(std::ostream& os, const Vector& vec) return os; } -//------------------------------------------------------------------------------ -template -inline bool operator<(const Vector& vec, const T scalar) -{ - bool result(true); - for(int d = 0; d < NDIMS; ++d) - { - result &= vec[d] < scalar; - } - return result; -} - -//------------------------------------------------------------------------------ -template -inline bool operator>=(const Vector& vec, const T scalar) -{ - bool result(true); - for(int d = 0; d < NDIMS; ++d) - { - result &= vec[d] >= scalar; - } - return result; -} - //------------------------------------------------------------------------------ template inline Vector Vector::make_vector(const T& x, diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index 760fcc9304..fb0cd32e9a 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -283,7 +283,7 @@ class ArrayIndexer return m_strides; } - //!@brief Whether a vector is a permutation vector + //!@brief Whether a StackArray represents a permutation. bool isPermutation(const axom::StackArray& v) { // v is a permutation if all its values are unique and in [0, DIM). diff --git a/src/axom/quest/MeshViewUtil.hpp b/src/axom/quest/MeshViewUtil.hpp index 9b64fab789..af6f02ebf8 100644 --- a/src/axom/quest/MeshViewUtil.hpp +++ b/src/axom/quest/MeshViewUtil.hpp @@ -381,8 +381,8 @@ class MeshViewUtil return axom::quest::internal::product(m_nodeShape); } - //! @brief Return the real (ghost-free) extents of mesh data. - const MdIndices& getRealExtents(const std::string& association) + //! @brief Return the real (ghost-free) shape of mesh data. + const MdIndices& getRealShape(const std::string& association) { if(association == "vertex") { @@ -676,11 +676,12 @@ class MeshViewUtil const MdIndices& strides, const MdIndices& offsets) { - SLIC_ERROR_IF(m_dom == nullptr, - axom::fmt::format( - "Cannot create field {}." - " MeshViewUtil was not constructed with a mutable domain.", - fieldName)); + SLIC_ERROR_IF( + m_dom == nullptr, + axom::fmt::format( + "Cannot create field {}." + " MeshViewUtil was not constructed with a non-const domain.", + fieldName)); SLIC_ERROR_IF( m_dom->has_path("fields/" + fieldName), axom::fmt::format("Cannot create field {}. It already exists.", fieldName)); @@ -690,7 +691,7 @@ class MeshViewUtil axom::fmt::format("MeshViewUtil doesn't support association '{}' yet.", association)); - const auto& realShape = getRealExtents(association); + const auto& realShape = getRealShape(association); MdIndices loPads, hiPads, paddedShape, strideOrder; axom::quest::internal::stridesAndOffsetsToShapes(realShape, offsets, @@ -758,11 +759,12 @@ class MeshViewUtil const MdIndices& hiPads, const MdIndices& strideOrder) { - SLIC_ERROR_IF(m_dom == nullptr, - axom::fmt::format( - "Cannot create field {}." - " MeshViewUtil was not constructed with a mutable domain.", - fieldName)); + SLIC_ERROR_IF( + m_dom == nullptr, + axom::fmt::format( + "Cannot create field {}." + " MeshViewUtil was not constructed with a non-const domain.", + fieldName)); SLIC_ERROR_IF( m_dom->has_path("fields/" + fieldName), axom::fmt::format("Cannot create field {}. It already exists.", fieldName)); @@ -774,7 +776,7 @@ class MeshViewUtil axom::StackArray offsets; axom::StackArray strides; axom::IndexType valuesCount; - const auto& realShape = getRealExtents(association); + const auto& realShape = getRealShape(association); axom::quest::internal::shapesToStridesAndOffsets(realShape, loPads, hiPads, diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 6e90497daa..84eb4ce167 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -72,16 +72,17 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase , m_caseIds() , m_caseIdsIndexer() , m_caseIdsFlat(caseIdsFlat) - , m_crossingCases(0, 0, m_allocatorID) , m_crossingFlags(crossingFlags) , m_scannedFlags(scannedFlags) + , m_facetIncrs(facetIncrs) + , m_crossingCases(0, 0, m_allocatorID) , m_crossingParentIds(0, 0, m_allocatorID) - , m_facetIncrs(facetIncrs) // (0, 0, m_allocatorID) , m_firstFacetIds(0, 0, m_allocatorID) { SLIC_ASSERT(caseIdsFlat.getAllocatorID() == allocatorID); SLIC_ASSERT(crossingFlags.getAllocatorID() == allocatorID); SLIC_ASSERT(scannedFlags.getAllocatorID() == allocatorID); + SLIC_ASSERT(facetIncrs.getAllocatorID() == allocatorID); } /*! @@ -123,13 +124,11 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase constexpr MarchingCubesDataParallelism autoPolicy = std::is_same::value ? MarchingCubesDataParallelism::hybridParallel - : #if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) - std::is_same::value + : std::is_same::value ? MarchingCubesDataParallelism::hybridParallel - : #endif - MarchingCubesDataParallelism::fullParallel; + : MarchingCubesDataParallelism::fullParallel; m_dataParallelism = dataPar; @@ -951,15 +950,15 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase //!@brief Crossing case for each computational mesh cell. axom::Array& m_caseIdsFlat; - //!@brief Case ids for found crossings. - axom::Array m_crossingCases; - //!@brief Whether a parent cell crosses the contour. axom::Array& m_crossingFlags; //!@brief Prefix sum of m_crossingFlags axom::Array& m_scannedFlags; + //!@brief Number of surface mesh facets added by each crossing. + axom::Array& m_facetIncrs; + //!@brief Number of parent cells crossing the contour surface. axom::IndexType m_crossingCount = 0; @@ -967,12 +966,12 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase axom::IndexType m_facetCount = 0; axom::IndexType getContourCellCount() const override { return m_facetCount; } + //!@brief Case ids for found crossings. + axom::Array m_crossingCases; + //!@brief Parent cell id (flat index into m_caseIds) for each crossing. axom::Array m_crossingParentIds; - //!@brief Number of surface mesh facets added by each crossing. - axom::Array& m_facetIncrs; - //!@brief First index of facets for each crossing. axom::Array m_firstFacetIds; diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index 24045d2325..844b8f2507 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -41,26 +41,21 @@ template class MarchingCubesImpl; /*! - * \@brief Class implementing marching cubes algorithm for a single - * domain. - * - * \sa MarchingCubes - */ + \@brief Class implementing marching cubes algorithm for a single + domain. + + This class is an internal detail for multi-domain implementation + MarchinCubes class, and should not be used outside it. + + \sa MarchingCubes +*/ class MarchingCubesSingleDomain { public: using RuntimePolicy = axom::runtime_policy::Policy; /*! - * \brief Constructor for applying algorithm in a single domain. - * See MarchingCubes for the multi-domain implementation. - * - * \param [in] runtimePolicy A value from RuntimePolicy. - * The simplest policy is RuntimePolicy::seq, which specifies - * running sequentially on the CPU. - * \param [in] allocatorID Data allocator ID. Choose something compatible - * with \c runtimePolicy. See \c esecution_space. - * \param [in] dataPar Choice of data-parallel implementation. - */ + \brief Constructor for applying algorithm in a single domain. + */ MarchingCubesSingleDomain(MarchingCubes &mc); ~MarchingCubesSingleDomain() { } @@ -116,10 +111,6 @@ class MarchingCubesSingleDomain // Methods trivially delegated to implementation. void markCrossings() { m_impl->markCrossings(); } void scanCrossings() { m_impl->scanCrossings(); } - axom::IndexType getContourCellCount() - { - return m_impl->getContourCellCount(); - } void computeFacets() { m_impl->computeFacets(); } /*! @@ -131,11 +122,7 @@ class MarchingCubesSingleDomain //!@brief Get number of cells in the generated contour mesh. axom::IndexType getContourCellCount() const { - SLIC_ASSERT_MSG( - m_impl, - "There is no contour mesh until you call computeIsocontour()"); - axom::IndexType cellCount = m_impl->getContourCellCount(); - return cellCount; + return m_impl->getContourCellCount(); } //!@brief Get number of nodes in the generated contour mesh. diff --git a/src/axom/quest/examples/quest_marching_cubes_example.cpp b/src/axom/quest/examples/quest_marching_cubes_example.cpp index 09aaf29a2c..fb847068c9 100644 --- a/src/axom/quest/examples/quest_marching_cubes_example.cpp +++ b/src/axom/quest/examples/quest_marching_cubes_example.cpp @@ -1175,7 +1175,7 @@ struct ContourTestBase domainIdToContiguousId[domainId] = iDomain; } - // Indexers to transltate between flat and multidim indices. + // Indexers to translate between flat and multidim indices. axom::Array> indexers(domainCount); for(int d = 0; d < domainCount; ++d) { @@ -1187,6 +1187,16 @@ struct ContourTestBase .slowestDirs()); } + auto elementGreaterThan = [](const axom::primal::Vector& a, + double b) { + bool result(true); + for(int d = 0; d < DIM; ++d) + { + result &= a[d] < b; + } + return result; + }; + for(axom::IndexType iStrat = 0; iStrat < m_testStrategies.size(); ++iStrat) { auto contourCellBegin = m_strategyFacetPrefixSum[iStrat]; @@ -1221,7 +1231,7 @@ struct ContourTestBase big.expand(tol); axom::primal::BoundingBox small(parentCellBox); auto range = parentCellBox.range(); - bool checkSmall = range >= tol; + bool checkSmall = elementGreaterThan(range, tol); if(checkSmall) { small.expand(-tol); @@ -1347,7 +1357,7 @@ struct ContourTestBase axom::StackArray domLengths; computationalMesh.domainLengths(domId, domLengths); - assert(domLengths == domainView.getRealExtents("element")); + assert(domLengths == domainView.getRealShape("element")); const axom::IndexType parentCellCount = domainView.getCellCount(); // axom::Array hasContour(parentCellCount, parentCellCount); diff --git a/src/axom/quest/tests/quest_mesh_view_util.cpp b/src/axom/quest/tests/quest_mesh_view_util.cpp index 24c46d05c1..9769e0e5fe 100644 --- a/src/axom/quest/tests/quest_mesh_view_util.cpp +++ b/src/axom/quest/tests/quest_mesh_view_util.cpp @@ -427,22 +427,21 @@ int testByConduitExample(const IndexCoords& domainShape, elemShape)); } - if(!isEqual(mview.getRealExtents("element"), elemShape)) + if(!isEqual(mview.getRealShape("element"), elemShape)) { ++errCount; - SLIC_INFO_IF( - params.isVerbose(), - axom::fmt::format("Mismatched real element extents: {} vs {}", - mview.getRealExtents("element"), - elemShape)); + SLIC_INFO_IF(params.isVerbose(), + axom::fmt::format("Mismatched real element shape: {} vs {}", + mview.getRealShape("element"), + elemShape)); } - if(!isEqual(mview.getRealExtents("vertex"), vertShape)) + if(!isEqual(mview.getRealShape("vertex"), vertShape)) { ++errCount; SLIC_INFO_IF(params.isVerbose(), - axom::fmt::format("Mismatched real vertex extents: {} vs {}", - mview.getRealExtents("vertex"), + axom::fmt::format("Mismatched real vertex shape: {} vs {}", + mview.getRealShape("vertex"), vertShape)); } From 9cbdbb626d2dbda36fd2b9a8d5061666601b2f56 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 7 Mar 2024 12:00:44 -0800 Subject: [PATCH 56/61] Make ArrayStrideOrder an enum (and convert to int as needed for bit operations). --- src/axom/quest/ArrayIndexer.hpp | 39 +++++++++++--------- src/axom/quest/detail/MarchingCubesImpl.hpp | 4 +- src/axom/quest/tests/quest_array_indexer.cpp | 24 ++++++------ 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index fb0cd32e9a..95474c306e 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -22,12 +22,12 @@ namespace axom or some arbitrarily permuted order. Row and column major ordering are the same thing if the array is 1D. */ -struct ArrayStrideOrder +enum class ArrayStrideOrder : int { - static constexpr int ARBITRARY = 0; // Neither row nor column - static constexpr int ROW = 1; // Row-major - static constexpr int COLUMN = 2; // Column-major - static constexpr int BOTH = ROW | COLUMN; // 1D arrays are both + ARBITRARY = 0, // Neither row nor column + ROW = 1, // Row-major + COLUMN = 2, // Column-major + BOTH = ROW | COLUMN // 1D arrays are both }; /*! @@ -52,7 +52,7 @@ class ArrayIndexer direction. */ ArrayIndexer(const axom::StackArray& shape, - int arrayStrideOrder, + axom::ArrayStrideOrder arrayStrideOrder, int fastestStrideLength = 1) { initializeShape(shape, arrayStrideOrder, fastestStrideLength); @@ -117,7 +117,7 @@ class ArrayIndexer direction. */ inline AXOM_HOST_DEVICE void initializeShape(const axom::StackArray& shape, - int arrayStrideOrder, + ArrayStrideOrder arrayStrideOrder, int fastestStrideLength = 1) { SLIC_ASSERT(arrayStrideOrder == ArrayStrideOrder::COLUMN || @@ -227,7 +227,7 @@ class ArrayIndexer */ inline AXOM_HOST_DEVICE void initializeStrides( const axom::StackArray& strides, - int orderPref) + ArrayStrideOrder orderPref) { SLIC_ASSERT(orderPref == axom::ArrayStrideOrder::COLUMN || orderPref == axom::ArrayStrideOrder::ROW); @@ -296,12 +296,12 @@ class ArrayIndexer { if(v[d] < 0 || v[d] >= DIM) { - return false; - } // Out of range. + return false; // Out of range. + } if(found[v[d]] == true) { - return false; - } // Repeated indices + return false; // Repeated index. + } found[v[d]] = true; } return true; @@ -313,15 +313,20 @@ class ArrayIndexer @return Value from ArrayStrideOrder, indicating column order, row order, both column and row (1D only) or arbitrary order. */ - inline AXOM_HOST_DEVICE int getStrideOrder() const + inline AXOM_HOST_DEVICE ArrayStrideOrder getStrideOrder() const { - int ord = ArrayStrideOrder::BOTH; + int ord = int(ArrayStrideOrder::BOTH); for(int d = 0; d < DIM - 1; ++d) { - ord &= m_slowestDirs[d] < m_slowestDirs[d + 1] ? ArrayStrideOrder::COLUMN - : ArrayStrideOrder::ROW; + ord &= m_slowestDirs[d] < m_slowestDirs[d + 1] + ? int(ArrayStrideOrder::COLUMN) + : int(ArrayStrideOrder::ROW); } - return ord; + static ArrayStrideOrder s_intToOrder[4] = {ArrayStrideOrder::ARBITRARY, + ArrayStrideOrder::ROW, + ArrayStrideOrder::COLUMN, + ArrayStrideOrder::BOTH}; + return s_intToOrder[ord]; } //!@brief Convert multidimensional index to flat index. diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 84eb4ce167..ec737be931 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -192,7 +192,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase RAJA::RangeSegment iRange(0, m_bShape[0]); using EXEC_POL = typename axom::mint::internal::structured_exec::loop2d_policy; - if(order & axom::ArrayStrideOrder::ROW) + if(int(order) & int(axom::ArrayStrideOrder::ROW)) { RAJA::kernel( RAJA::make_tuple(iRange, jRange), @@ -246,7 +246,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase RAJA::RangeSegment iRange(0, m_bShape[0]); using EXEC_POL = typename axom::mint::internal::structured_exec::loop3d_policy; - if(order & axom::ArrayStrideOrder::ROW) + if(int(order) & int(axom::ArrayStrideOrder::ROW)) { RAJA::kernel( RAJA::make_tuple(iRange, jRange, kRange), diff --git a/src/axom/quest/tests/quest_array_indexer.cpp b/src/axom/quest/tests/quest_array_indexer.cpp index 70d6b601b2..d4e1a72a40 100644 --- a/src/axom/quest/tests/quest_array_indexer.cpp +++ b/src/axom/quest/tests/quest_array_indexer.cpp @@ -22,7 +22,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::ArrayIndexer colIndexer( lengths, axom::ArrayStrideOrder::COLUMN); - EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); + EXPECT_EQ(colIndexer.getStrideOrder(), axom::ArrayStrideOrder::BOTH); axom::StackArray colSlowestDirs {0}; axom::StackArray colStrides {1}; for(int d = 0; d < DIM; ++d) @@ -37,7 +37,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::ArrayIndexer rowIndexer( lengths, axom::ArrayStrideOrder::ROW); - EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); + EXPECT_EQ(rowIndexer.getStrideOrder(), axom::ArrayStrideOrder::BOTH); axom::StackArray rowSlowestDirs {0}; axom::StackArray rowStrides {1}; for(int d = 0; d < DIM; ++d) @@ -57,7 +57,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::ArrayIndexer colIndexer( lengths, axom::ArrayStrideOrder::COLUMN); - EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(colIndexer.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); axom::StackArray colSlowestDirs {0, 1}; axom::StackArray colStrides {2, 1}; for(int d = 0; d < DIM; ++d) @@ -69,7 +69,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::ArrayIndexer rowIndexer( lengths, axom::ArrayStrideOrder::ROW); - EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(rowIndexer.getStrideOrder(), axom::ArrayStrideOrder::ROW); axom::StackArray rowSlowestDirs {1, 0}; axom::StackArray rowStrides {1, 3}; for(int d = 0; d < DIM; ++d) @@ -86,7 +86,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::ArrayIndexer colIndexer( lengths, axom::ArrayStrideOrder::COLUMN); - EXPECT_EQ(colIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(colIndexer.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); axom::StackArray colSlowestDirs {0, 1, 2}; axom::StackArray colStrides {6, 2, 1}; for(int d = 0; d < DIM; ++d) @@ -98,7 +98,7 @@ TEST(quest_array_indexer, quest_strides_and_permutations) axom::ArrayIndexer rowIndexer( lengths, axom::ArrayStrideOrder::ROW); - EXPECT_EQ(rowIndexer.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(rowIndexer.getStrideOrder(), axom::ArrayStrideOrder::ROW); axom::StackArray rowSlowestDirs {2, 1, 0}; axom::StackArray rowStrides {1, 5, 15}; for(int d = 0; d < DIM; ++d) @@ -118,7 +118,7 @@ TEST(quest_array_indexer, quest_col_major_offset) axom::StackArray lengths {3}; axom::ArrayIndexer ai(lengths, axom::ArrayStrideOrder::COLUMN); - EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::BOTH); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -134,7 +134,7 @@ TEST(quest_array_indexer, quest_col_major_offset) axom::StackArray lengths {3, 2}; axom::ArrayIndexer ai(lengths, axom::ArrayStrideOrder::COLUMN); - EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -152,7 +152,7 @@ TEST(quest_array_indexer, quest_col_major_offset) axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer ai(lengths, axom::ArrayStrideOrder::COLUMN); - EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::COLUMN)); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -179,7 +179,7 @@ TEST(quest_array_indexer, quest_row_major_offset) axom::StackArray lengths {3}; axom::ArrayIndexer ai(lengths, axom::ArrayStrideOrder::ROW); - EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::BOTH)); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::BOTH); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -195,7 +195,7 @@ TEST(quest_array_indexer, quest_row_major_offset) axom::StackArray lengths {3, 2}; axom::ArrayIndexer ai(lengths, axom::ArrayStrideOrder::ROW); - EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::ROW); axom::IndexType offset = 0; for(int j = 0; j < lengths[1]; ++j) { @@ -214,7 +214,7 @@ TEST(quest_array_indexer, quest_row_major_offset) axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer ai(lengths, axom::ArrayStrideOrder::ROW); - EXPECT_EQ(ai.getStrideOrder(), int(axom::ArrayStrideOrder::ROW)); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::ROW); axom::IndexType offset = 0; for(int k = 0; k < lengths[2]; ++k) { From f46cf8172954ae119a4d9c6f477fbb6376252ff8 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 7 Mar 2024 13:06:17 -0800 Subject: [PATCH 57/61] Fix recent change breaking non-RAJA build. --- src/axom/quest/detail/MarchingCubesImpl.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index ec737be931..24ea18c872 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -209,7 +209,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase }); } #else - if(order & axom::ArrayStrideOrder::ROW) + if(int(order) & int(axom::ArrayStrideOrder::ROW)) { for(int j = 0; j < m_bShape[1]; ++j) { @@ -263,7 +263,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase }); } #else - if(order & axom::ArrayStrideOrder::ROW) + if(int(order) & int(axom::ArrayStrideOrder::ROW)) { for(int k = 0; k < m_bShape[2]; ++k) { From eba4cfb1bc9f4944de3201a55fbc3a81c38118b1 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 8 Mar 2024 10:04:56 -0800 Subject: [PATCH 58/61] Fix a mix-up of row and column ordering. The errors canceled out, so no diff in performance. --- src/axom/quest/ArrayIndexer.hpp | 6 +-- src/axom/quest/detail/MarchingCubesImpl.hpp | 8 ++-- src/axom/quest/tests/quest_array_indexer.cpp | 48 ++++++++++---------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/axom/quest/ArrayIndexer.hpp b/src/axom/quest/ArrayIndexer.hpp index 95474c306e..fbd53e7681 100644 --- a/src/axom/quest/ArrayIndexer.hpp +++ b/src/axom/quest/ArrayIndexer.hpp @@ -123,7 +123,7 @@ class ArrayIndexer SLIC_ASSERT(arrayStrideOrder == ArrayStrideOrder::COLUMN || arrayStrideOrder == ArrayStrideOrder::ROW || (DIM == 1 && arrayStrideOrder == ArrayStrideOrder::BOTH)); - if(arrayStrideOrder == ArrayStrideOrder::ROW) + if(arrayStrideOrder == ArrayStrideOrder::COLUMN) { for(int d = 0; d < DIM; ++d) { @@ -319,8 +319,8 @@ class ArrayIndexer for(int d = 0; d < DIM - 1; ++d) { ord &= m_slowestDirs[d] < m_slowestDirs[d + 1] - ? int(ArrayStrideOrder::COLUMN) - : int(ArrayStrideOrder::ROW); + ? int(ArrayStrideOrder::ROW) + : int(ArrayStrideOrder::COLUMN); } static ArrayStrideOrder s_intToOrder[4] = {ArrayStrideOrder::ARBITRARY, ArrayStrideOrder::ROW, diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 24ea18c872..5582b3efec 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -192,7 +192,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase RAJA::RangeSegment iRange(0, m_bShape[0]); using EXEC_POL = typename axom::mint::internal::structured_exec::loop2d_policy; - if(int(order) & int(axom::ArrayStrideOrder::ROW)) + if(int(order) & int(axom::ArrayStrideOrder::COLUMN)) { RAJA::kernel( RAJA::make_tuple(iRange, jRange), @@ -209,7 +209,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase }); } #else - if(int(order) & int(axom::ArrayStrideOrder::ROW)) + if(int(order) & int(axom::ArrayStrideOrder::COLUMN)) { for(int j = 0; j < m_bShape[1]; ++j) { @@ -246,7 +246,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase RAJA::RangeSegment iRange(0, m_bShape[0]); using EXEC_POL = typename axom::mint::internal::structured_exec::loop3d_policy; - if(int(order) & int(axom::ArrayStrideOrder::ROW)) + if(int(order) & int(axom::ArrayStrideOrder::COLUMN)) { RAJA::kernel( RAJA::make_tuple(iRange, jRange, kRange), @@ -263,7 +263,7 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase }); } #else - if(int(order) & int(axom::ArrayStrideOrder::ROW)) + if(int(order) & int(axom::ArrayStrideOrder::COLUMN)) { for(int k = 0; k < m_bShape[2]; ++k) { diff --git a/src/axom/quest/tests/quest_array_indexer.cpp b/src/axom/quest/tests/quest_array_indexer.cpp index d4e1a72a40..a84430b68d 100644 --- a/src/axom/quest/tests/quest_array_indexer.cpp +++ b/src/axom/quest/tests/quest_array_indexer.cpp @@ -58,8 +58,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) lengths, axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(colIndexer.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); - axom::StackArray colSlowestDirs {0, 1}; - axom::StackArray colStrides {2, 1}; + axom::StackArray colSlowestDirs {1, 0}; + axom::StackArray colStrides {1, 3}; for(int d = 0; d < DIM; ++d) { EXPECT_EQ(colIndexer.slowestDirs()[d], colSlowestDirs[d]); @@ -70,8 +70,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) lengths, axom::ArrayStrideOrder::ROW); EXPECT_EQ(rowIndexer.getStrideOrder(), axom::ArrayStrideOrder::ROW); - axom::StackArray rowSlowestDirs {1, 0}; - axom::StackArray rowStrides {1, 3}; + axom::StackArray rowSlowestDirs {0, 1}; + axom::StackArray rowStrides {2, 1}; for(int d = 0; d < DIM; ++d) { EXPECT_EQ(rowIndexer.slowestDirs()[d], rowSlowestDirs[d]); @@ -87,8 +87,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) lengths, axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(colIndexer.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); - axom::StackArray colSlowestDirs {0, 1, 2}; - axom::StackArray colStrides {6, 2, 1}; + axom::StackArray colSlowestDirs {2, 1, 0}; + axom::StackArray colStrides {1, 5, 15}; for(int d = 0; d < DIM; ++d) { EXPECT_EQ(colIndexer.slowestDirs()[d], colSlowestDirs[d]); @@ -99,8 +99,8 @@ TEST(quest_array_indexer, quest_strides_and_permutations) lengths, axom::ArrayStrideOrder::ROW); EXPECT_EQ(rowIndexer.getStrideOrder(), axom::ArrayStrideOrder::ROW); - axom::StackArray rowSlowestDirs {2, 1, 0}; - axom::StackArray rowStrides {1, 5, 15}; + axom::StackArray rowSlowestDirs {0, 1, 2}; + axom::StackArray rowStrides {6, 2, 1}; for(int d = 0; d < DIM; ++d) { EXPECT_EQ(rowIndexer.slowestDirs()[d], rowSlowestDirs[d]); @@ -109,15 +109,15 @@ TEST(quest_array_indexer, quest_strides_and_permutations) } } -// Test column-major offsets. -TEST(quest_array_indexer, quest_col_major_offset) +// Test row-major offsets. +TEST(quest_array_indexer, quest_row_major_offset) { { // 1D constexpr int DIM = 1; axom::StackArray lengths {3}; axom::ArrayIndexer ai(lengths, - axom::ArrayStrideOrder::COLUMN); + axom::ArrayStrideOrder::ROW); EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::BOTH); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) @@ -133,8 +133,8 @@ TEST(quest_array_indexer, quest_col_major_offset) constexpr int DIM = 2; axom::StackArray lengths {3, 2}; axom::ArrayIndexer ai(lengths, - axom::ArrayStrideOrder::COLUMN); - EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); + axom::ArrayStrideOrder::ROW); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::ROW); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -151,8 +151,8 @@ TEST(quest_array_indexer, quest_col_major_offset) // 3D axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer ai(lengths, - axom::ArrayStrideOrder::COLUMN); - EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); + axom::ArrayStrideOrder::ROW); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::ROW); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) { @@ -170,15 +170,15 @@ TEST(quest_array_indexer, quest_col_major_offset) } } -// Test row-major offsets. -TEST(quest_array_indexer, quest_row_major_offset) +// Test column-major offsets. +TEST(quest_array_indexer, quest_col_major_offset) { { // 1D constexpr int DIM = 1; axom::StackArray lengths {3}; axom::ArrayIndexer ai(lengths, - axom::ArrayStrideOrder::ROW); + axom::ArrayStrideOrder::COLUMN); EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::BOTH); axom::IndexType offset = 0; for(int i = 0; i < lengths[0]; ++i) @@ -194,8 +194,8 @@ TEST(quest_array_indexer, quest_row_major_offset) constexpr int DIM = 2; axom::StackArray lengths {3, 2}; axom::ArrayIndexer ai(lengths, - axom::ArrayStrideOrder::ROW); - EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::ROW); + axom::ArrayStrideOrder::COLUMN); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); axom::IndexType offset = 0; for(int j = 0; j < lengths[1]; ++j) { @@ -213,8 +213,8 @@ TEST(quest_array_indexer, quest_row_major_offset) constexpr int DIM = 3; axom::StackArray lengths {5, 3, 2}; axom::ArrayIndexer ai(lengths, - axom::ArrayStrideOrder::ROW); - EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::ROW); + axom::ArrayStrideOrder::COLUMN); + EXPECT_EQ(ai.getStrideOrder(), axom::ArrayStrideOrder::COLUMN); axom::IndexType offset = 0; for(int k = 0; k < lengths[2]; ++k) { @@ -403,7 +403,7 @@ TEST(quest_array_indexer, quest_array_match) axom::StackArray lengths {3, 2}; axom::Array a(lengths); axom::ArrayIndexer ai(lengths, - axom::ArrayStrideOrder::COLUMN); + axom::ArrayStrideOrder::ROW); for(axom::IndexType i = 0; i < lengths[0]; ++i) { for(axom::IndexType j = 0; j < lengths[1]; ++j) @@ -418,7 +418,7 @@ TEST(quest_array_indexer, quest_array_match) axom::StackArray lengths {5, 3, 2}; axom::Array a(lengths); axom::ArrayIndexer ai(lengths, - axom::ArrayStrideOrder::COLUMN); + axom::ArrayStrideOrder::ROW); for(axom::IndexType i = 0; i < lengths[0]; ++i) { for(axom::IndexType j = 0; j < lengths[1]; ++j) From 6e6dd1dce2e0e2011efa36914081cc0e44163e08 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 11 Mar 2024 14:38:05 -0700 Subject: [PATCH 59/61] Slight dox comment clarification. --- src/axom/quest/MarchingCubes.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index bd8b459aea..bc4c2db0ab 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -162,9 +162,9 @@ class MarchingCubes */ void computeIsocontour(double contourVal = 0.0); - //!@brief Get number of cells in the generated contour mesh. + //!@brief Get number of cells (facets) in the generated contour mesh. axom::IndexType getContourCellCount() const { return m_facetCount; } - //!@brief Get number of cells in the generated contour mesh. + //!@brief Get number of cells (facets) in the generated contour mesh. axom::IndexType getContourFacetCount() const { return m_facetCount; } //!@brief Get number of nodes in the generated contour mesh. From aff1ba9da4d94a7e80beb4ebd0efa562780f595b Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 12 Mar 2024 09:32:05 -0700 Subject: [PATCH 60/61] Silence warning about shadow variable. --- src/axom/quest/MeshViewUtil.hpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/axom/quest/MeshViewUtil.hpp b/src/axom/quest/MeshViewUtil.hpp index af6f02ebf8..7c3098c6e5 100644 --- a/src/axom/quest/MeshViewUtil.hpp +++ b/src/axom/quest/MeshViewUtil.hpp @@ -541,14 +541,7 @@ class MeshViewUtil } } - MdIndices offsets = conduitIndicesToStackArray(fieldNode, "offsets"); - if(!fieldNode.has_child("offsets")) - { - for(int d = 0; d < DIM; ++d) - { - offsets[d] = 0; - } - } + MdIndices offsets = conduitIndicesToStackArray(fieldNode, "offsets", 0); MdIndices loPads, hiPads, paddedShape, strideOrder; axom::quest::internal::stridesAndOffsetsToShapes(realShape, @@ -565,7 +558,6 @@ class MeshViewUtil if(withGhosts == false) { - MdIndices offsets = conduitIndicesToStackArray(fieldNode, "offsets", 0); auto rval1 = rval; rval = rval1.subspan(offsets, realShape); } From 663b90472e80dbab54115aaf8868044a42346452 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 14 Mar 2024 18:39:32 -0700 Subject: [PATCH 61/61] Documenting comments and diagnostic output changes from code review. --- src/axom/quest/MarchingCubes.cpp | 2 -- src/axom/quest/MarchingCubes.hpp | 32 +++++++++---------- src/axom/quest/detail/MarchingCubesImpl.hpp | 9 ++---- .../detail/MarchingCubesSingleDomain.cpp | 6 ++-- .../detail/MarchingCubesSingleDomain.hpp | 2 +- 5 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/axom/quest/MarchingCubes.cpp b/src/axom/quest/MarchingCubes.cpp index 6aa7e49815..50fec43b75 100644 --- a/src/axom/quest/MarchingCubes.cpp +++ b/src/axom/quest/MarchingCubes.cpp @@ -191,8 +191,6 @@ void MarchingCubes::populateContourMesh( // Reserve space once for all local domains. const axom::IndexType contourCellCount = getContourCellCount(); const axom::IndexType contourNodeCount = getContourNodeCount(); - // Temporarily disable reservation due to unknown bug. - // See https://github.com/LLNL/axom/pull/1271 mesh.reserveCells(contourCellCount); mesh.reserveNodes(contourNodeCount); diff --git a/src/axom/quest/MarchingCubes.hpp b/src/axom/quest/MarchingCubes.hpp index bc4c2db0ab..bc053915a8 100644 --- a/src/axom/quest/MarchingCubes.hpp +++ b/src/axom/quest/MarchingCubes.hpp @@ -36,8 +36,6 @@ namespace detail { namespace marching_cubes { -template -class MarchingCubesImpl; // TODO: Delete this. class MarchingCubesSingleDomain; } // namespace marching_cubes } // namespace detail @@ -101,6 +99,10 @@ enum class MarchingCubesDataParallelism * specify ids for the domains. If "state/domain_id" exists in the * domains, it is used as the domain id. Otherwise, the domain's * iteration index within the multidomain mesh is used. + * + * Output arrays use the allocator id specified in the constructor. + * However, the output mint mesh currently uses host data. The data + * output interfaces are interim and subject to change) */ class MarchingCubes { @@ -129,18 +131,13 @@ class MarchingCubes \param [in] maskField Cell-based std::int32_t mask field. If provided, cells where this field evaluates to false are skipped. - Array data in \a dom must be accessible in the \a runtimePolicy + Array data in \a bpMesh must be accessible in the \a runtimePolicy environment specified in the constructor. It's an error if not, e.g., using CPU memory with a GPU policy. Some metadata from \a bpMesh may be cached. Any change to it after setMesh() leads to undefined behavior. - Blueprint allows users to specify ids for the domains. If - "state/domain_id" exists in the domains, it is used as the domain - id. Otherwise, the domain's interation index within the - multidomain mesh is used. - @see clearMesh() */ void setMesh(const conduit::Node &bpMesh, @@ -171,7 +168,7 @@ class MarchingCubes axom::IndexType getContourNodeCount() const; //@{ - //!@name Output methods (interim interfaces, subject to change) + //!@name Access to output contour mesh /*! @brief Put generated contour in a mint::UnstructuredMesh. @param mesh Output contour mesh @@ -200,8 +197,6 @@ class MarchingCubes The array shape is (getContourCellCount(), ), where the second index is index of the facet corner. - - Memory space of data corresponds to allocator set in the constructor. */ axom::ArrayView getContourFacetCorners() const { @@ -213,8 +208,6 @@ class MarchingCubes The array shape is (getContourNodeCount(), ), where the second index is the spatial index. - - Memory space of data corresponds to allocator set in the constructor. */ axom::ArrayView getContourNodeCoords() const { @@ -227,8 +220,6 @@ class MarchingCubes The buffer size is getContourCellCount(). The parent ID is the column-major ordered flat index of the cell in the parent domain (see ArrayIndexer), not counting ghost cells. - - Memory space of data corresponds to allocator set in the constructor. */ axom::ArrayView getContourFacetParents() const { @@ -250,9 +241,18 @@ class MarchingCubes /*! @brief Give caller posession of the contour data. - This efficiently turns the generated contour data to the caller, + This efficiently gives the generated contour data to the caller, to stay in scope after the MarchingCubes object is deleted. + @param [i] facetNodeIds Node ids for the node at the corners of + each facet. @see getContourFacetCorners(). + @param [i] facetNodeCoords Coordinates of each facet node. + @see getContourNodeCoords(). + @param [i] facetParentIds Parent cell id of each facet. + @see getContourFacetParents(). + @param [i] facetDomainIds Domain id of each facet. + @see getContourFacetDomainIds(). + @pre computeIsocontour() must have been called. @post outputs can no longer be accessed from object, as though clearOutput() has been called. diff --git a/src/axom/quest/detail/MarchingCubesImpl.hpp b/src/axom/quest/detail/MarchingCubesImpl.hpp index 5582b3efec..eb46623177 100644 --- a/src/axom/quest/detail/MarchingCubesImpl.hpp +++ b/src/axom/quest/detail/MarchingCubesImpl.hpp @@ -32,14 +32,12 @@ namespace marching_cubes /*! @brief Computations for MarchingCubesSingleDomain - Spatial dimension templating is here, to keep out of higher level - classes MarchCubes and MarchingCubesSingleDomain. + Spatial dimension and execution space are here as template + parameters, to keep out of higher level classes MarchingCubes and + MarchingCubesSingleDomain. ExecSpace is the general execution space, like axom::SEQ_EXEC and axom::CUDA_EXEC<256>. - - See MarchingCubesImpl for the difference between that class and - MarchingCubesImpl. */ template class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase @@ -186,7 +184,6 @@ class MarchingCubesImpl : public MarchingCubesSingleDomain::ImplBase MarkCrossings_Util mcu(m_caseIds, m_fcnView, m_maskView, m_contourVal); auto order = m_caseIdsIndexer.getStrideOrder(); - // order ^= axom::ArrayStrideOrder::BOTH; // Pick wrong ordering to test behavior. #if defined(AXOM_USE_RAJA) RAJA::RangeSegment jRange(0, m_bShape[1]); RAJA::RangeSegment iRange(0, m_bShape[0]); diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp index ae711c8462..b450a3774c 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.cpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.cpp @@ -46,9 +46,9 @@ void MarchingCubesSingleDomain::setDomain(const conduit::Node& dom, { m_topologyName = topologyName; - SLIC_ASSERT_MSG( - !conduit::blueprint::mesh::is_multi_domain(dom), - "MarchingCubesSingleDomain is single-domain only. Try MarchingCubes."); + SLIC_ASSERT_MSG(!conduit::blueprint::mesh::is_multi_domain(dom), + "Internal error. Attempt to set a multi-domain mesh in " + "MarchingCubesSingleDomain."); SLIC_ASSERT( dom.fetch_existing("topologies/" + m_topologyName + "/type").as_string() == "structured"); diff --git a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp index 844b8f2507..007e3ce7dd 100644 --- a/src/axom/quest/detail/MarchingCubesSingleDomain.hpp +++ b/src/axom/quest/detail/MarchingCubesSingleDomain.hpp @@ -203,7 +203,7 @@ class MarchingCubesSingleDomain ImplBase &getImpl() { return *m_impl; } private: - //!@brief Multi-somain implementation this object is under. + //!@brief Multi-domain implementation this object is under. MarchingCubes &m_mc; RuntimePolicy m_runtimePolicy;