diff --git a/.gitlab/build_lassen.yml b/.gitlab/build_lassen.yml index 21f870faa1..94f224efbe 100644 --- a/.gitlab/build_lassen.yml +++ b/.gitlab/build_lassen.yml @@ -23,13 +23,13 @@ # Template .src_build_on_lassen: variables: - ALLOC_TIME: "25" + ALLOC_TIME: "35" extends: [.src_build_script, .on_lassen, .src_workflow] needs: [] .full_build_on_lassen: variables: - ALLOC_TIME: "45" + ALLOC_TIME: "60" extends: [.full_build_script, .on_lassen, .full_workflow] needs: [] diff --git a/.gitlab/build_ruby.yml b/.gitlab/build_ruby.yml index 50f23a68d8..88e5794119 100644 --- a/.gitlab/build_ruby.yml +++ b/.gitlab/build_ruby.yml @@ -24,13 +24,13 @@ # Template .src_build_on_ruby: variables: - ALLOC_TIME: "30" + ALLOC_TIME: "40" extends: [.src_build_script, .on_ruby, .src_workflow] needs: [] .full_build_on_ruby: variables: - ALLOC_TIME: "60" + ALLOC_TIME: "70" extends: [.full_build_script, .on_ruby, .full_workflow] needs: [] diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 938309dcb3..1a6980abb7 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -20,6 +20,11 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ ## [Unreleased] - Release date yyyy-mm-dd ### Added +- Added a new "Mir" Axom component for accelerated Material Interface Reconstruction (MIR) algorithms. + MIR algorithms take Blueprint meshes with a matset and they use the matset's material information + to split any input zones that contain multiple materials into zones that contain a single material. + The Mir component provides an implementation of the Equi-Z MIR algorithm, which is a visualization- + oriented algorithm that produces smooth interfaces between zones and their neighbors. - `sidre::View` holding array data may now be re-shaped. See `sidre::View::reshapeArray`. - Sina C++ library is now a component of Axom - Adds optional dependency on [Open CASCADE](https://dev.opencascade.org). The initial intention is diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b4358f96e6..8592d305af 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,7 +17,7 @@ variables: jobs: - job: Build_and_Test - timeoutInMinutes: 90 + timeoutInMinutes: 120 strategy: matrix: @@ -48,7 +48,7 @@ jobs: linux_clang14_noraja: VM_ImageName: 'ubuntu-22.04' Compiler_ImageName: '$(CLANG14_IMAGENAME)' - CMAKE_EXTRA_FLAGS: '-DBUILD_SHARED_LIBS=ON -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON -U RAJA_DIR' + CMAKE_EXTRA_FLAGS: '-DBUILD_SHARED_LIBS=ON -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON -DAXOM_ENABLE_MIR:BOOL=OFF -U RAJA_DIR' BUILD_TYPE: 'Debug' COMPILER: 'clang++' TEST_TARGET: 'linux_clang14' @@ -64,7 +64,7 @@ jobs: linux_clang14_noraja_noumpire: VM_ImageName: 'ubuntu-22.04' Compiler_ImageName: '$(CLANG14_IMAGENAME)' - CMAKE_EXTRA_FLAGS: '-DBUILD_SHARED_LIBS=ON -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON -U RAJA_DIR -U UMPIRE_DIR' + CMAKE_EXTRA_FLAGS: '-DBUILD_SHARED_LIBS=ON -DAXOM_QUEST_ENABLE_EXTRA_REGRESSION_TESTS:BOOL=ON -DAXOM_ENABLE_MIR:BOOL=OFF -U RAJA_DIR -U UMPIRE_DIR' BUILD_TYPE: 'Debug' COMPILER: 'clang++' TEST_TARGET: 'linux_clang14' @@ -79,11 +79,11 @@ jobs: HOST_CONFIG: 'clang@14.0.0' osx_gcc: VM_ImageName: 'macos-13' - CMAKE_EXTRA_FLAGS: '-DAXOM_ENABLE_SIDRE:BOOL=OFF -DAXOM_ENABLE_INLET:BOOL=OFF -DAXOM_ENABLE_KLEE:BOOL=OFF -DAXOM_ENABLE_SINA:BOOL=OFF' + CMAKE_EXTRA_FLAGS: '-DAXOM_ENABLE_SIDRE:BOOL=OFF -DAXOM_ENABLE_INLET:BOOL=OFF -DAXOM_ENABLE_KLEE:BOOL=OFF -DAXOM_ENABLE_SINA:BOOL=OFF -DAXOM_ENABLE_MIR:BOOL=OFF' TEST_TARGET: 'osx_gcc' windows: VM_ImageName: 'windows-2019' - CMAKE_EXTRA_FLAGS: '-DAXOM_ENABLE_SIDRE:BOOL=OFF -DAXOM_ENABLE_INLET:BOOL=OFF -DAXOM_ENABLE_KLEE:BOOL=OFF -DAXOM_ENABLE_SINA:BOOL=OFF' + CMAKE_EXTRA_FLAGS: '-DAXOM_ENABLE_SIDRE:BOOL=OFF -DAXOM_ENABLE_INLET:BOOL=OFF -DAXOM_ENABLE_KLEE:BOOL=OFF -DAXOM_ENABLE_SINA:BOOL=OFF -DAXOM_ENABLE_MIR:BOOL=OFF' TEST_TARGET: 'win_vs' pool: diff --git a/config-build.py b/config-build.py index e87a575928..54d4c3b61f 100755 --- a/config-build.py +++ b/config-build.py @@ -84,6 +84,10 @@ def parse_arguments(): help="Create an eclipse project file.", ) + parser.add_argument( + "-n", "--ninja", action="store_true", help="Create a Ninja project." + ) + parser.add_argument( "-x", "--xcode", action="store_true", help="Create an xcode project." ) @@ -270,6 +274,9 @@ def create_cmake_command_line( if args.eclipse: cmakeline += ' -G "Eclipse CDT4 - Unix Makefiles"' + if args.ninja: + cmakeline += ' -G "Ninja"' + if args.xcode: cmakeline += ' -G "Xcode"' diff --git a/data b/data index c0c33018bd..837af1a208 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit c0c33018bd6796cfe2ff64e46bb2a16402f00f9c +Subproject commit 837af1a2087ba9c8897fd8c415d75bdf8c25d295 diff --git a/scripts/azure-pipelines/linux-build_and_test.sh b/scripts/azure-pipelines/linux-build_and_test.sh index b1f865495d..7b5a484a3d 100755 --- a/scripts/azure-pipelines/linux-build_and_test.sh +++ b/scripts/azure-pipelines/linux-build_and_test.sh @@ -28,18 +28,21 @@ export BUILD_TYPE=${BUILD_TYPE:-Debug} if [[ "$DO_BUILD" == "yes" ]] ; then + echo "~~~~~~ FIND NUMPROCS ~~~~~~~~" + NUMPROCS=`python3 -c "import os; print(f'{os.cpu_count()}')"` + NUM_BUILD_PROCS=`python3 -c "import os; print(f'{max(2, os.cpu_count() * 8 // 10)}')"` echo "~~~~~~ RUNNING CMAKE ~~~~~~~~" or_die python3 ./config-build.py -hc /home/axom/axom/host-configs/docker/${HOST_CONFIG}.cmake -bt ${BUILD_TYPE} -DENABLE_GTEST_DEATH_TESTS=ON ${CMAKE_EXTRA_FLAGS} or_die cd build-$HOST_CONFIG-${BUILD_TYPE,,} echo "~~~~~~ BUILDING ~~~~~~~~" if [[ ${CMAKE_EXTRA_FLAGS} == *COVERAGE* ]] ; then - or_die make -j 8 + or_die make -j $NUM_BUILD_PROCS else - or_die make -j 8 VERBOSE=1 + or_die make -j $NUM_BUILD_PROCS VERBOSE=1 fi if [[ "${DO_TEST}" == "yes" ]] ; then echo "~~~~~~ RUNNING TESTS ~~~~~~~~" - make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test -VV -j8' + make CTEST_OUTPUT_ON_FAILURE=1 test ARGS='-T Test -VV -j$NUM_BUILD_PROCS' fi if [[ "${DO_BENCHMARKS}" == "yes" ]] ; then echo "~~~~~~ RUNNING BENCHMARKS ~~~~~~~~" diff --git a/scripts/benchmarks/mir_concentric_circles.py b/scripts/benchmarks/mir_concentric_circles.py new file mode 100644 index 0000000000..f5f3edef5a --- /dev/null +++ b/scripts/benchmarks/mir_concentric_circles.py @@ -0,0 +1,80 @@ +import os, sys + +runs = { + "build-rzansel-blueos_3_ppc64le_ib_p9-clang@10.0.1.2_cuda-release" : ["seq", "omp", "cuda"], + "build-rzwhippet-toss_4_x86_64_ib-clang@14.0.6-release" : ["seq", "omp"], + "build-rzwhippet-toss_4_x86_64_ib-gcc@10.3.1-release" : ["seq", "omp"], + "build-rzwhippet-toss_4_x86_64_ib-intel@2022.1.0-release" : ["seq", "omp"], + "build-rzvernal-toss_4_x86_64_ib_cray-clang@17.0.0_hip-release" : ["seq", "hip"], + "build-rzadams-toss_4_x86_64_ib_cray-cce@18.0.0_hip-release" :["seq", "hip"] +} + +sizes = (50, 100, 200, 500, 1000, 1500, 2000, 4000, 8000) + +def generate(): + for r in runs: + filename = os.path.join(r, "run_concentric_circles.bash") + f = open(filename, "wt") + f.write("#!/bin/bash\n\n") + f.write("CONCENTRIC_CIRCLES=./examples/mir_concentric_circles\n\n") + + for s in sizes: + f.write(f"# Size {s}\n") + for policy in runs[r]: + f.write(f'echo "Running --gridsize {s} --numcircles 5 --policy {policy}"\n') + f.write(f'$CONCENTRIC_CIRCLES --gridsize {s} --numcircles 5 --policy {policy} > result_{policy}_{s}.txt\n\n') + + f.close() + os.chmod(filename, 0o700) + print(f"Wrote {filename}") + +def read_timings(filename): + retval = "" # no data + try: + lines = open(filename, "rt").readlines() + for line in lines: + pos = line.find("runMIR") + if pos != -1: + toks = [x for x in line.split() if x != ''] + retval = float(toks[2]) # timings (I) + break + except: + pass + return retval + +def make_csv(outputfile): + columns = [] + # Add sizes column + sc = ["Sizes"] + for s in sizes: + sc.append(s) + columns.append(sc) + + # Gather data. + for r in sorted(runs.keys()): + for policy in runs[r]: + buildname = r[6:-8] + name = f"{buildname} {policy.upper()}" + data = [name] + for s in sizes: + filename = os.path.join(r, f"result_{policy}_{s}.txt") + value = read_timings(filename) + data.append(value) + columns.append(data) + + # Write data + f = open(outputfile, "wt") + nrows = len(columns[0]) + for i in range(nrows): + rowdata = [str(c[i]) for c in columns] + line = ",".join(rowdata) + f.write(f"{line}\n") + f.close() + +def main(): + if "--generate" in sys.argv: + generate() + else: + make_csv("concentric_circle_timings.csv") + +main() diff --git a/scripts/spack/packages/axom/package.py b/scripts/spack/packages/axom/package.py index 372a677f44..4712730214 100644 --- a/scripts/spack/packages/axom/package.py +++ b/scripts/spack/packages/axom/package.py @@ -602,6 +602,8 @@ def cmake_args(self): options.append(self.define_from_variant("BUILD_SHARED_LIBS", "shared")) options.append(self.define_from_variant("AXOM_ENABLE_EXAMPLES", "examples")) options.append(self.define_from_variant("AXOM_ENABLE_TOOLS", "tools")) + if "+raja" not in spec or "+umpire" not in spec: + options.append("-DAXOM_ENABLE_MIR:BOOL=OFF") return options diff --git a/src/axom/CMakeLists.txt b/src/axom/CMakeLists.txt index a2fe71cc7c..63a9a0ef3c 100644 --- a/src/axom/CMakeLists.txt +++ b/src/axom/CMakeLists.txt @@ -32,6 +32,7 @@ axom_add_component(COMPONENT_NAME sina DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONE axom_add_component(COMPONENT_NAME slam DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) axom_add_component(COMPONENT_NAME primal DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) axom_add_component(COMPONENT_NAME sidre DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) +axom_add_component(COMPONENT_NAME mir DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) axom_add_component(COMPONENT_NAME mint DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) axom_add_component(COMPONENT_NAME spin DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) axom_add_component(COMPONENT_NAME inlet DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) diff --git a/src/axom/config.hpp.in b/src/axom/config.hpp.in index 0b8af21d7e..76ae1b622a 100644 --- a/src/axom/config.hpp.in +++ b/src/axom/config.hpp.in @@ -100,6 +100,7 @@ #cmakedefine AXOM_USE_KLEE #cmakedefine AXOM_USE_LUMBERJACK #cmakedefine AXOM_USE_MINT +#cmakedefine AXOM_USE_MIR #cmakedefine AXOM_USE_PRIMAL #cmakedefine AXOM_USE_QUEST #cmakedefine AXOM_USE_SIDRE diff --git a/src/axom/core/ArrayView.hpp b/src/axom/core/ArrayView.hpp index ed339ebd70..9e297f600b 100644 --- a/src/axom/core/ArrayView.hpp +++ b/src/axom/core/ArrayView.hpp @@ -73,7 +73,7 @@ class ArrayView : public ArrayBase> typename... Args, typename Enable = typename std::enable_if< sizeof...(Args) == DIM && detail::all_types_are_integral::value>::type> - ArrayView(T* data, Args... args); + AXOM_HOST_DEVICE ArrayView(T* data, Args... args); /*! * \brief Generic constructor for an ArrayView of arbitrary dimension with external data @@ -232,7 +232,7 @@ using MCArrayView = ArrayView; //------------------------------------------------------------------------------ template template -ArrayView::ArrayView(T* data, Args... args) +AXOM_HOST_DEVICE ArrayView::ArrayView(T* data, Args... args) : ArrayView(data, StackArray {{static_cast(args)...}}) { diff --git a/src/axom/core/CMakeLists.txt b/src/axom/core/CMakeLists.txt index f1dbf22b26..6597893751 100644 --- a/src/axom/core/CMakeLists.txt +++ b/src/axom/core/CMakeLists.txt @@ -27,6 +27,7 @@ set(core_headers utilities/CommandLineUtilities.hpp utilities/FileUtilities.hpp utilities/RAII.hpp + utilities/Sorting.hpp utilities/StringUtilities.hpp utilities/System.hpp utilities/Timer.hpp @@ -77,6 +78,7 @@ set(core_headers execution/for_all.hpp execution/nested_for_exec.hpp execution/runtime_policy.hpp + execution/scans.hpp execution/synchronize.hpp execution/internal/seq_exec.hpp diff --git a/src/axom/core/StackArray.hpp b/src/axom/core/StackArray.hpp index e638e9ca69..ec6f482ea3 100644 --- a/src/axom/core/StackArray.hpp +++ b/src/axom/core/StackArray.hpp @@ -10,6 +10,8 @@ #include "axom/core/Macros.hpp" // for axom macros #include "axom/core/Types.hpp" // for axom types +#include + namespace axom { /*! @@ -27,6 +29,14 @@ namespace axom template struct StackArray { + using value_type = T; + + /*! + * \brief Return size of the array. + */ + AXOM_HOST_DEVICE + constexpr static int size() { return N; } + /*! * \brief Accessor, returns a reference to the value at the given index. * @@ -55,6 +65,9 @@ struct StackArray AXOM_HOST_DEVICE constexpr operator const T*() const noexcept { return &m_data[0]; } + AXOM_HOST_DEVICE T* data() noexcept { return &m_data[0]; } + AXOM_HOST_DEVICE const T* data() const noexcept { return &m_data[0]; } + /// @} /*! @@ -135,6 +148,28 @@ AXOM_HOST_DEVICE bool operator<(const StackArray& lhs, return false; } +/** + * \brief Print the StackArray to a stream. + * \param os The stream to use. + * \param obj The StackArray to print. + * \return The input stream. + */ +template +std::ostream& operator<<(std::ostream& os, const StackArray& obj) +{ + os << "("; + for(int i = 0; i < N; i++) + { + if(i > 0) + { + os << ", "; + } + os << obj.m_data[i]; + } + os << ")"; + return os; +} + } /* namespace axom */ #endif /* AXOM_STACKARRAY_HPP_ */ diff --git a/src/axom/core/StaticArray.hpp b/src/axom/core/StaticArray.hpp index 6b5ce4e907..8bd471a957 100644 --- a/src/axom/core/StaticArray.hpp +++ b/src/axom/core/StaticArray.hpp @@ -16,7 +16,8 @@ namespace axom * \accelerated * \class StaticArray * - * \brief Provides a wrapper for a StackArray with an additional size member. + * \brief This class extends StackArray with some std::vector-like convenience methods. + * * * \tparam T the type of the values to hold. * \tparam N the number of values in the array. @@ -25,14 +26,27 @@ namespace axom * execution. */ template -struct StaticArray : public StackArray +class StaticArray : public StackArray { +public: + /*! + * \brief Returns the capacity of the static array + * + * \return The capacity of the static array + */ + AXOM_HOST_DEVICE + constexpr axom::IndexType capacity() const + { + return static_cast(N); + } + /*! * \brief Returns the size of the static array * * \return The size of the static array */ - AXOM_HOST_DEVICE int size() const { return m_size; } + AXOM_HOST_DEVICE + axom::IndexType size() const { return m_size; } /*! * \brief Pushes an object to the back of the static array @@ -45,29 +59,51 @@ struct StaticArray : public StackArray * \note If the static array is full, push_back * will not modify the static array. */ - AXOM_HOST_DEVICE void push_back(const T& obj) + AXOM_HOST_DEVICE + void push_back(const T &obj) { - assert(m_size < N); - if(m_size < N) + assert(m_size < capacity()); + if(m_size < capacity()) { StackArray::m_data[m_size++] = obj; } } + /*! + * \brief Pops the last element off the list. + */ + AXOM_HOST_DEVICE + void pop_back() { m_size = (m_size > 0) ? (m_size - 1) : 0; } + /*! * \brief Clears the data from the static array */ - AXOM_HOST_DEVICE void clear() + AXOM_HOST_DEVICE + void clear() { m_size = 0; } + + /** + * \brief Determines whether the container is empty. + * \return True if empty, false otherwise. + */ + AXOM_HOST_DEVICE + bool empty() const { return m_size == 0; } + + /*! + * \brief Fills the container with the supplied value. + * + * \param fill_value The fill value. + */ + AXOM_HOST_DEVICE + void fill(const T &fill_value) { - for(T& datum : StackArray::m_data) + for(T &datum : StackArray::m_data) { - datum = T(); + datum = fill_value; } - m_size = 0; } private: - int m_size {0}; + axom::IndexType m_size {0}; }; } /* namespace axom */ diff --git a/src/axom/core/execution/scans.hpp b/src/axom/core/execution/scans.hpp new file mode 100644 index 0000000000..9e1bcef565 --- /dev/null +++ b/src/axom/core/execution/scans.hpp @@ -0,0 +1,126 @@ +// 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) + +#ifndef AXOM_CORE_EXECUTION_SCANS_HPP_ +#define AXOM_CORE_EXECUTION_SCANS_HPP_ + +#include "axom/config.hpp" +#include "axom/core/execution/execution_space.hpp" +#include "axom/core/Macros.hpp" +#include "axom/core/Types.hpp" + +// C/C++ includes +#include +#include + +namespace axom +{ +/// \name Scans +/// @{ + +/*! + * \brief Performs exclusive scan over \a input view and stores result in \a output. + * + * \param [in] input The input container to be scanned. + * \param [out] output The container that will contain the output scan data. This + * must have the same number of elements as \a input. + * + * \tparam ExecSpace the execution space where to run the supplied kernel + * \tparam ContiguousMemoryContainer The container type that holds the data + * + * \see axom::execution_space + * + * Usage Example: + * \code + * + * axom::ArrayView sizesView = sizes.view(); + * axom::ArrayView offsetsView = offsets.view(); + * + * // Compute the scan for all elements in sizesView, store scan in offsetsView. + * axom::exclusive_scan(sizesView, offsetsView); + * + * \endcode + * + */ +template +inline void exclusive_scan(const ContiguousMemoryContainer &input, + ContiguousMemoryContainer &output) +{ + assert(input.size() == output.size()); + +#ifdef AXOM_USE_RAJA + + using loop_policy = typename axom::execution_space::loop_policy; + RAJA::exclusive_scan( + RAJA::make_span(input.data(), input.size()), + RAJA::make_span(output.data(), output.size())); + +#else + constexpr bool is_serial = std::is_same::value; + AXOM_STATIC_ASSERT(is_serial); + + typename ContiguousMemoryContainer::value_type total {0}; + for(IndexType i = 0; i < input.size(); ++i) + { + output[i] = total; + total += input[i]; + } +#endif +} + +/*! + * \brief Performs inclusive scan over \a input view and stores result in \a output. + * + * \param [in] input The input container to be scanned. + * \param [out] output The container that will contain the output scan data. This + * must have the same number of elements as \a input. + * + * \tparam ExecSpace the execution space where to run the supplied kernel + * \tparam ContiguousMemoryContainer The container type that holds the data + * + * \see axom::execution_space + * + * Usage Example: + * \code + * + * axom::ArrayView sizesView = sizes.view(); + * axom::ArrayView totalView = totals.view(); + * + * // Compute the scan for all elements in sizesView, store scan in totalView. + * axom::inclusive_scan(sizesView, totalView); + * + * \endcode + * + */ +template +inline void inclusive_scan(const ContiguousMemoryContainer &input, + ContiguousMemoryContainer &output) +{ + assert(input.size() == output.size()); + +#ifdef AXOM_USE_RAJA + + using loop_policy = typename axom::execution_space::loop_policy; + RAJA::inclusive_scan( + RAJA::make_span(input.data(), input.size()), + RAJA::make_span(output.data(), output.size())); + +#else + constexpr bool is_serial = std::is_same::value; + AXOM_STATIC_ASSERT(is_serial); + + typename ContiguousMemoryContainer::value_type total {0}; + for(IndexType i = 0; i < input.size(); ++i) + { + total += input[i]; + output[i] = total; + } +#endif +} +/// @} + +} // namespace axom + +#endif // AXOM_CORE_EXECUTION_FOR_ALL_HPP_ diff --git a/src/axom/core/memory_management.hpp b/src/axom/core/memory_management.hpp index 5a8cebb424..1214781981 100644 --- a/src/axom/core/memory_management.hpp +++ b/src/axom/core/memory_management.hpp @@ -110,6 +110,56 @@ inline int getDefaultAllocatorID() #endif } +/*! + * \brief Returns the ID of the allocator that allocated the memory pointed + * to by \a ptr. + * \param ptr A pointer to memory. + * \return ID of the allocator that allocated the memory. + */ +/// &{ +#ifdef AXOM_USE_UMPIRE +inline int getAllocatorIDForAddress(void* ptr) +{ + umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); + int id; + try + { + id = rm.getAllocator(ptr).getId(); + } + catch(...) + { + // The pointer was likely not allocated via umpire. + id = INVALID_ALLOCATOR_ID; + } + return id; +} +inline int getAllocatorIDForAddress(const void* ptr) +{ + umpire::ResourceManager& rm = umpire::ResourceManager::getInstance(); + int id; + try + { + id = rm.getAllocator(const_cast(ptr)).getId(); + } + catch(...) + { + // The pointer was likely not allocated via umpire. + id = INVALID_ALLOCATOR_ID; + } + return id; +} +#else +inline int getAllocatorIDForAddress(void* AXOM_UNUSED_PARAM(ptr)) +{ + return axom::getDefaultAllocatorID(); +} +inline int getAllocatorIDForAddress(const void* AXOM_UNUSED_PARAM(ptr)) +{ + return axom::getDefaultAllocatorID(); +} +#endif +/// @} + /*! * \brief Allocates a chunk of memory of type T. * @@ -379,16 +429,18 @@ inline int getAllocatorID() * * \return True if the allocator id is for a device; false otherwise. */ +#if defined(AXOM_USE_UMPIRE) inline bool isDeviceAllocator(int allocator_id) { -#if defined(AXOM_USE_UMPIRE) return axom::detail::getAllocatorSpace(allocator_id) == axom::MemorySpace::Device; +} #else - AXOM_UNUSED_VAR(allocator_id); +inline bool isDeviceAllocator(int AXOM_UNUSED_PARAM(allocator_id)) +{ return false; -#endif } +#endif } // namespace axom diff --git a/src/axom/core/tests/core_bit_utilities.hpp b/src/axom/core/tests/core_bit_utilities.hpp index d8ec6eb5e4..8d0edade11 100644 --- a/src/axom/core/tests/core_bit_utilities.hpp +++ b/src/axom/core/tests/core_bit_utilities.hpp @@ -181,3 +181,33 @@ TEST(core_bit_utilities, countl_zero) EXPECT_EQ(bit, axom::utilities::countl_zero(rand_val)); } } + +TEST(core_bit_utilities, setbit_bitisset) +{ + const std::uint32_t pattern = 0xaaaaaaaa; + for(size_t bit = 0; + bit < axom::utilities::BitTraits::BITS_PER_WORD; + bit++) + { + EXPECT_EQ(axom::utilities::bitIsSet(pattern, bit), ((bit & 1) == 1)); + } + + const bool bitvals[] = {false, true, true, false, true, true, false, true}; + std::uint8_t value = 0; + for(size_t i = 0; i < axom::utilities::BitTraits::BITS_PER_WORD; + i++) + { + axom::utilities::setBit(value, i, bitvals[i]); + for(size_t b = 0; b <= i; b++) + { + EXPECT_EQ(axom::utilities::bitIsSet(value, b), bitvals[b]); + } + } + + for(size_t i = 0; i < axom::utilities::BitTraits::BITS_PER_WORD; + i++) + { + axom::utilities::setBit(value, i, false); + EXPECT_EQ(axom::utilities::bitIsSet(value, i), false); + } +} diff --git a/src/axom/core/tests/core_memory_management.hpp b/src/axom/core/tests/core_memory_management.hpp index 669f6df6b9..b5802b501b 100644 --- a/src/axom/core/tests/core_memory_management.hpp +++ b/src/axom/core/tests/core_memory_management.hpp @@ -483,3 +483,26 @@ TEST(core_memory_management, basic_alloc_realloc_dealloc) axom::deallocate(buf); EXPECT_EQ(buf, nullptr); } + +//------------------------------------------------------------------------------ +#ifdef AXOM_USE_UMPIRE +TEST(core_memory_management, allocator_id_for_address) +{ + constexpr std::size_t N = 5; + + int* buf = nullptr; + + // Allocate through allocator. + buf = axom::allocate(N); + EXPECT_NE(buf, nullptr); + int id = axom::getAllocatorIDForAddress(buf); + EXPECT_EQ(id, axom::getDefaultAllocatorID()); + axom::deallocate(buf); + + // Allocate directly (not through allocator). + buf = new int[N]; + id = axom::getAllocatorIDForAddress(buf); + EXPECT_EQ(id, axom::INVALID_ALLOCATOR_ID); + delete[] buf; +} +#endif diff --git a/src/axom/core/tests/utils_utilities.cpp b/src/axom/core/tests/utils_utilities.cpp new file mode 100644 index 0000000000..005729a2da --- /dev/null +++ b/src/axom/core/tests/utils_utilities.cpp @@ -0,0 +1,146 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core/utilities/Utilities.hpp" + +TEST(core_Utilities, log2) +{ + std::cout << "Testing log2 functions." << std::endl; + + // Test integer log2 value of type int + { + int val = 64; + int exp = 6; + EXPECT_EQ(exp, axom::utilities::log2(val)); + } + + // Test non-integer log2 value of type int + { + int val = 72; // not a power of 2 + int exp = 6; + EXPECT_EQ(exp, axom::utilities::log2(val)); + } + + // Test integer log2 value of type double + { + double val = 16.; + double exp = 4.; + EXPECT_EQ(exp, axom::utilities::log2(val)); + } + + // Test non-integer log2 value of type double + { + double val = 20.; // not a power of 2 + double exp = 4.3219281; + EXPECT_NEAR(exp, axom::utilities::log2(val), 1e-5); + } +} + +TEST(core_Utilities, random_real) +{ + std::cout << "Testing random_real functions (non-deterministic)." << std::endl; + + int min = 0; + int max = 1; + for(int offset = 0; offset < 10; ++offset) + { + int cur_min = min - offset; + int cur_max = max + offset; + for(int i = 0; i < 100; ++i) + { + float f_val = axom::utilities::random_real(cur_min, cur_max); + EXPECT_GE(f_val, cur_min); + EXPECT_LT(f_val, cur_max); + + double d_val = axom::utilities::random_real(cur_min, cur_max); + EXPECT_GE(d_val, cur_min); + EXPECT_LT(d_val, cur_max); + + long double ld_val = + axom::utilities::random_real(cur_min, cur_max); + EXPECT_GE(ld_val, cur_min); + EXPECT_LT(ld_val, cur_max); + } + } +} + +TEST(core_Utilities, random_real_with_seed) +{ + std::cout << "Testing random_real functions (deterministic)." << std::endl; + constexpr unsigned int seed = 123456789; + + constexpr double a = -5.0; + constexpr double b = 5.0; + + const double expected_reals[5] = {-1.5112829544380526, + -2.3311429024686219, + -3.6335370551231403, + -4.714431326610093, + 3.6893326916732878}; + + for(int i = 0; i < 5; ++i) + { + const double real = axom::utilities::random_real(a, b, seed); + EXPECT_DOUBLE_EQ(real, expected_reals[i]); + EXPECT_GE(real, a); + EXPECT_LT(real, b); + } +} + +TEST(core_Utilities, minmax) +{ + std::cout << "Testing min and max functions." << std::endl; + + // Test simple min, max comparisons on ints + { + int a = 5; + int b = 7; + + EXPECT_EQ(a, axom::utilities::min(a, b)); + EXPECT_EQ(b, axom::utilities::max(a, b)); + } + + // Test simple min, max comparisons on doubles + { + double a = 5.2; + double b = -1.7; + + EXPECT_EQ(b, axom::utilities::min(a, b)); + EXPECT_EQ(a, axom::utilities::max(a, b)); + } +} + +TEST(core_Utilities, lerp) +{ + std::cout << "Testing linear interpolation (lerp) function." << std::endl; + + double f0 = 50.0; + double f1 = 100.0; + + // Test end points + { + EXPECT_DOUBLE_EQ(f0, axom::utilities::lerp(f0, f1, 0.)); + EXPECT_DOUBLE_EQ(f1, axom::utilities::lerp(f1, f0, 0.)); + + EXPECT_DOUBLE_EQ(f1, axom::utilities::lerp(f0, f1, 1.)); + EXPECT_DOUBLE_EQ(f0, axom::utilities::lerp(f1, f0, 1.)); + } + + // Test midpoint + { + double t = 0.5; + double exp = 75.; + EXPECT_DOUBLE_EQ(exp, axom::utilities::lerp(f0, f1, t)); + } + + // Another test + { + double t = 0.66; + double exp = 83.; + EXPECT_DOUBLE_EQ(exp, axom::utilities::lerp(f0, f1, t)); + } +} diff --git a/src/axom/core/utilities/BitUtilities.hpp b/src/axom/core/utilities/BitUtilities.hpp index e75b5f23c2..ddaf0f3dde 100644 --- a/src/axom/core/utilities/BitUtilities.hpp +++ b/src/axom/core/utilities/BitUtilities.hpp @@ -50,6 +50,38 @@ namespace utilities template struct BitTraits; +template <> +struct BitTraits +{ + constexpr static int NUM_BYTES = 8; + constexpr static int BITS_PER_WORD = NUM_BYTES << 3; + constexpr static int LG_BITS_PER_WORD = 6; +}; + +template <> +struct BitTraits +{ + constexpr static int NUM_BYTES = 4; + constexpr static int BITS_PER_WORD = NUM_BYTES << 3; + constexpr static int LG_BITS_PER_WORD = 5; +}; + +template <> +struct BitTraits +{ + constexpr static int NUM_BYTES = 2; + constexpr static int BITS_PER_WORD = NUM_BYTES << 3; + constexpr static int LG_BITS_PER_WORD = 4; +}; + +template <> +struct BitTraits +{ + constexpr static int NUM_BYTES = 1; + constexpr static int BITS_PER_WORD = NUM_BYTES << 3; + constexpr static int LG_BITS_PER_WORD = 3; +}; + template <> struct BitTraits { @@ -197,6 +229,64 @@ AXOM_HOST_DEVICE inline std::int32_t countl_zero(std::int32_t word) noexcept } // gpu_macros_example_end +/*! + * \brief Determine whether the \a bit is set in \a flags. + * \accelerated + * + * \tparam FlagType The integer type that contains the flags. + * \tparam BitType The index type that stores the bit index. + * + * \param[in] flags The flags whose bit we're testing. + * \param[in] bit The bit we're testing. + * + * \return True if the bit is set; false otherwise. + */ +template +AXOM_HOST_DEVICE +constexpr bool bitIsSet(FlagType flags, BitType bit) +{ + assert(static_cast(bit) < BitTraits::BITS_PER_WORD << 3); + return (flags & (1 << bit)) > 0; +} + +/*! + * \brief Set the bit in flags. + * \accelerated + * + * \tparam FlagType The integer type that contains the flags. + * \tparam BitType The index type that stores the bit index. + * + * \param[inout] flags The flags whose bit we're setting. + * \param[in] bit The bit we're setting. + * \param[in] value The value we're setting into the bit. + */ +template +AXOM_HOST_DEVICE +constexpr void setBit(FlagType &flags, BitType bit, bool value = true) +{ + assert(static_cast(bit) < BitTraits::BITS_PER_WORD << 3); + const auto mask = 1 << bit; + flags = value ? (flags | mask) : (flags & ~mask); +} + +/*! + * \brief Set the bit in flags. + * \accelerated + * + * \tparam FlagType The integer type that contains the flags. + * \tparam BitType The index type that stores the bit index. + * + * \param[inout] flags The flags whose bit we're setting. + * \param[in] bit The bit we're setting. + */ +template +AXOM_HOST_DEVICE +constexpr void setBitOn(FlagType &flags, BitType bit) +{ + assert(static_cast(bit) < BitTraits::BITS_PER_WORD << 3); + flags |= (1 << bit); +} + } // namespace utilities } // namespace axom diff --git a/src/axom/core/utilities/Sorting.hpp b/src/axom/core/utilities/Sorting.hpp new file mode 100644 index 0000000000..5b647f1a99 --- /dev/null +++ b/src/axom/core/utilities/Sorting.hpp @@ -0,0 +1,213 @@ +// 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) + +#ifndef AXOM_CORE_UTILITIES_SORTING_HPP +#define AXOM_CORE_UTILITIES_SORTING_HPP +#include +#include + +namespace axom +{ +namespace utilities +{ +/** + * \brief This is a template suitable for sorting small arrays on device. + * + * \tparam T The data type being sorted. + * \tparam N The biggest array size that will be used. + * + * \note For very short arrays, a simpler sort is faster. As the array size + * increases, the algorithm switches to qsort. Also, this is designed as + * a template class so it can be specialized. + */ +template ::max()> +struct Sorting +{ + /** + * \brief Sort an array of values in place. + * + * \param[inout] values The array of values to sort. + * \param n The number of values in the array. + */ + AXOM_HOST_DEVICE + inline static void sort(T *values, int n) + { + if(n < 11) + insertionSort(values, n); + else + qsort(values, n); + } + + /** + * \brief Computes stack size for qsort. + * \return A number of elements for an array-based stack. + */ + AXOM_HOST_DEVICE + constexpr static int stack_size() + { + int v = N; + int i = 0; + while(v > 0) + { + i++; + v /= 2; + } + return i * 2; + } + + /** + * \brief Sort the input array using qsort. + * + * \param[inout] values The array to be sorted. + * \param n The number of values in the array. + */ + AXOM_HOST_DEVICE + static void qsort(T *values, int n) + { + if(n <= 1) return; + int stack[stack_size()][2]; + int stack_count = 1; + stack[0][0] = 0; + stack[0][1] = n - 1; + + while(stack_count > 0) + { + stack_count--; + int low = stack[stack_count][0]; + int high = stack[stack_count][1]; + + if(low < high) + { + int pivot_index = partition(values, low, high); + + stack[stack_count][0] = low; + stack[stack_count][1] = pivot_index - 1; + stack_count++; + + stack[stack_count][0] = pivot_index + 1; + stack[stack_count][1] = high; + stack_count++; + } + } + } + + /** + * \brief Create a partition for qsort. + * + * \param[inout] values The array to be sorted. + * \param low The lower bound of the input partition. + * \param high The upper bound of the input partition. + * + * \return A new pivot. + */ + AXOM_HOST_DEVICE + static int partition(T *values, int low, int high) + { + const T pivot = values[high]; + int i = low - 1; + for(int j = low; j < high; j++) + { + if(values[j] <= pivot) + { + i++; + axom::utilities::swap(values[i], values[j]); + } + } + axom::utilities::swap(values[i + 1], values[high]); + return i + 1; + } + + /** + * \brief Sort the input array using insertion sort. + * + * \param[inout] values The array to be sorted. + * \param n The number of values in the array. + */ + AXOM_HOST_DEVICE + inline static void insertionSort(T *values, int n) + { + for(int i = 1; i < n; i++) + { + int j = i; + // Keep swapping elements until we're not out-of-order. + while(j > 0 && (values[j] < values[j - 1])) + { + axom::utilities::swap(values[j], values[j - 1]); + j--; + } + } + } +}; + +namespace detail +{ + +/** + * \brief Swap 2 elements if b < a. + * param a The first value. + * param b The second value. + */ +template +AXOM_HOST_DEVICE inline void ifswap(T &a, T &b) +{ + if(b < a) + { + T tmp = a; + a = b; + b = tmp; + } +} + +} // end namespace detail + +/** + * \brief Template specialization for sorting arrays with 3 elements. + * \note Specializing resulted in a small speedup over general sorting. + */ +template +struct Sorting +{ + /** + * \brief Sort the input array. + * + * \param[inout] values The array to be sorted. + */ + AXOM_HOST_DEVICE + inline static void sort(T *values) + { + detail::ifswap(values[0], values[1]); + detail::ifswap(values[1], values[2]); + detail::ifswap(values[0], values[1]); + } +}; + +/** + * \brief Template specialization for sorting arrays with 4 elements. + * \note Specializing resulted in a small speedup over general sorting. + */ +template +struct Sorting +{ + /** + * \brief Sort the input array. + * + * \param[inout] values The array to be sorted. + */ + AXOM_HOST_DEVICE + inline static void sort(T *values) + { + detail::ifswap(values[0], values[1]); + detail::ifswap(values[2], values[3]); + detail::ifswap(values[1], values[2]); + detail::ifswap(values[0], values[1]); + detail::ifswap(values[2], values[3]); + detail::ifswap(values[1], values[2]); + } +}; + +} // end namespace utilities +} // end namespace axom + +#endif diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index c48d85476d..16cd21e729 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -109,23 +109,28 @@ inline AXOM_HOST_DEVICE void swap(T& a, T& b) b = tmp; } -/*! - * \brief returns the linear interpolation of \a A and \a B at \a t. i.e. (1-t)A+tB +/*! + * \brief Returns the base 2 logarithm of the input. + * \param [in] val The input value */ template -inline AXOM_HOST_DEVICE T lerp(T A, T B, T t) +inline T log2(T val) { - return (1 - t) * A + t * B; + return static_cast(std::log2(val)); } /*! - * \brief Returns the base 2 logarithm of the input. - * \param [in] val The input value + * \brief Linearly interpolates between two values + * \param [in] val0 The first value + * \param [in] val2 The second value + * \param [in] t The interpolation parameter. + * \return The interpolated value */ template -inline T log2(T val) +inline AXOM_HOST_DEVICE T lerp(T v0, T v1, T t) { - return static_cast(std::log2(val)); + constexpr T one = T(1); + return (one - t) * v0 + t * v1; } /*! diff --git a/src/axom/doxygen_mainpage.md b/src/axom/doxygen_mainpage.md index fd3662f7c2..8999520209 100644 --- a/src/axom/doxygen_mainpage.md +++ b/src/axom/doxygen_mainpage.md @@ -10,6 +10,7 @@ Axom provides libraries that address common computer science needs. It grew fro * @subpage kleetop provides functionality to add non-conformal material regions to meshes. * @subpage lumberjacktop provides logging aggregation and filtering capability. * @subpage minttop provides a comprehensive mesh data model. +* @subpage mirtop provides algorithms for material interface reconstruction on multimaterial meshes. * @subpage multimattop provides an API for managing multimaterial field data. * @subpage primaltop provides an API for geometric primitives and computational geometry tests. * @subpage questtop provides an API to query point distance and position relative to meshes. @@ -23,9 +24,10 @@ Dependencies between components are as follows: - Core, Slic, and Lumberjack provide basic services to the rest of Axom and to user code - Core has no dependencies, and the other modules depend on Core - Slic optionally depends on Lumberjack -- Slam, Primal, Sidre, Spin, Inlet, Mint, Klee, Multimat and Quest all depend on Slic and Core +- Slam, Primal, Sidre, Spin, Inlet, Mint, Mir, Klee, Multimat and Quest all depend on Slic and Core - Inlet depends on Sidre and Primal - Mint depends on Slam, and optionally Sidre + - Mir depends on Slic, Slam, and Primal - Spin depends on Primal and Slam - Quest depends on Slam, Primal, Spin, and Mint - Klee depends on Sidre, Inlet and Primal diff --git a/src/axom/mint/tests/mint_execution_cell_traversals.cpp b/src/axom/mint/tests/mint_execution_cell_traversals.cpp index 9a2805d9f6..a713f546da 100644 --- a/src/axom/mint/tests/mint_execution_cell_traversals.cpp +++ b/src/axom/mint/tests/mint_execution_cell_traversals.cpp @@ -30,7 +30,6 @@ namespace mint //------------------------------------------------------------------------------ namespace { - template void check_for_all_cells_idx(int dimension) { diff --git a/src/axom/mint/tests/mint_execution_face_traversals.cpp b/src/axom/mint/tests/mint_execution_face_traversals.cpp index ea322a911d..49007c73cf 100644 --- a/src/axom/mint/tests/mint_execution_face_traversals.cpp +++ b/src/axom/mint/tests/mint_execution_face_traversals.cpp @@ -28,7 +28,6 @@ namespace mint //------------------------------------------------------------------------------ namespace { - template void check_for_all_faces(int dimension) { diff --git a/src/axom/mint/tests/mint_execution_node_traversals.cpp b/src/axom/mint/tests/mint_execution_node_traversals.cpp index 4f3713a486..d3e159331d 100644 --- a/src/axom/mint/tests/mint_execution_node_traversals.cpp +++ b/src/axom/mint/tests/mint_execution_node_traversals.cpp @@ -29,7 +29,6 @@ namespace mint //------------------------------------------------------------------------------ namespace { - template void check_for_all_nodes_idx(int dimension) { diff --git a/src/axom/mir/CMakeLists.txt b/src/axom/mir/CMakeLists.txt new file mode 100644 index 0000000000..92ca3d1a08 --- /dev/null +++ b/src/axom/mir/CMakeLists.txt @@ -0,0 +1,144 @@ +# Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +# other Axom Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (BSD-3-Clause) +#------------------------------------------------------------------------------ +# MIR -- Material Interface Reconstruction +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# Check necessary dependencies +#------------------------------------------------------------------------------ +axom_component_requires(NAME MIR + COMPONENTS SLIC SLAM PRIMAL + TPLS Conduit RAJA) + +#------------------------------------------------------------------------------ +# Specify all headers/sources +#------------------------------------------------------------------------------ + +# A serial reference implementation (remove eventually) +set(mir_reference_headers + reference/MIRMesh.hpp + reference/MIRMeshTypes.hpp + reference/ZooClippingTables.hpp + reference/InterfaceReconstructor.hpp + reference/CellData.hpp + reference/CellClipper.hpp + reference/MIRUtilities.hpp + reference/CellGenerator.hpp + ) +set(mir_reference_sources + reference/MIRMesh.cpp + reference/InterfaceReconstructor.cpp + reference/ZooClippingTables.cpp + reference/CellData.cpp + reference/CellClipper.cpp + reference/CellGenerator.cpp + ) + +# Future classes (evaluate whether to keep them) +set(mir_future_headers + future/ClipFieldFilterDevice.hpp + future/ClipFieldFilter.hpp + ) +set(mir_future_sources + future/ClipFieldFilter.cpp + ) + +set(mir_headers + ${mir_reference_headers} + MeshTester.hpp + + clipping/BlendGroupBuilder.hpp + clipping/ClipCases.h + clipping/ClipField.hpp + clipping/ClipOptions.hpp + clipping/ClipTableManager.hpp + EquiZAlgorithm.hpp + MIRAlgorithm.hpp + MIROptions.hpp + Options.hpp + utilities/blueprint_utilities.hpp + utilities/CoordsetBlender.hpp + utilities/CoordsetSlicer.hpp + utilities/ExtractZones.hpp + utilities/FieldBlender.hpp + utilities/FieldSlicer.hpp + utilities/MakeUnstructured.hpp + utilities/MatsetSlicer.hpp + utilities/MergeMeshes.hpp + utilities/NodeToZoneRelationBuilder.hpp + utilities/RecenterField.hpp + utilities/SelectedZones.hpp + utilities/utilities.hpp + utilities/ZoneListBuilder.hpp + views/dispatch_coordset.hpp + views/dispatch_material.hpp + views/dispatch_rectilinear_topology.hpp + views/dispatch_structured_topology.hpp + views/dispatch_topology.hpp + views/dispatch_uniform_topology.hpp + views/dispatch_unstructured_topology.hpp + views/dispatch_utilities.hpp + views/ExplicitCoordsetView.hpp + views/MaterialView.hpp + views/NodeArrayView.hpp + views/RectilinearCoordsetView.hpp + views/Shapes.hpp + views/StridedStructuredIndexing.hpp + views/StructuredIndexing.hpp + views/StructuredTopologyView.hpp + views/UniformCoordsetView.hpp + views/UnstructuredTopologyMixedShapeView.hpp + views/UnstructuredTopologyPolyhedralView.hpp + views/UnstructuredTopologySingleShapeView.hpp + views/view_traits.hpp + ) + +set(mir_sources + ${mir_reference_sources} + + clipping/ClipCasesHex.cpp + clipping/ClipCasesPyr.cpp + clipping/ClipCasesQua.cpp + clipping/ClipCasesTet.cpp + clipping/ClipCasesTri.cpp + clipping/ClipCasesWdg.cpp + MeshTester.cpp + MIRAlgorithm.cpp + utilities/blueprint_utilities.cpp + views/MaterialView.cpp + views/UnstructuredTopologyMixedShapeView.cpp + ) + +#------------------------------------------------------------------------------ +# Build and install the library +#------------------------------------------------------------------------------ +set(mir_depends_on core slic slam primal conduit::conduit RAJA) + +axom_add_library( + NAME mir + SOURCES ${mir_sources} + HEADERS ${mir_headers} + DEPENDS_ON ${mir_depends_on} + FOLDER axom/mir) + +axom_write_unified_header(NAME mir + HEADERS ${mir_headers} ) + +axom_install_component(NAME mir + HEADERS ${mir_headers} ) + +#------------------------------------------------------------------------------ +# Add tests, benchmarks and examples +#------------------------------------------------------------------------------ +if (AXOM_ENABLE_TESTS) + add_subdirectory(tests) +endif() + +if (AXOM_ENABLE_EXAMPLES) + add_subdirectory(examples) +endif() + + diff --git a/src/axom/mir/EquiZAlgorithm.hpp b/src/axom/mir/EquiZAlgorithm.hpp new file mode 100644 index 0000000000..3cd79b084e --- /dev/null +++ b/src/axom/mir/EquiZAlgorithm.hpp @@ -0,0 +1,1398 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for internals. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#ifndef AXOM_MIR_EQUIZ_ALGORITHM_HPP_ +#define AXOM_MIR_EQUIZ_ALGORITHM_HPP_ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/mir.hpp" +#include "axom/slic.hpp" + +// Include these directly for now. +#include "axom/mir/MIRAlgorithm.hpp" +#include "axom/mir/utilities/ExtractZones.hpp" +#include "axom/mir/utilities/MergeMeshes.hpp" +#include "axom/mir/utilities/NodeToZoneRelationBuilder.hpp" +#include "axom/mir/utilities/RecenterField.hpp" +#include "axom/mir/utilities/ZoneListBuilder.hpp" +#include "axom/mir/views/dispatch_coordset.hpp" +#include "axom/mir/views/MaterialView.hpp" + +#include + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +#include +#include + +// This macro makes the EquiZ algorithm split processing into clean/mixed stages. +#define AXOM_EQUIZ_SPLIT_PROCESSING + +// Uncomment to save inputs and outputs. +// #define AXOM_EQUIZ_DEBUG + +#if defined(AXOM_EQUIZ_DEBUG) + #include +#endif + +namespace axom +{ +namespace mir +{ +using MaterialID = int; +using MaterialIDArray = axom::Array; +using MaterialIDView = axom::ArrayView; +using MaterialVF = float; +using MaterialVFArray = axom::Array; +using MaterialVFView = axom::ArrayView; + +constexpr static int NULL_MATERIAL = -1; +constexpr static MaterialVF NULL_MATERIAL_VF = -1.f; + +namespace internal +{ +/*! + * \brief This class is an intersection policy compatible with ClipField. It + * helps determine clip cases and weights using material-aware logic. + * + * \tparam ConnectivityT The type of index we'd see in the associated mesh's + * connectivity. We template on it so we can pass the + * array views of connectivity (node lists) to methods here. + * \tparam MAXMATERIALS The max number of materials to handle. + */ +template +class MaterialIntersector +{ +public: + using ConnectivityType = ConnectivityT; + using ConnectivityView = axom::ArrayView; + + /*! + * \brief This is a view class for MatsetIntersector that can be used in device code. + */ + struct View + { + static constexpr int INVALID_INDEX = -1; + + /*! + * \brief Determine the clipping case, taking into account the zone's material + * and the current material being added. + * + * \param zoneIndex The zone index in the zoneMaterialView field. + * \param nodeIds A view containing node ids for the current zone. + * + * \return The clip case number for the zone. + */ + AXOM_HOST_DEVICE + axom::IndexType determineClipCase(axom::IndexType zoneIndex, + const ConnectivityView &nodeIdsView) const + { + // Determine the matvf view index for the material that owns the zone. + int backgroundIndex = INVALID_INDEX; + int zoneMatID = m_zoneMatNumberView[zoneIndex]; + if(zoneMatID != NULL_MATERIAL) + backgroundIndex = matNumberToIndex(zoneMatID); + + axom::IndexType clipcase = 0; + const auto n = nodeIdsView.size(); + for(IndexType i = 0; i < n; i++) + { + const auto nid = nodeIdsView[i]; +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(nid >= 0 && nid < m_matvfViews[0].size()); + #else + SLIC_ASSERT_MSG(nid >= 0 && nid < m_matvfViews[0].size(), + axom::fmt::format("Node id {} is not in range [0, {}).", + nid, + m_matvfViews[0].size())); + #endif +#endif + // clang-format off + MaterialVF vf1 = (backgroundIndex != INVALID_INDEX) ? m_matvfViews[backgroundIndex][nid] : NULL_MATERIAL_VF; + MaterialVF vf2 = (m_currentMaterialIndex != INVALID_INDEX) ? m_matvfViews[m_currentMaterialIndex][nid] : 0; + // clang-format on + + clipcase |= (vf2 > vf1) ? (1 << i) : 0; + } + return clipcase; + } + + /*! + * \brief Compute the weight of a clip value along an edge (id0, id1) using the clip field and value. + * + * \param id0 The mesh node at the start of the edge. + * \param id1 The mesh node at the end of the edge. + */ + AXOM_HOST_DEVICE + float computeWeight(axom::IndexType zoneIndex, + ConnectivityType id0, + ConnectivityType id1) const + { + // Determine the matvf view index for the material that owns the zone. + int backgroundIndex = INVALID_INDEX; + int zoneMatID = m_zoneMatNumberView[zoneIndex]; + if(zoneMatID != NULL_MATERIAL) + backgroundIndex = matNumberToIndex(zoneMatID); + // Determine the matvf view index for the current material. +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(id0 >= 0 && id0 < m_matvfViews[0].size()); + assert(id1 >= 0 && id1 < m_matvfViews[0].size()); + #else + SLIC_ASSERT_MSG(id0 >= 0 && id0 < m_matvfViews[0].size(), + axom::fmt::format("Node id {} is not in range [0, {}).", + id0, + m_matvfViews[0].size())); + SLIC_ASSERT_MSG(id1 >= 0 && id1 < m_matvfViews[0].size(), + axom::fmt::format("Node id {} is not in range [0, {}).", + id1, + m_matvfViews[0].size())); + #endif +#endif + // Get the volume fractions for mat1, mat2 at the edge endpoints id0, id1. + MaterialVF vf1[2], vf2[2]; + // clang-format off + vf1[0] = (backgroundIndex != INVALID_INDEX) ? m_matvfViews[backgroundIndex][id0] : NULL_MATERIAL_VF; + vf1[1] = (backgroundIndex != INVALID_INDEX) ? m_matvfViews[backgroundIndex][id1] : NULL_MATERIAL_VF; + vf2[0] = (m_currentMaterialIndex != INVALID_INDEX) ? m_matvfViews[m_currentMaterialIndex][id0] : 0; + vf2[1] = (m_currentMaterialIndex != INVALID_INDEX) ? m_matvfViews[m_currentMaterialIndex][id1] : 0; + // clang-format on + + float numerator = vf2[0] - vf1[0]; + float denominator = -vf1[0] + vf1[1] + vf2[0] - vf2[1]; + + float t = 0.f; + if(denominator != 0.f) + { + t = numerator / denominator; + } + t = axom::utilities::clampVal(t, 0.f, 1.f); + + return t; + } + + /*! + * \brief Return the volume fraction array index in m_matIndicesView for the + * given material number \a matNumber. + * + * \param matNumber A material number that occurs in the matset material ids. + * + * \return The m_matNumbersView index on success; INVALID_INDEX on failure. + */ + AXOM_HOST_DEVICE + inline int matNumberToIndex(int matNumber) const + { + auto index = axom::mir::utilities::bsearch(matNumber, m_matNumbersView); + return (index != -1) ? m_matIndicesView[index] : INVALID_INDEX; + } + + /// Helper initialization methods for the host. + + void addMaterial(const MaterialVFView &matvf) + { + m_matvfViews.push_back(matvf); + } + + void setMaterialNumbers(const axom::ArrayView &matNumbersView) + { + m_matNumbersView = matNumbersView; + } + + void setMaterialIndices(const axom::ArrayView &matIndicesView) + { + m_matIndicesView = matIndicesView; + } + + void setZoneMaterialID(const axom::ArrayView &zoneMatsView) + { + m_zoneMatNumberView = zoneMatsView; + } + + void setCurrentMaterial(int matNumber, int matNumberIndex) + { + m_currentMaterial = matNumber; + m_currentMaterialIndex = matNumberIndex; + } + + axom::StaticArray + m_matvfViews {}; //!< Array of volume fraction views + axom::ArrayView m_matNumbersView {}; //!< Sorted array of material numbers. + axom::ArrayView m_matIndicesView {}; //!< Array of indices into m_matvfViews for the material numbers. + axom::ArrayView m_zoneMatNumberView {}; //!< Contains the current material number that owns each zone. + int m_currentMaterial {}; //!< The current material. + int m_currentMaterialIndex {}; //!< The current material's index in the m_matvfViews. + }; + + /*! + * \brief Initialize the object from options. + * \param n_options The node that contains the options. + * \param n_fields The node that contains fields. + */ + void initialize(const conduit::Node &AXOM_UNUSED_PARAM(n_options), + const conduit::Node &AXOM_UNUSED_PARAM(n_fields)) + { } + + /*! + * \brief Determine the name of the topology on which to operate. + * \param n_input The input mesh node. + * \param n_options The clipping options. + * \return The name of the toplogy on which to operate. + */ + std::string getTopologyName(const conduit::Node &AXOM_UNUSED_PARAM(n_input), + const conduit::Node &n_options) const + { + return n_options["topology"].as_string(); + } + + /// Set various attributes. + + void addMaterial(const MaterialVFView &matvf) { m_view.addMaterial(matvf); } + + void setMaterialNumbers(const axom::ArrayView &matNumbers) + { + m_view.setMaterialNumbers(matNumbers); + } + + void setMaterialIndices(const axom::ArrayView &matIndices) + { + m_view.setMaterialIndices(matIndices); + } + + void setZoneMaterialID(const axom::ArrayView &zoneMatsView) + { + m_view.setZoneMaterialID(zoneMatsView); + } + + void setCurrentMaterial(int matNumber, int matNumberIndex) + { + m_view.setCurrentMaterial(matNumber, matNumberIndex); + } + + /*! + * \brief Return a new instance of the view. + * \return A new instance of the view. + * \note Call this after all values are set. + */ + View view() const { return m_view; } + +private: + View m_view {}; +}; + +} // end namespace internal + +/*! + * \accelerated + * \brief Implements Meredith's Equi-Z algorithm on the GPU using Blueprint inputs/outputs. + */ +template +class EquiZAlgorithm : public axom::mir::MIRAlgorithm +{ + using reduce_policy = typename axom::execution_space::reduce_policy; + +public: + using ConnectivityType = typename TopologyView::ConnectivityType; + + /*! + * \brief Constructor + * + * \param topoView The topology view to use for the input data. + * \param coordsetView The coordset view to use for the input data. + * \param matsetView The matset view to use for the input data. + */ + EquiZAlgorithm(const TopologyView &topoView, + const CoordsetView &coordsetView, + const MatsetView &matsetView) + : axom::mir::MIRAlgorithm() + , m_topologyView(topoView) + , m_coordsetView(coordsetView) + , m_matsetView(matsetView) + { } + + /// Destructor + virtual ~EquiZAlgorithm() = default; + +// The following members are protected (unless using CUDA) +#if !defined(__CUDACC__) +protected: +#endif + +#if defined(AXOM_EQUIZ_DEBUG) + void printNode(const conduit::Node &n) const + { + conduit::Node options; + options["num_children_threshold"] = 10000; + options["num_elements_threshold"] = 10000; + n.to_summary_string_stream(std::cout, options); + } +#endif + + /*! + * \brief Perform material interface reconstruction on a single domain. + * + * \param[in] n_topo The Conduit node containing the topology that will be used for MIR. + * \param[in] n_coordset The Conduit node containing the coordset. + * \param[in] n_fields The Conduit node containing the fields. + * \param[in] n_matset The Conduit node containing the matset. + * \param[in] n_options The Conduit node containing the options that help govern MIR execution. + * + * \param[out] n_newTopo A node that will contain the new clipped topology. + * \param[out] n_newCoordset A node that will contain the new coordset for the clipped topology. + * \param[out] n_newFields A node that will contain the new fields for the clipped topology. + * \param[out] n_newMatset A Conduit node that will contain the new matset. + * + */ + virtual void executeDomain(const conduit::Node &n_topo, + const conduit::Node &n_coordset, + const conduit::Node &n_fields, + const conduit::Node &n_matset, + const conduit::Node &n_options, + conduit::Node &n_newTopo, + conduit::Node &n_newCoordset, + conduit::Node &n_newFields, + conduit::Node &n_newMatset) override + { + namespace bputils = axom::mir::utilities::blueprint; + AXOM_ANNOTATE_SCOPE("EquizAlgorithm"); + + // Copy the options. + conduit::Node n_options_copy; + bputils::copy(n_options_copy, n_options); + n_options_copy["topology"] = n_topo.name(); + +#if defined(AXOM_EQUIZ_DEBUG) + // Save the MIR input. + conduit::Node n_tmpInput; + n_tmpInput[n_topo.path()].set_external(n_topo); + n_tmpInput[n_coordset.path()].set_external(n_coordset); + n_tmpInput[n_fields.path()].set_external(n_fields); + n_tmpInput[n_matset.path()].set_external(n_matset); + conduit::relay::io::blueprint::save_mesh(n_tmpInput, + "debug_equiz_input", + "hdf5"); +#endif + +#if defined(AXOM_EQUIZ_SPLIT_PROCESSING) + // _mir_utilities_zlb_begin + // Come up with lists of clean/mixed zones. + axom::Array cleanZones, mixedZones; + bputils::ZoneListBuilder zlb( + m_topologyView, + m_matsetView); + if(n_options.has_child("selectedZones")) + { + auto selectedZonesView = bputils::make_array_view( + n_options.fetch_existing("selectedZones")); + zlb.execute(m_coordsetView.numberOfNodes(), + selectedZonesView, + cleanZones, + mixedZones); + } + else + { + zlb.execute(m_coordsetView.numberOfNodes(), cleanZones, mixedZones); + } + // _mir_utilities_zlb_end + SLIC_ASSERT((cleanZones.size() + mixedZones.size()) == + m_topologyView.numberOfZones()); + SLIC_INFO(axom::fmt::format("cleanZones: {}, mixedZones: {}", + cleanZones.size(), + mixedZones.size())); + + if(cleanZones.size() > 0 && mixedZones.size() > 0) + { + // Gather the inputs into a single root but replace the fields with + // a new node to which we can add additional fields. + conduit::Node n_root; + n_root[n_coordset.path()].set_external(n_coordset); + n_root[n_topo.path()].set_external(n_topo); + n_root[n_matset.path()].set_external(n_matset); + conduit::Node &n_root_coordset = n_root[n_coordset.path()]; + conduit::Node &n_root_topo = n_root[n_topo.path()]; + conduit::Node &n_root_matset = n_root[n_matset.path()]; + conduit::Node &n_root_fields = n_root["fields"]; + for(conduit::index_t i = 0; i < n_fields.number_of_children(); i++) + { + n_root_fields[n_fields[i].name()].set_external(n_fields[i]); + } + + // Make the clean mesh. + conduit::Node n_cleanOutput; + makeCleanOutput(n_root, n_topo.name(), cleanZones.view(), n_cleanOutput); + + // Add a original nodes field. + addOriginal(n_root_fields[originalNodesFieldName()], + n_topo.name(), + "vertex", + m_coordsetView.numberOfNodes()); + // If there are fields in the options, make sure the new field is handled too. + if(n_options_copy.has_child("fields")) + n_options_copy["fields/" + originalNodesFieldName()] = + originalNodesFieldName(); + + // Process the mixed part of the mesh. We select just the mixed zones. + n_options_copy["selectedZones"].set_external(mixedZones.data(), + mixedZones.size()); + n_options_copy["newNodesField"] = newNodesFieldName(); + processMixedZones(n_root_topo, + n_root_coordset, + n_root_fields, + n_root_matset, + n_options_copy, + n_newTopo, + n_newCoordset, + n_newFields, + n_newMatset); + + // Make node map and slice info for merging. + axom::Array nodeMap, nodeSlice; + createNodeMapAndSlice(n_newFields, nodeMap, nodeSlice); + + // Gather the MIR output into a single node. + conduit::Node n_mirOutput; + n_mirOutput[n_topo.path()].set_external(n_newTopo); + n_mirOutput[n_coordset.path()].set_external(n_newCoordset); + n_mirOutput[n_fields.path()].set_external(n_newFields); + n_mirOutput[n_matset.path()].set_external(n_newMatset); + #if defined(AXOM_EQUIZ_DEBUG) + printNode(n_mirOutput); + conduit::relay::io::blueprint::save_mesh(n_mirOutput, + "debug_equiz_mir", + "hdf5"); + #endif + + // Merge clean and MIR output. + // _mir_utilities_mergemeshes_begin + std::vector inputs(2); + inputs[0].m_input = &n_cleanOutput; + + inputs[1].m_input = &n_mirOutput; + inputs[1].m_nodeMapView = nodeMap.view(); + inputs[1].m_nodeSliceView = nodeSlice.view(); + + conduit::Node mmOpts, n_merged; + mmOpts["topology"] = n_topo.name(); + bputils::MergeMeshes mm; + mm.execute(inputs, mmOpts, n_merged); + // _mir_utilities_mergemeshes_end + + #if defined(AXOM_EQUIZ_DEBUG) + std::cout << "--- clean ---\n"; + printNode(n_cleanOutput); + std::cout << "--- MIR ---\n"; + printNode(n_mirOutput); + std::cout << "--- merged ---\n"; + + // Save merged output. + printNode(n_merged); + conduit::relay::io::blueprint::save_mesh(n_merged, + "debug_equiz_merged", + "hdf5"); + #endif + + // Move the merged output into the output variables. + n_newCoordset.move(n_merged[n_coordset.path()]); + n_newTopo.move(n_merged[n_topo.path()]); + n_newFields.move(n_merged[n_fields.path()]); + n_newMatset.move(n_merged[n_matset.path()]); + } + else if(cleanZones.size() == 0 && mixedZones.size() > 0) + { + // Only mixed zones. + processMixedZones(n_topo, + n_coordset, + n_fields, + n_matset, + n_options_copy, + n_newTopo, + n_newCoordset, + n_newFields, + n_newMatset); + } + else if(cleanZones.size() > 0 && mixedZones.size() == 0) + { + // There were no mixed zones. We can copy the input to the output. + { + AXOM_ANNOTATE_SCOPE("copy"); + bputils::copy(n_newCoordset, n_coordset); + bputils::copy(n_newTopo, n_topo); + bputils::copy(n_newFields, n_fields); + bputils::copy(n_newMatset, n_matset); + } + + // Add an originalElements array. + addOriginal(n_newFields["originalElements"], + n_topo.name(), + "element", + m_topologyView.numberOfZones()); + } +#else + // Handle all zones via MIR. + processMixedZones(n_topo, + n_coordset, + n_fields, + n_matset, + n_options_copy, + n_newTopo, + n_newCoordset, + n_newFields, + n_newMatset); +#endif + } + +#if defined(AXOM_EQUIZ_SPLIT_PROCESSING) + /*! + * \brief Adds original ids field to supplied fields node. + * + * \param n_field The new field node. + * \param topoName The topology name for the field. + * \param association The field association. + * \param nvalues The number of nodes in the field. + * + * \note This field is added to the mesh before feeding it through MIR so we will have an idea + * of which nodes are original nodes in the output. Blended nodes may not have good values + * but there is a mask field that can identify those nodes. + */ + void addOriginal(conduit::Node &n_field, + const std::string &topoName, + const std::string &association, + axom::IndexType nvalues) const + { + AXOM_ANNOTATE_SCOPE("addOriginal"); + namespace bputils = axom::mir::utilities::blueprint; + bputils::ConduitAllocateThroughAxom c2a; + + // Add a new field for the original ids. + n_field["topology"] = topoName; + n_field["association"] = association; + n_field["values"].set_allocator(c2a.getConduitAllocatorID()); + n_field["values"].set( + conduit::DataType(bputils::cpp2conduit::id, nvalues)); + auto view = bputils::make_array_view(n_field["values"]); + axom::for_all( + nvalues, + AXOM_LAMBDA(axom::IndexType index) { + view[index] = static_cast(index); + }); + } + + /*! + * \brief Take the mesh in n_root and extract the zones identified by the + * \a cleanZones array and store the results into the \a n_cleanOutput + * node. + * + * \param n_root The input mesh from which zones are being extracted. + * \param topoName The name of the topology. + * \param cleanZones An array of clean zone ids. + * \param[out] n_cleanOutput The node that will contain the clean mesh output. + * + * \return The number of nodes in the clean mesh output. + */ + void makeCleanOutput(const conduit::Node &n_root, + const std::string &topoName, + const axom::ArrayView &cleanZones, + conduit::Node &n_cleanOutput) const + { + AXOM_ANNOTATE_SCOPE("makeCleanOutput"); + namespace bputils = axom::mir::utilities::blueprint; + + // Make the clean mesh. Set compact=0 so it does not change the number of nodes. + bputils::ExtractZonesAndMatset + ez(m_topologyView, m_coordsetView, m_matsetView); + conduit::Node n_ezopts; + n_ezopts["topology"] = topoName; + n_ezopts["compact"] = 0; + ez.execute(cleanZones, n_root, n_ezopts, n_cleanOutput); + #if defined(AXOM_EQUIZ_DEBUG) + AXOM_ANNOTATE_BEGIN("saveClean"); + conduit::relay::io::blueprint::save_mesh(n_cleanOutput, "clean", "hdf5"); + AXOM_ANNOTATE_END("saveClean"); + #endif + } + + /*! + * \brief Create node map and node slice arrays for the MIR output that help + * merge it back with the clean output. + * + * \param n_newFields The fields from the MIR output. + * \param[out] nodeMap An array used to map node ids from the MIR output to their node ids in the merged mesh. + * \param[out] nodeSlice An array that identifies new blended node ids in the MIR output so they can be appended into coordsets and fields during merge. + */ + void createNodeMapAndSlice(conduit::Node &n_newFields, + axom::Array &nodeMap, + axom::Array &nodeSlice) const + { + namespace bputils = axom::mir::utilities::blueprint; + AXOM_ANNOTATE_SCOPE("createNodeMapAndSlice"); + SLIC_ASSERT(n_newFields.has_child(originalNodesFieldName())); + SLIC_ASSERT(n_newFields.has_child(newNodesFieldName())); + + const axom::IndexType numCleanNodes = m_coordsetView.numberOfNodes(); + + // These are the original node ids. + const conduit::Node &n_output_orig_nodes = + n_newFields[originalNodesFieldName() + "/values"]; + auto numOutputNodes = n_output_orig_nodes.dtype().number_of_elements(); + auto outputOrigNodesView = + bputils::make_array_view(n_output_orig_nodes); + + // __equiz_new_nodes is the int mask field that identifies new nodes created from blending. + const conduit::Node &n_new_nodes_values = + n_newFields[newNodesFieldName() + "/values"]; + const auto maskView = bputils::make_array_view(n_new_nodes_values); + + // Count new nodes created from blending. + const int allocatorID = axom::execution_space::allocatorID(); + axom::Array maskOffset(numOutputNodes, numOutputNodes, allocatorID); + auto maskOffsetsView = maskOffset.view(); + RAJA::ReduceSum mask_reduce(0); + axom::for_all( + numOutputNodes, + AXOM_LAMBDA(axom::IndexType index) { mask_reduce += maskView[index]; }); + const auto numNewNodes = mask_reduce.get(); + + // Make offsets. + axom::exclusive_scan(maskView, maskOffsetsView); + + // Make a list of indices that we need to slice out of the node arrays. + nodeSlice = + axom::Array(numNewNodes, numNewNodes, allocatorID); + auto nodeSliceView = nodeSlice.view(); + axom::for_all( + numOutputNodes, + AXOM_LAMBDA(axom::IndexType index) { + if(maskView[index] > 0) + { + nodeSliceView[maskOffsetsView[index]] = index; + } + }); + + // Make a node map for mapping mixed connectivity into combined node numbering. + nodeMap = + axom::Array(numOutputNodes, numOutputNodes, allocatorID); + auto nodeMapView = nodeMap.view(); + axom::for_all( + numOutputNodes, + AXOM_LAMBDA(axom::IndexType index) { + if(maskView[index] == 0) + { + nodeMapView[index] = + static_cast(outputOrigNodesView[index]); + } + else + { + nodeMapView[index] = numCleanNodes + maskOffsetsView[index]; + } + }); + + // Remove fields that are no longer needed. + n_newFields.remove(originalNodesFieldName()); + n_newFields.remove(newNodesFieldName()); + } +#endif + + /*! + * \brief Perform material interface reconstruction on a single domain. + * + * \param[in] n_topo The Conduit node containing the topology that will be used for MIR. + * \param[in] n_coordset The Conduit node containing the coordset. + * \param[in] n_fields The Conduit node containing the fields. + * \param[in] n_matset The Conduit node containing the matset. + * \param[in] n_options The Conduit node containing the options that help govern MIR execution. + * + * \param[out] n_newTopo A node that will contain the new clipped topology. + * \param[out] n_newCoordset A node that will contain the new coordset for the clipped topology. + * \param[out] n_newFields A node that will contain the new fields for the clipped topology. + * \param[out] n_newMatset A Conduit node that will contain the new matset. + * + */ + void processMixedZones(const conduit::Node &n_topo, + const conduit::Node &n_coordset, + const conduit::Node &n_fields, + const conduit::Node &n_matset, + conduit::Node &n_options, + conduit::Node &n_newTopo, + conduit::Node &n_newCoordset, + conduit::Node &n_newFields, + conduit::Node &n_newMatset) const + { + AXOM_ANNOTATE_SCOPE("processMixedZones"); + + // Make some nodes that will contain the inputs to subsequent iterations. + // Store them under a single node so the nodes will have names. + conduit::Node n_Input; + conduit::Node &n_InputTopo = n_Input[n_topo.path()]; + conduit::Node &n_InputCoordset = n_Input[n_coordset.path()]; + conduit::Node &n_InputFields = n_Input[n_fields.path()]; + + // Get the materials from the matset and determine which of them are clean/mixed. + axom::mir::views::MaterialInformation allMats, cleanMats, mixedMats; + classifyMaterials(n_matset, allMats, cleanMats, mixedMats); + + //-------------------------------------------------------------------------- + // + // Make node-centered VF fields and add various working fields. + // + //-------------------------------------------------------------------------- + n_InputFields.reset(); + for(conduit::index_t i = 0; i < n_fields.number_of_children(); i++) + { + n_InputFields[n_fields[i].name()].set_external(n_fields[i]); + } + makeNodeCenteredVFs(n_topo, n_coordset, n_InputFields, mixedMats); + makeWorkingFields(n_topo, n_InputFields, cleanMats, mixedMats); + + //-------------------------------------------------------------------------- + // + // Iterate over mixed materials. + // + //-------------------------------------------------------------------------- + constexpr int first = 0; + for(size_t i = first; i < mixedMats.size(); i++) + { + if(i == first) + { + // The first time through, we can use the supplied views. + iteration(i, + m_topologyView, + m_coordsetView, + + allMats, + mixedMats[i], + + n_topo, + n_coordset, + n_InputFields, + + n_options, + + n_newTopo, + n_newCoordset, + n_newFields); + + // In later iterations, we do not want to pass selectedZones through + // since they are only valid on the current input topology. Also, if they + // were passed then the new topology only has those selected zones. + if(n_options.has_child("selectedZones")) + { + n_options.remove("selectedZones"); + } + } + else + { + // Clear the inputs from the last iteration. + n_InputTopo.reset(); + n_InputCoordset.reset(); + n_InputFields.reset(); + + // Move the outputs of the last iteration to the inputs of this iteration. + n_InputTopo.move(n_newTopo); + n_InputCoordset.move(n_newCoordset); + n_InputFields.move(n_newFields); + + // Create an appropriate coordset view. + using CSDataType = typename CoordsetView::value_type; + auto coordsetView = axom::mir::views:: + make_explicit_coordset::view( + n_InputCoordset); + + using ConnectivityType = typename TopologyView::ConnectivityType; + // Dispatch to an appropriate topo view, taking into account the connectivity + // type and the possible shapes that would be supported for the input topology. + views::typed_dispatch_unstructured_topology< + ConnectivityType, + views::view_traits::selected_shapes()>( + n_InputTopo, + [&](const auto &AXOM_UNUSED_PARAM(shape), auto topologyView) { + // Do the next iteration (uses new topologyView type). + iteration(i, + topologyView, + coordsetView, + + allMats, + mixedMats[i], + + n_InputTopo, + n_InputCoordset, + n_InputFields, + + n_options, + + n_newTopo, + n_newCoordset, + n_newFields); + }); + } + } + + // Build the new matset. + buildNewMatset(n_matset, n_newFields, n_newMatset); + + // Cleanup. + { + AXOM_ANNOTATE_SCOPE("cleanup"); + for(const auto &mat : allMats) + { + const std::string nodalMatName(nodalFieldName(mat.number)); + if(n_newFields.has_child(nodalMatName)) + { + n_newFields.remove(nodalMatName); + } +#if defined(AXOM_EQUIZ_DEBUG) + const std::string zonalMatName(zonalFieldName(mat.number)); + if(n_newFields.has_child(zonalMatName)) + { + n_newFields.remove(zonalMatName); + } +#endif + } + n_newFields.remove(zonalMaterialIDName()); + } + +#if defined(AXOM_EQUIZ_DEBUG) + //-------------------------------------------------------------------------- + // + // Save the MIR output. + // + //-------------------------------------------------------------------------- + conduit::Node n_output; + n_output[n_newTopo.path()].set_external(n_newTopo); + n_output[n_newCoordset.path()].set_external(n_newCoordset); + n_output[n_newFields.path()].set_external(n_newFields); + n_output[n_newMatset.path()].set_external(n_newMatset); + conduit::relay::io::blueprint::save_mesh(n_output, + "debug_equiz_output", + "hdf5"); + //printNode(n_output); +#endif + } + + /*! + * \brief Examine the materials and determine which are clean/mixed. + * + * \param n_matset A Conduit node containing the matset. + * \param[out] allMats A vector of all of the materials. + * \param[out] cleanMats A vector of the clean materials. + * \param[out] mixedMats A vector of the mixed materials. + */ + void classifyMaterials(const conduit::Node &n_matset, + axom::mir::views::MaterialInformation &allMats, + axom::mir::views::MaterialInformation &cleanMats, + axom::mir::views::MaterialInformation &mixedMats) const + { + AXOM_ANNOTATE_SCOPE("classifyMaterials"); + + cleanMats.clear(); + mixedMats.clear(); + allMats = axom::mir::views::materials(n_matset); + + // TODO: actually determine which materials are clean/mixed. It's probably + // best to ask the matsetView since it takes some work to determine + // this. + + mixedMats = allMats; + } + + /*! + * \brief Return the name of the zonal material field for a given matId. + * \return The name of the zonal material field. + */ + std::string zonalFieldName(int matId) const + { + std::stringstream ss; + ss << "__equiz_zonal_volume_fraction_" << matId; + return ss.str(); + } + + /*! + * \brief Return the name of the nodal material field for a given matId. + * \return The name of the nodal material field. + */ + std::string nodalFieldName(int matId) const + { + std::stringstream ss; + ss << "__equiz_nodal_volume_fraction_" << matId; + return ss.str(); + } + + /*! + * \brief Return the name of the zonal material id field. + * \return The name of the zonal material id field. + */ + std::string zonalMaterialIDName() const { return "__equiz_zonalMaterialID"; } + + /*! + * \brief Return the name of the original nodes field. + * \return The name of the original nodes field. + */ + std::string originalNodesFieldName() const { return "__equiz_original_node"; } + + /*! + * \brief Return the name of the new nodes field that identifies blended nodes in the MIR output. + * \return The name of the new nodes field. + */ + std::string newNodesFieldName() const { return "__equiz_new_nodes"; } + + /*! + * \brief Makes node-cenetered volume fractions for the materials in the matset + * and attaches them as fields. + * + * \param n_topo A Conduit node containing the input topology. + * \param n_coordset A Conduit node containin the input coordset. + * \param[inout] A Conduit node where the new fields will be added. + * \param mixedMats A vector of mixed materials. + */ + void makeNodeCenteredVFs(const conduit::Node &n_topo, + const conduit::Node &n_coordset, + conduit::Node &n_fields, + const axom::mir::views::MaterialInformation &mixedMats) const + { + AXOM_ANNOTATE_SCOPE("makeNodeCenteredVFs"); + + namespace bputils = axom::mir::utilities::blueprint; + // Make a node to zone relation so we know for each node, which zones it touches. + conduit::Node relation; + { + AXOM_ANNOTATE_SCOPE("relation"); + bputils::NodeToZoneRelationBuilder rb; + rb.execute(n_topo, n_coordset, relation); + //printNode(relation); + //std::cout.flush(); + } + + // Get the ID of a Conduit allocator that will allocate through Axom with device allocator allocatorID. + bputils::ConduitAllocateThroughAxom c2a; + + // Make nodal VFs for each mixed material. + const auto nzones = m_topologyView.numberOfZones(); + const auto nnodes = m_coordsetView.numberOfNodes(); + { + AXOM_ANNOTATE_SCOPE("zonal"); + for(const auto &mat : mixedMats) + { + const int matNumber = mat.number; + const std::string zonalName = zonalFieldName(matNumber); + conduit::Node &n_zonalField = n_fields[zonalName]; + n_zonalField["topology"] = n_topo.name(); + n_zonalField["association"] = "element"; + n_zonalField["values"].set_allocator(c2a.getConduitAllocatorID()); + n_zonalField["values"].set( + conduit::DataType(bputils::cpp2conduit::id, nzones)); + auto zonalFieldView = + bputils::make_array_view(n_zonalField["values"]); + + // Fill the zonal field from the matset. + MatsetView deviceMatsetView(m_matsetView); + axom::for_all( + m_topologyView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + typename MatsetView::FloatType vf {}; + deviceMatsetView.zoneContainsMaterial(zoneIndex, matNumber, vf); + zonalFieldView[zoneIndex] = static_cast(vf); + }); + } + } + + { + AXOM_ANNOTATE_SCOPE("recenter"); + for(const auto &mat : mixedMats) + { + const int matNumber = mat.number; + const std::string zonalName = zonalFieldName(matNumber); + conduit::Node &n_zonalField = n_fields[zonalName]; + + // Make a nodal field for the current material by recentering. + const std::string nodalName = nodalFieldName(matNumber); + conduit::Node &n_nodalField = n_fields[nodalName]; + n_nodalField["topology"] = n_topo.name(); + n_nodalField["association"] = "vertex"; + n_nodalField["values"].set_allocator(c2a.getConduitAllocatorID()); + n_nodalField["values"].set( + conduit::DataType(bputils::cpp2conduit::id, nnodes)); + bputils::RecenterField z2n; + z2n.execute(n_zonalField, relation, n_nodalField); + +#if !defined(AXOM_EQUIZ_DEBUG) + // Remove the zonal field that we don't normally need (unless we're debugging). + n_fields.remove(zonalName); +#endif + } + } + } + + /*! + * \brief Set up the "working fields", mainly a zonalMaterialID that includes + * the contributions from the clean materials and the first mixed material. + * + * \param n_topo A Conduit node containing the input topology pre-MIR. + * \param n_fields A Conduit node containing the fields pre-MIR. + * \param cleanMats A vector of clean materials. + * \param mixedMats A vector of mixed materials. + */ + void makeWorkingFields( + const conduit::Node &n_topo, + conduit::Node &n_fields, + const axom::mir::views::MaterialInformation &cleanMats, + const axom::mir::views::MaterialInformation &AXOM_UNUSED_PARAM(mixedMats)) const + { + namespace bputils = axom::mir::utilities::blueprint; + AXOM_ANNOTATE_SCOPE("makeWorkingFields"); + + // Get the ID of a Conduit allocator that will allocate through Axom with device allocator allocatorID. + bputils::ConduitAllocateThroughAxom c2a; + + const auto nzones = m_topologyView.numberOfZones(); + + // Make the zonal id field. + conduit::Node &n_zonalIDField = n_fields[zonalMaterialIDName()]; + n_zonalIDField["topology"] = n_topo.name(); + n_zonalIDField["association"] = "element"; + n_zonalIDField["values"].set_allocator(c2a.getConduitAllocatorID()); + n_zonalIDField["values"].set( + conduit::DataType(bputils::cpp2conduit::id, nzones)); + auto zonalIDFieldView = + bputils::make_array_view(n_zonalIDField["values"]); + + // Fill all zones with NULL_MATERIAL. + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType nodeIndex) { + zonalIDFieldView[nodeIndex] = NULL_MATERIAL; + }); + + // Fill in the clean zones. + using FloatType = typename MatsetView::FloatType; + MatsetView deviceMatsetView(m_matsetView); + for(const auto &mat : cleanMats) + { + const int matNumber = mat.number; + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + FloatType vf {}; + if(deviceMatsetView.zoneContainsMaterial(zoneIndex, matNumber, vf)) + { + zonalIDFieldView[zoneIndex] = matNumber; + } + }); + } + } + + /*! + * \brief Perform one iteration of material clipping. + * + * \tparam ITopologyView The topology view type for the intermediate topology. + * \tparam ICoordsetView The topology view type for the intermediate coordset. + * + * \param iter The iteration number. + * \param topoView The topology view for the intermediate input topology. + * \param coordsetView The coordset view for the intermediate input coordset. + * \param allMats A vector of Material information (all materials). + * \param currentMat A Material object for the current material. + * \param n_topo A Conduit node containing the intermediate input topology. + * \param n_fields A Conduit node containing the intermediate input fields. + * \param n_options MIR options. + * \param n_newTopo[out] A Conduit node to contain the new topology. + * \param n_newCoordset[out] A Conduit node to contain the new coordset. + * \param n_newFields[out] A Conduit node to contain the new fields. + * + * \note This algorithm uses a ClipField with a MaterialIntersector that gives + * it the ability to access nodal volume fraction fields and make intersection + * decisions with that data. + */ + template + void iteration(int iter, + const ITopologyView &topoView, + const ICoordsetView &coordsetView, + + const axom::mir::views::MaterialInformation &allMats, + const axom::mir::views::Material ¤tMat, + + const conduit::Node &n_topo, + const conduit::Node &n_coordset, + conduit::Node &n_fields, + + const conduit::Node &n_options, + + conduit::Node &n_newTopo, + conduit::Node &n_newCoordset, + conduit::Node &n_newFields) const + { + namespace bputils = axom::mir::utilities::blueprint; + namespace bpmeshutils = conduit::blueprint::mesh::utils; + AXOM_ANNOTATE_SCOPE(axom::fmt::format("iteration {}", iter)); + + const std::string colorField("__equiz__colors"); + +#if defined(AXOM_EQUIZ_DEBUG) + //-------------------------------------------------------------------------- + // + // Save the iteration inputs. + // + //-------------------------------------------------------------------------- + { + AXOM_ANNOTATE_SCOPE("Saving input"); + conduit::Node n_mesh_input; + n_mesh_input[n_topo.path()].set_external(n_topo); + n_mesh_input[n_coordset.path()].set_external(n_coordset); + n_mesh_input[n_fields.path()].set_external(n_fields); + + // save + std::stringstream ss1; + ss1 << "debug_equiz_input_iter." << iter; + conduit::relay::io::blueprint::save_mesh(n_mesh_input, ss1.str(), "hdf5"); + } +#else + AXOM_UNUSED_VAR(iter); +#endif + + //-------------------------------------------------------------------------- + // + // Make material intersector. + // + //-------------------------------------------------------------------------- + using ConnectivityType = typename ITopologyView::ConnectivityType; + using IntersectorType = + internal::MaterialIntersector; + + IntersectorType intersector; + int allocatorID = axom::execution_space::allocatorID(); + const int nmats = static_cast(allMats.size()); + axom::Array matNumberDevice(nmats, nmats, allocatorID), + matIndexDevice(nmats, nmats, allocatorID); + { + AXOM_ANNOTATE_SCOPE("Intersector setup"); + // Populate intersector, including making a number:index map + axom::Array matNumber, matIndex; + for(int index = 0; index < nmats; index++) + { + // Add a matvf view to the intersector. + const std::string matFieldName = nodalFieldName(allMats[index].number); + auto matVFView = bputils::make_array_view( + n_fields.fetch_existing(matFieldName + "/values")); + intersector.addMaterial(matVFView); + + matNumber.push_back(allMats[index].number); + matIndex.push_back(index); + } + // Sort indices by matNumber. + std::sort(matIndex.begin(), matIndex.end(), [&](auto idx1, auto idx2) { + return matNumber[idx1] < matNumber[idx2]; + }); + std::sort(matNumber.begin(), matNumber.end()); + // Get the current material's index in the number:index map. + int currentMatIndex = 0; + for(axom::IndexType i = 0; i < matNumber.size(); i++) + { + if(matNumber[i] == currentMat.number) + { + currentMatIndex = matIndex[i]; + break; + } + } + + // Store the number:index map into the intersector. The number:index map lets us + // ask for the field index for a material number, allowing scattered material + // numbers to be used in the matset. + axom::copy(matNumberDevice.data(), matNumber.data(), sizeof(int) * nmats); + axom::copy(matIndexDevice.data(), matIndex.data(), sizeof(int) * nmats); + intersector.setMaterialNumbers(matNumberDevice.view()); + intersector.setMaterialIndices(matIndexDevice.view()); + intersector.setCurrentMaterial(currentMat.number, currentMatIndex); + + // Store the current zone material ids and current material number into the intersector. + intersector.setZoneMaterialID(bputils::make_array_view( + n_fields.fetch_existing(zonalMaterialIDName() + "/values"))); + } + + //-------------------------------------------------------------------------- + // + // Make clip options + // + //-------------------------------------------------------------------------- + conduit::Node options; + options["inside"] = 1; + options["outside"] = 1; + options["colorField"] = colorField; + if(n_options.has_child("selectedZones")) + { + // Pass selectedZones along in the clip options, if present. + options["selectedZones"].set_external( + n_options.fetch_existing("selectedZones")); + } + if(n_options.has_child("fields")) + { + // Pass along fields, if present. + options["fields"].set_external(n_options.fetch_existing("fields")); + } + if(n_options.has_child("newNodesField")) + { + // Pass along newNodesField, if present. + options["newNodesField"] = + n_options.fetch_existing("newNodesField").as_string(); + } + options["topology"] = n_options["topology"]; + + //-------------------------------------------------------------------------- + // + // Clip the topology using the material intersector. + // + //-------------------------------------------------------------------------- + { + using ClipperType = + axom::mir::clipping::ClipField; + ClipperType clipper(topoView, coordsetView, intersector); + clipper.execute(n_topo, + n_coordset, + n_fields, + options, + n_newTopo, + n_newCoordset, + n_newFields); + } + + //-------------------------------------------------------------------------- + // + // Update zoneMaterialID based on color field. + // + //-------------------------------------------------------------------------- + { + AXOM_ANNOTATE_SCOPE("Update zonalMaterialID"); + + const auto colorView = bputils::make_array_view( + n_newFields.fetch_existing(colorField + "/values")); + const auto nzonesNew = colorView.size(); + + // Get zonalMaterialID field so we can make adjustments. + conduit::Node &n_zonalMaterialID = + n_newFields.fetch_existing(zonalMaterialIDName() + "/values"); + auto zonalMaterialID = + bputils::make_array_view(n_zonalMaterialID); + const int currentMatNumber = currentMat.number; + axom::for_all( + nzonesNew, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + // Color the part we want with the current material. + if(colorView[zoneIndex] == 1) + { + zonalMaterialID[zoneIndex] = currentMatNumber; + } + }); + } + +#if defined(AXOM_EQUIZ_DEBUG) + //-------------------------------------------------------------------------- + // + // Save the clip results. + // + //-------------------------------------------------------------------------- + { + AXOM_ANNOTATE_SCOPE("Saving output"); + conduit::Node mesh; + mesh[n_newTopo.path()].set_external(n_newTopo); + mesh[n_newCoordset.path()].set_external(n_newCoordset); + mesh[n_newFields.path()].set_external(n_newFields); + + // save + std::stringstream ss; + ss << "debug_equiz_output_iter." << iter; + conduit::relay::io::blueprint::save_mesh(mesh, ss.str(), "hdf5"); + } +#endif + + // We do not want the color field to survive into the next iteration. + n_newFields.remove(colorField); + } + + /*! + * \brief Build a new matset with only clean zones, representing the MIR output. + * + * \param n_matset n_matset The Conduit node that contains the input matset. + * \param[inout] n_newFields The Conduit node that contains the fields for the MIR output. + * \param[out] n_newMatset The node that contains the new matset. + */ + void buildNewMatset(const conduit::Node &n_matset, + conduit::Node &n_newFields, + conduit::Node &n_newMatset) const + { + namespace bputils = axom::mir::utilities::blueprint; + AXOM_ANNOTATE_SCOPE("buildNewMatset"); + + // Get the zonalMaterialID field that has our new material ids. + conduit::Node &n_zonalMaterialID = + n_newFields[zonalMaterialIDName() + "/values"]; + auto zonalMaterialID = + bputils::make_array_view(n_zonalMaterialID); + const auto nzones = n_zonalMaterialID.dtype().number_of_elements(); + + // Copy some information from the old matset to the new one. + if(n_matset.has_child("topology")) + { + n_newMatset["topology"].set(n_matset.fetch_existing("topology")); + } + if(n_matset.has_child("material_map")) + { + n_newMatset["material_map"].set(n_matset.fetch_existing("material_map")); + } + + // Make new nodes in the matset. + conduit::Node &n_material_ids = n_newMatset["material_ids"]; + conduit::Node &n_volume_fractions = n_newMatset["volume_fractions"]; + conduit::Node &n_sizes = n_newMatset["sizes"]; + conduit::Node &n_offsets = n_newMatset["offsets"]; + conduit::Node &n_indices = n_newMatset["indices"]; + + bputils::ConduitAllocateThroughAxom c2a; + n_material_ids.set_allocator(c2a.getConduitAllocatorID()); + n_volume_fractions.set_allocator(c2a.getConduitAllocatorID()); + n_sizes.set_allocator(c2a.getConduitAllocatorID()); + n_offsets.set_allocator(c2a.getConduitAllocatorID()); + n_indices.set_allocator(c2a.getConduitAllocatorID()); + + // We'll store the output matset in the same types as the input matset. + using MIntType = typename MatsetView::IndexType; + using MFloatType = typename MatsetView::FloatType; + n_material_ids.set( + conduit::DataType(bputils::cpp2conduit::id, nzones)); + n_volume_fractions.set( + conduit::DataType(bputils::cpp2conduit::id, nzones)); + n_sizes.set(conduit::DataType(bputils::cpp2conduit::id, nzones)); + n_offsets.set(conduit::DataType(bputils::cpp2conduit::id, nzones)); + n_indices.set(conduit::DataType(bputils::cpp2conduit::id, nzones)); + + auto material_ids_view = bputils::make_array_view(n_material_ids); + auto volume_fractions_view = + bputils::make_array_view(n_volume_fractions); + auto sizes_view = bputils::make_array_view(n_sizes); + auto offsets_view = bputils::make_array_view(n_offsets); + auto indices_view = bputils::make_array_view(n_indices); + + // Fill in the new matset data arrays. + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + material_ids_view[zoneIndex] = + static_cast(zonalMaterialID[zoneIndex]); + volume_fractions_view[zoneIndex] = 1; + sizes_view[zoneIndex] = 1; + offsets_view[zoneIndex] = static_cast(zoneIndex); + indices_view[zoneIndex] = static_cast(zoneIndex); + }); + } + +private: + TopologyView m_topologyView; + CoordsetView m_coordsetView; + MatsetView m_matsetView; +}; + +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/MIRAlgorithm.cpp b/src/axom/mir/MIRAlgorithm.cpp new file mode 100644 index 0000000000..f85a5a2673 --- /dev/null +++ b/src/axom/mir/MIRAlgorithm.cpp @@ -0,0 +1,117 @@ +// 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/mir/MIRAlgorithm.hpp" +#include "axom/mir/MIROptions.hpp" +#include "axom/slic.hpp" + +#include +#include + +namespace axom +{ +namespace mir +{ +void MIRAlgorithm::execute(const conduit::Node &n_input, + const conduit::Node &n_options, + conduit::Node &n_output) +{ + const auto domains = conduit::blueprint::mesh::domains(n_input); + if(domains.size() > 1) + { + // Handle multiple domains + for(const auto &dom_ptr : domains) + { + const conduit::Node &n_domain = *dom_ptr; + conduit::Node &n_newDomain = n_output.append(); + + executeSetup(n_domain, n_options, n_newDomain); + } + } + else if(domains.size() > 0) + { + // Handle single domain + const conduit::Node &n_domain = *domains[0]; + executeSetup(n_domain, n_options, n_output); + } +} + +void MIRAlgorithm::executeSetup(const conduit::Node &n_domain, + const conduit::Node &n_options, + conduit::Node &n_newDomain) +{ + MIROptions options(n_options); + + // Get the matset that we'll operate on. + const std::string matset = options.matset(); + + // Which topology is that matset defined on? + const conduit::Node &n_matsets = n_domain.fetch_existing("matsets"); + const conduit::Node &n_matset = n_matsets.fetch_existing(matset); + const conduit::Node *n_topo = + conduit::blueprint::mesh::utils::find_reference_node(n_matset, "topology"); + SLIC_ASSERT(n_topo != nullptr); + + // Which coordset is used by that topology? + const conduit::Node *n_coordset = + conduit::blueprint::mesh::utils::find_reference_node(*n_topo, "coordset"); + SLIC_ASSERT(n_coordset != nullptr); + + // Get the names of the output items. + const std::string newTopoName = options.topologyName(n_topo->name()); + const std::string newCoordsetName = options.coordsetName(n_coordset->name()); + const std::string newMatsetName = options.matsetName(matset); + + // Make some new nodes in the output. + conduit::Node &newCoordset = n_newDomain["coordsets/" + newCoordsetName]; + conduit::Node &newTopo = n_newDomain["topologies/" + newTopoName]; + newTopo["coordset"] = newCoordsetName; + conduit::Node &newMatset = n_newDomain["matsets/" + newMatsetName]; + newMatset["topology"] = newTopoName; + + // Execute the algorithm on the domain. + if(n_domain.has_path("state")) + copyState(n_domain["state"], n_newDomain["state"]); + if(n_domain.has_path("fields")) + { + conduit::Node &newFields = n_newDomain["fields"]; + executeDomain(*n_topo, + *n_coordset, + n_domain["fields"], + n_matset, + n_options, + newTopo, + newCoordset, + newFields, + newMatset); + } + else + { + // There are no input fields, but make sure n_fields has a name. + conduit::Node tmp; + conduit::Node &n_fields = tmp["fields"]; + // MIR is likely to output some created fields. + conduit::Node &newFields = n_newDomain["fields"]; + executeDomain(*n_topo, + *n_coordset, + n_fields, + n_matset, + n_options, + newTopo, + newCoordset, + newFields, + newMatset); + } +} + +void MIRAlgorithm::copyState(const conduit::Node &srcState, + conduit::Node &destState) const +{ + for(conduit::index_t i = 0; i < srcState.number_of_children(); i++) + destState[srcState[i].name()].set(srcState[i]); +} + +} // namespace mir +} // namespace axom diff --git a/src/axom/mir/MIRAlgorithm.hpp b/src/axom/mir/MIRAlgorithm.hpp new file mode 100644 index 0000000000..334d3e865e --- /dev/null +++ b/src/axom/mir/MIRAlgorithm.hpp @@ -0,0 +1,117 @@ +// 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) + +#ifndef AXOM_MIR_ALGORITHM_HPP_ +#define AXOM_MIR_ALGORITHM_HPP_ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/core/ArrayView.hpp" + +#include + +#include +#include + +namespace axom +{ +namespace mir +{ +/*! + \brief Base class for Material Interface Reconstruction (MIR) algorithms. + */ +class MIRAlgorithm +{ +public: + MIRAlgorithm() = default; + virtual ~MIRAlgorithm() = default; + + /*! + \brief Perform material interface reconstruction on the meshes supplied in the + root node. Root can either be a mesh domain or a node that contains multiple + domains. + + \param[in] n_input The root node that contains either a mesh or list of mesh + domains that contain a topology and matset to be used for MIR. + \param[in] n_options A node that contains options that help govern MIR execution. + +\code{.yaml} +options: + topology: main + matset: matset + new_topology: mirtopo + new_coordset: mircoords + new_matset: cleanmat + fields: + - temperature + - pressure + selectedZones: [0,1,6,9] + mapping: 0 +\endcode + The "topology" option specifies which topology we'll reconstruct. It must have an associated matset. + "new_topology" is the name of the topology that will be created in the output node. + "new_coordset" is the name of the new coordset that will be created in the output node. If it is not provided then the name of the topology's coordset will be used. + "new_matset" is the name of the new matset that will be created in the output node. If it is not provided then the name of the topology's matset will be used. + "fields" is the name of the fields to map to the new topology. If fields is specified but empty, no fields will be mapped. If fields is not present then all fields will be mapped. + "zones" is a list of zone indices from the topology that need to be reconstructed. If not present then all zones will be considered. + "mapping" indicates whether we should include an original_element_numbers field on the new topology to indicate where each new zone came from in the original topology. + + \param[out] n_output A node that will contain the new entities. + + */ + virtual void execute(const conduit::Node &n_input, + const conduit::Node &n_options, + conduit::Node &n_output); + +protected: + /*! + * \brief Set up the new domain from the old one and invoke executeDomain. + * + * \param n_domain The input domain. + * \param n_options The MIR options. + * \param n_newDomain The output domain. + */ + void executeSetup(const conduit::Node &n_domain, + const conduit::Node &n_options, + conduit::Node &n_newDomain); + + /*! + * \brief Perform material interface reconstruction on a single domain. Derived classes + * must implement this method and any device-specific coding gets handled under it. + * + * \param[in] n_topo The Conduit node containing the topology that will be used for MIR. + * \param[in] n_coordset The Conduit node containing the coordset. + * \param[in] n_fields The Conduit node containing the fields. + * \param[in] n_matset The Conduit node containing the matset. + * \param[in] n_options The Conduit node containing the options that help govern MIR execution. + * + * \param[out] n_newTopo A node that will contain the new clipped topology. + * \param[out] n_newCoordset A node that will contain the new coordset for the clipped topology. + * \param[out] n_newFields A node that will contain the new fields for the clipped topology. + * \param[out] n_newMatset A Conduit node that will contain the new matset. + * + */ + virtual void executeDomain(const conduit::Node &n_topo, + const conduit::Node &n_coordset, + const conduit::Node &n_fields, + const conduit::Node &n_matset, + const conduit::Node &n_options, + conduit::Node &n_newTopo, + conduit::Node &n_newCoordset, + conduit::Node &n_newFields, + conduit::Node &n_newMatset) = 0; + + /*! + * \brief Copy state from the src domain to the destination domain. + * \param srcState The node that contains the state in the source domain. + * \param destState The node that contains the state in the destination domain. + */ + void copyState(const conduit::Node &srcState, conduit::Node &destState) const; +}; + +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/MIROptions.hpp b/src/axom/mir/MIROptions.hpp new file mode 100644 index 0000000000..39aec62504 --- /dev/null +++ b/src/axom/mir/MIROptions.hpp @@ -0,0 +1,58 @@ +// 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) +#ifndef AXOM_MIR_MIROPTIONS_HPP_ +#define AXOM_MIR_MIROPTIONS_HPP_ + +#include "axom/mir/Options.hpp" + +namespace axom +{ +namespace mir +{ +/** + * \brief This class provides a kind of schema over the MIR options, as well + * as default values, and some utilities functions. + */ +class MIROptions : public axom::mir::Options +{ +public: + /** + * \brief Constructor + * + * \param options The node that contains the clipping options. + */ + MIROptions(const conduit::Node &options) : axom::mir::Options(options) { } + + /** + * \brief Get the name of the matset on which we'll operate. + * \return The name of the matset. + */ + std::string matset() const + { + return options().fetch_existing("matset").as_string(); + } + + /** + * \brief Return the name of the matset to make in the output. + * \param default_value The name to use if the option is not defined. + * \return The name of the matset to make in the output. + */ + std::string matsetName(const std::string &default_value = std::string()) const + { + std::string name(default_value.empty() ? matset() : default_value); + if(options().has_child("matsetName")) + name = options().fetch_existing("matsetName").as_string(); + return name; + } + +private: + /// Access the base class' options. + const conduit::Node &options() const { return this->m_options; } +}; + +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/MeshTester.cpp b/src/axom/mir/MeshTester.cpp new file mode 100644 index 0000000000..b8d50ec2ad --- /dev/null +++ b/src/axom/mir/MeshTester.cpp @@ -0,0 +1,931 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "MeshTester.hpp" +#include "axom/mir.hpp" + +namespace numerics = axom::numerics; +namespace slam = axom::slam; +namespace bputils = axom::mir::utilities::blueprint; + +namespace axom +{ +namespace mir +{ +//-------------------------------------------------------------------------------- +/*! + * \brief Calculates the percent overlap between the given circle and quad. + * + * \param gridSize The size of the uniform grid which will be sampled over to check for overlap. + * \param circleCenter The center point of the circle. + * \param circleRadius The radius of the circle. + * \param quadP0 The upper left vertex of the quad. + * \param quadP1 The lower left vertex of the quad. + * \param quadP2 The lower right vertex of the quad. + * \param quadP3 The upper right vertex of the quad. + * + * /return The percent value overlap of the circle and the quad between [0, 1]. + */ + +template +static axom::float64 calculatePercentOverlapMonteCarlo(int gridSize, + const PointType& circleCenter, + axom::float64 circleRadius, + const PointType& quadP0, + const PointType& quadP1, + const PointType& quadP2, + const PointType& quadP3) +{ + // Check if any of the quad's corners are within the circle + auto d0Sq = primal::squared_distance(quadP0, circleCenter); + auto d1Sq = primal::squared_distance(quadP1, circleCenter); + auto d2Sq = primal::squared_distance(quadP2, circleCenter); + auto d3Sq = primal::squared_distance(quadP3, circleCenter); + auto dRSq = circleRadius * circleRadius; + + int inFlags = ((d0Sq < dRSq) ? 1 << 0 : 0) + ((d1Sq < dRSq) ? 1 << 1 : 0) + + ((d2Sq < dRSq) ? 1 << 2 : 0) + ((d3Sq < dRSq) ? 1 << 3 : 0); + const int allFlags = 15; + const int noFlags = 0; + + if(inFlags == allFlags) + { + // The entire quad overlaps the circle + return 1.; + } + else if(inFlags == noFlags) + { + return 0.; + } + else + { + // Some of the quad overlaps the circle, so run the Monte Carlo sampling to determine how much + const int numSamples = std::min(gridSize, 20); + float delta_x = axom::utilities::abs(quadP2[0] - quadP1[0]) / + static_cast(numSamples - 1); + float delta_y = axom::utilities::abs(quadP0[1] - quadP1[1]) / + static_cast(numSamples - 1); + int countOverlap = 0; + for(int y = 0; y < numSamples; ++y) + { + for(int x = 0; x < numSamples; ++x) + { + PointType samplePoint({delta_x * x + quadP1[0], delta_y * y + quadP1[1]}); + if(primal::squared_distance(samplePoint, circleCenter) < dRSq) + ++countOverlap; + } + } + return static_cast(countOverlap) / + static_cast(gridSize * gridSize); + } +} + +//-------------------------------------------------------------------------------- +void MeshTester::mesh3x3(conduit::Node& mesh) +{ + // clang-format off + mesh["coordsets/coords/type"] = "explicit"; + mesh["coordsets/coords/values/x"].set(std::vector{{ + 0., 1., 2., 3., 0., 1., 2., 3., 0., 1., 2., 3., 0., 1., 2., 3. + }}); + mesh["coordsets/coords/values/y"].set(std::vector{{ + 0., 0., 0., 0., 1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3. + }}); + + mesh["topologies/mesh/type"] = "unstructured"; + mesh["topologies/mesh/coordset"] = "coords"; + mesh["topologies/mesh/elements/shape"] = "quad"; + mesh["topologies/mesh/elements/connectivity"].set(std::vector{{ + 0,1,5,4, 1,2,6,5, 2,3,7,6, 4,5,9,8, 5,6,10,9, 6,7,11,10, 8,9,13,12, 9,10,14,13, 10,11,15,14 + }}); + mesh["topologies/mesh/elements/sizes"].set(std::vector{{ + 4,4,4,4, 4,4,4,4, 4,4,4,4, 4,4,4,4 + }}); + mesh["topologies/mesh/elements/offsets"].set(std::vector{{ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60 + }}); + // clang-format on +} + +//-------------------------------------------------------------------------------- +void MeshTester::initTestCaseOne(conduit::Node& mesh) +{ + mesh3x3(mesh); + + // clang-format off + mesh["matsets/mat/topology"] = "mesh"; + mesh["matsets/mat/material_map/green"] = 0; + mesh["matsets/mat/material_map/blue"] = 1; + mesh["matsets/mat/material_ids"].set(std::vector{{ + 0, + 0, + 0, + 0, + 0, 1, + 0, 1, + 0, 1, + 1, + 1 + }}); + mesh["matsets/mat/volume_fractions"].set(std::vector{{ + 1., + 1., + 1., + 1., + 0.5, 0.5, + 0.2, 0.8, + 0.2, 0.8, + 1., + 1. + }}); + mesh["matsets/mat/sizes"].set(std::vector{{ + 1, 1, 1, 1, 2, 2, 2, 1, 1 + }}); + mesh["matsets/mat/offsets"].set(std::vector{{ + //0, 1, 2, 3, 5, 7, 9, 10, 11 + 0, 1, 2, 3, 4, 6, 8, 10, 11 + }}); + mesh["matsets/mat/indices"].set(std::vector{{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }}); + // clang-format on +} + +//-------------------------------------------------------------------------------- +void MeshTester::initTestCaseTwo(conduit::Node& mesh) +{ + mesh3x3(mesh); + + // clang-format off + constexpr int BLUE = 0; + constexpr int RED = 1; + constexpr int ORANGE = 2; + mesh["matsets/mat/topology"] = "mesh"; + mesh["matsets/mat/material_map/blue"] = BLUE; + mesh["matsets/mat/material_map/red"] = RED; + mesh["matsets/mat/material_map/orange"] = ORANGE; + mesh["matsets/mat/material_ids"].set(std::vector{{ + BLUE, + BLUE, + BLUE, + BLUE, + BLUE, RED, ORANGE, + BLUE, RED, + BLUE, ORANGE, + RED, ORANGE, + RED + }}); + mesh["matsets/mat/volume_fractions"].set(std::vector{{ + 1., + 1., + 1., + 1., + 0.5, 0.3, 0.2, + 0.2, 0.8, + 0.2, 0.8, + 0.3, 0.7, + 1. + }}); + mesh["matsets/mat/sizes"].set(std::vector{{ + 1, 1, 1, 1, 3, 2, 2, 2, 1 + }}); + mesh["matsets/mat/offsets"].set(std::vector{{ + 0, 1, 2, 3, 4, 7, 9, 11, 13 + }}); + mesh["matsets/mat/indices"].set(std::vector{{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 + }}); + // clang-format on +} + +//-------------------------------------------------------------------------------- +void MeshTester::initTestCaseThree(conduit::Node& mesh) +{ + // clang-format off + mesh["coordsets/coords/type"] = "explicit"; + mesh["coordsets/coords/values/x"].set(std::vector{{ + 1., 0.5, 1.5, 0., 1., 2., + }}); + mesh["coordsets/coords/values/y"].set(std::vector{{ + 2., 1., 1., 0., 0., 0. + }}); + + mesh["topologies/mesh/type"] = "unstructured"; + mesh["topologies/mesh/coordset"] = "coords"; + mesh["topologies/mesh/elements/shape"] = "tri"; + mesh["topologies/mesh/elements/connectivity"].set(std::vector{{ + 0,1,2, 1,3,4, 1,4,2, 2,4,5 + }}); + mesh["topologies/mesh/elements/sizes"].set(std::vector{{3,3,3,3}}); + mesh["topologies/mesh/elements/offsets"].set(std::vector{{0,3,6,9}}); + + constexpr int BLUE = 0; + constexpr int RED = 1; + mesh["matsets/mat/topology"] = "mesh"; + mesh["matsets/mat/material_map/blue"] = BLUE; + mesh["matsets/mat/material_map/red"] = RED; + mesh["matsets/mat/material_ids"].set(std::vector{{ + RED, + BLUE, RED, + BLUE, RED, + BLUE, RED + }}); + mesh["matsets/mat/volume_fractions"].set(std::vector{{ + 1., + 0.5, 0.5, + 0.8, 0.2, + 0.5, 0.5 + }}); + mesh["matsets/mat/sizes"].set(std::vector{{ + 1, 2, 2, 2 + }}); + mesh["matsets/mat/offsets"].set(std::vector{{ + 0, 1, 3, 5 + }}); + mesh["matsets/mat/indices"].set(std::vector{{ + 0, 1, 2, 3, 4, 5, 6 + }}); + // clang-format on +} + +//-------------------------------------------------------------------------------- +template +static void addCircleMaterial(const TopoView& topoView, + const CoordsetView& coordsetView, + conduit::Node& mesh, + const MeshTester::Point2& circleCenter, + axom::float64 circleRadius, + int numSamples) +{ + constexpr int GREEN = 0; + constexpr int BLUE = 1; + + int numElements = topoView.numberOfZones(); + std::vector volFracs[2]; + volFracs[GREEN].resize(numElements); + volFracs[BLUE].resize(numElements); + + // Generate the element volume fractions for the circle + axom::ArrayView greenView(volFracs[GREEN].data(), numElements); + axom::ArrayView blueView(volFracs[BLUE].data(), numElements); + typename CoordsetView::PointType center; + center[0] = circleCenter[0]; + center[1] = circleCenter[1]; + + const TopoView deviceTopologyView(topoView); + axom::for_all( + topoView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zone = deviceTopologyView.zone(zoneIndex); + // NOTE: node ordering shuffled because function takes different node ordering. + auto vf = calculatePercentOverlapMonteCarlo(numSamples, + center, + circleRadius, + coordsetView[zone.getId(3)], + coordsetView[zone.getId(0)], + coordsetView[zone.getId(1)], + coordsetView[zone.getId(2)]); + greenView[zoneIndex] = vf; + blueView[zoneIndex] = 1.0 - vf; + }); + + // Figure out the material buffers from the volume fractions. + std::vector material_ids, sizes, offsets, indices; + std::vector volume_fractions; + for(int i = 0; i < numElements; ++i) + { + int nmats = 0; + offsets.push_back(indices.size()); + if(volFracs[GREEN][i] > 0.) + { + material_ids.push_back(GREEN); + volume_fractions.push_back(volFracs[GREEN][i]); + indices.push_back(indices.size()); + nmats++; + } + if(volFracs[BLUE][i] > 0.) + { + material_ids.push_back(BLUE); + volume_fractions.push_back(volFracs[BLUE][i]); + indices.push_back(indices.size()); + nmats++; + } + sizes.push_back(nmats); + } + + mesh["matsets/mat/topology"] = "mesh"; + mesh["matsets/mat/material_map/green"] = GREEN; + mesh["matsets/mat/material_map/blue"] = BLUE; + mesh["matsets/mat/material_ids"].set(material_ids); + mesh["matsets/mat/volume_fractions"].set(volume_fractions); + mesh["matsets/mat/sizes"].set(sizes); + mesh["matsets/mat/offsets"].set(offsets); + mesh["matsets/mat/indices"].set(indices); +} + +//-------------------------------------------------------------------------------- +void MeshTester::initTestCaseFour(conduit::Node& mesh) +{ + mesh3x3(mesh); + + // Make views + using CoordsetView = axom::mir::views::ExplicitCoordsetView; + CoordsetView coordsetView( + bputils::make_array_view(mesh["coordsets/coords/values/x"]), + bputils::make_array_view(mesh["coordsets/coords/values/y"])); + using TopoView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::QuadShape>; + TopoView topoView(bputils::make_array_view( + mesh["topologies/mesh/elements/connectivity"])); + + // Add material + const Point2 circleCenter({1.5, 1.5}); + const axom::float64 circleRadius = 1.25; + const int numSamples = 100; + addCircleMaterial(topoView, + coordsetView, + mesh, + circleCenter, + circleRadius, + numSamples); +} + +//-------------------------------------------------------------------------------- +void MeshTester::createUniformGridTestCaseMesh(int gridSize, + const MeshTester::Point2& circleCenter, + axom::float64 circleRadius, + conduit::Node& mesh) +{ + // Generate the mesh + generateGrid(gridSize, mesh); + + // Make views + using CoordsetView = axom::mir::views::ExplicitCoordsetView; + CoordsetView coordsetView( + bputils::make_array_view(mesh["coordsets/coords/values/x"]), + bputils::make_array_view(mesh["coordsets/coords/values/y"])); + using TopoView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::QuadShape>; + TopoView topoView(bputils::make_array_view( + mesh["topologies/mesh/elements/connectivity"])); + + // Add material + int numSamples = 100; + addCircleMaterial(topoView, + coordsetView, + mesh, + circleCenter, + circleRadius, + numSamples); +} + +//-------------------------------------------------------------------------------- +void MeshTester::generateGrid(int gridSize, conduit::Node& mesh) +{ + AXOM_ANNOTATE_SCOPE("generateGrid"); + int nx = gridSize + 1; + int ny = gridSize + 1; + int nzones = gridSize * gridSize; + int nnodes = nx * ny; + + std::vector xc, yc; + xc.reserve(nnodes); + yc.reserve(nnodes); + for(int j = 0; j < ny; j++) + { + for(int i = 0; i < nx; i++) + { + xc.push_back(i); + yc.push_back(j); + } + } + + std::vector conn, sizes, offsets; + conn.reserve(nzones * 4); + sizes.reserve(nzones); + offsets.reserve(nzones); + for(int j = 0; j < gridSize; j++) + { + for(int i = 0; i < gridSize; i++) + { + offsets.push_back(offsets.size() * 4); + sizes.push_back(4); + conn.push_back(j * nx + i); + conn.push_back(j * nx + i + 1); + conn.push_back((j + 1) * nx + i + 1); + conn.push_back((j + 1) * nx + i); + } + } + + mesh["coordsets/coords/type"] = "explicit"; + mesh["coordsets/coords/values/x"].set(xc); + mesh["coordsets/coords/values/y"].set(yc); + mesh["topologies/mesh/type"] = "unstructured"; + mesh["topologies/mesh/coordset"] = "coords"; + mesh["topologies/mesh/elements/shape"] = "quad"; + mesh["topologies/mesh/elements/connectivity"].set(conn); + mesh["topologies/mesh/elements/sizes"].set(sizes); + mesh["topologies/mesh/elements/offsets"].set(offsets); +} + +//-------------------------------------------------------------------------------- +static void addMaterial( + axom::IndexType numElements, + int numMaterials, + const std::vector>& materialVolumeFractionsData, + conduit::Node& mesh) +{ + AXOM_ANNOTATE_SCOPE("addMaterial"); + // Figure out the material buffers from the volume fractions. + std::vector material_ids, sizes, offsets, indices; + std::vector volume_fractions; + std::vector sums(numMaterials, 0.); + for(axom::IndexType i = 0; i < numElements; ++i) + { + int nmats = 0; + offsets.push_back(indices.size()); + for(int mat = 0; mat < numMaterials; mat++) + { + if(materialVolumeFractionsData[mat][i] > 0.) + { + material_ids.push_back(mat); + volume_fractions.push_back(materialVolumeFractionsData[mat][i]); + indices.push_back(indices.size()); + nmats++; + + // Keep a total of the VFs for each material. + sums[mat] += materialVolumeFractionsData[mat][i]; + } + } + sizes.push_back(nmats); + } + + // Add the material + mesh["matsets/mat/topology"] = "mesh"; + for(int mat = 0; mat < numMaterials; mat++) + { + if(sums[mat] > 0.) + { + std::stringstream ss; + ss << "matsets/mat/material_map/mat" << mat; + mesh[ss.str()] = mat; + } + } + mesh["matsets/mat/material_ids"].set(material_ids); + mesh["matsets/mat/volume_fractions"].set(volume_fractions); + mesh["matsets/mat/sizes"].set(sizes); + mesh["matsets/mat/offsets"].set(offsets); + mesh["matsets/mat/indices"].set(indices); +} + +//-------------------------------------------------------------------------------- +template +void addConcentricCircleMaterial(const TopoView& topoView, + const CoordsetView& coordsetView, + const MeshTester::Point2& circleCenter, + std::vector& circleRadii, + int numSamples, + conduit::Node& mesh) +{ + AXOM_ANNOTATE_SCOPE("addConcentricCircleMaterial"); + // Generate the element volume fractions with concentric circles + int numMaterials = circleRadii.size() + 1; + int defaultMaterialID = + numMaterials - 1; // default material is always the last index + + // Initialize all material volume fractions to 0 + std::vector> materialVolumeFractionsData( + numMaterials); + constexpr int MAXMATERIALS = 100; + axom::StackArray, MAXMATERIALS> matvfViews; + for(int i = 0; i < numMaterials; ++i) + { + const auto len = topoView.numberOfZones(); + materialVolumeFractionsData[i].resize(len, 0.); + matvfViews[i] = + axom::ArrayView(materialVolumeFractionsData[i].data(), len); + } + // Make a vector of radius^2. + std::vector circleRadii2; + for(const auto& r : circleRadii) circleRadii2.push_back(r * r); + + auto circleRadii2View = + axom::ArrayView(circleRadii2.data(), circleRadii2.size()); + const int numCircles = circleRadii2.size(); + + // Use the uniform sampling method to generate volume fractions for each material + // Note: Assumes that the cell is a parallelogram. This could be modified via biliear interpolation + const TopoView deviceTopologyView(topoView); + axom::for_all( + topoView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType eID) { + const auto zone = deviceTopologyView.zone(eID); + + const auto v0 = coordsetView[zone.getId(0)]; + const auto v1 = coordsetView[zone.getId(1)]; + const auto v2 = coordsetView[zone.getId(2)]; + + // Run the uniform sampling to determine how much of the current cell is composed of each material + float delta_x = + axom::utilities::abs(v1[0] - v0[0]) / (float)(numSamples - 1); + float delta_y = + axom::utilities::abs(v2[1] - v1[1]) / (float)(numSamples - 1); + + // If the corners are all in the same circle then we can skip checking for mix. + int circle = defaultMaterialID; + bool sameCircles = true; + for(int c = 0; c < 4; c++) + { + const auto corner = coordsetView[zone.getId(c)]; + const auto dist2 = primal::squared_distance(corner, circleCenter); + // Check which circle the point is in. + int currentCircle = defaultMaterialID; + for(int cID = 0; cID < numCircles; ++cID) + { + if(dist2 < circleRadii2View[cID]) + { + currentCircle = cID; + break; + } + } + if(c > 0) + { + sameCircles &= circle == currentCircle; + } + circle = currentCircle; + } + + if(sameCircles) + { + // All of the points were found to be in circle. + matvfViews[circle][eID] = 1.; + } + else + { + // There was variation along the edge path so the element is mixed. + for(int y = 0; y < numSamples; ++y) + { + const float yc = static_cast(delta_y * y + v0[1]); + for(int x = 0; x < numSamples; ++x) + { + const float xc = static_cast(delta_x * x + v0[0]); + bool isPointSampled = false; + const auto dist2 = + primal::squared_distance(MeshTester::Point2({xc, yc}), + circleCenter); + for(int cID = 0; cID < numCircles && !isPointSampled; ++cID) + { + if(dist2 < circleRadii2View[cID]) + { + matvfViews[cID][eID] += 1.; + isPointSampled = true; + } + } + if(!isPointSampled) + { + // The point was not within any of the circles, so increment the count for the default material + matvfViews[defaultMaterialID][eID] += 1.; + } + } + } + + // Assign the element volume fractions based on the count of the samples in each circle + const axom::float64 ns2 = + static_cast(numSamples * numSamples); + for(int matID = 0; matID < numMaterials; ++matID) + { + matvfViews[matID][eID] /= ns2; + } + } + }); + + addMaterial(topoView.numberOfZones(), + numMaterials, + materialVolumeFractionsData, + mesh); +} + +//-------------------------------------------------------------------------------- +void MeshTester::initTestCaseFive(int gridSize, int numCircles, conduit::Node& mesh) +{ + // Generate the mesh topology + generateGrid(gridSize, mesh); + + Point2 circleCenter( + {gridSize / 2.f, + gridSize / 2.f}); // all circles are centered around the same point + + // Initialize the radii of the circles + std::vector circleRadii; + axom::float64 maxRadius = + gridSize / 2.4; // Note: The choice of divisor is arbitrary + axom::float64 minRadius = + gridSize / 8; // Note: The choice of divisor is arbitrary + + axom::float64 radiusDelta; + if(numCircles <= 1) + radiusDelta = (maxRadius - minRadius); + else + radiusDelta = (maxRadius - minRadius) / (double)(numCircles - 1); + + for(int i = 0; i < numCircles; ++i) + { + circleRadii.push_back(minRadius + (i * radiusDelta)); + } + + // Make views + using CoordsetView = axom::mir::views::ExplicitCoordsetView; + CoordsetView coordsetView( + bputils::make_array_view(mesh["coordsets/coords/values/x"]), + bputils::make_array_view(mesh["coordsets/coords/values/y"])); + using TopoView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::QuadShape>; + TopoView topoView(bputils::make_array_view( + mesh["topologies/mesh/elements/connectivity"])); + + // Add the material + addConcentricCircleMaterial(topoView, + coordsetView, + circleCenter, + circleRadii, + 100, + mesh); +} + +//-------------------------------------------------------------------------------- + +int MeshTester::circleQuadCornersOverlaps(const MeshTester::Point2& circleCenter, + axom::float64 circleRadius, + const MeshTester::Point2& quadP0, + const MeshTester::Point2& quadP1, + const MeshTester::Point2& quadP2, + const MeshTester::Point2& quadP3) +{ + // Check if any of the quad's corners are within the circle + auto d0Sq = primal::squared_distance(quadP0, circleCenter); + auto d1Sq = primal::squared_distance(quadP1, circleCenter); + auto d2Sq = primal::squared_distance(quadP2, circleCenter); + auto d3Sq = primal::squared_distance(quadP3, circleCenter); + auto dRSq = circleRadius * circleRadius; + + int numCorners = 0; + + if(d0Sq < dRSq) numCorners++; + if(d1Sq < dRSq) numCorners++; + if(d2Sq < dRSq) numCorners++; + if(d3Sq < dRSq) numCorners++; + + return numCorners; +} + +//-------------------------------------------------------------------------------- +void MeshTester::initQuadClippingTestMesh(conduit::Node& mesh) +{ + // Generate the mesh topology + constexpr int gridSize = 3; + generateGrid(gridSize, mesh); + + // clang-format off + mesh["matsets/mat/topology"] = "mesh"; + mesh["matsets/mat/material_map/green"] = 0; + mesh["matsets/mat/material_map/blue"] = 1; + mesh["matsets/mat/material_ids"].set(std::vector{{ + 0, + 0, + 0, + 0, 1, + 0, 1, + 0, 1, + 1, + 1, + 1 + }}); + mesh["matsets/mat/volume_fractions"].set(std::vector{{ + 1., + 1., + 1., + 0.5, 0.5, + 0.5, 0.5, + 0.5, 0.5, + 1., + 1., + 1. + }}); + mesh["matsets/mat/sizes"].set(std::vector{{ + 1, 1, 1, 2, 2, 2, 1, 1, 1 + }}); + mesh["matsets/mat/offsets"].set(std::vector{{ + 0, 1, 2, 3, 5, 7, 9, 10, 11 + }}); + mesh["matsets/mat/indices"].set(std::vector{{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }}); + // clang-format on +} + +//-------------------------------------------------------------------------------- +void MeshTester::initTestCaseSix(int gridSize, int numSpheres, conduit::Node& mesh) +{ + // Generate the mesh topology + generateGrid3D(gridSize, mesh); + + // Generate the element volume fractions with concentric spheres + int numMaterials = numSpheres + 1; + int defaultMaterialID = + numMaterials - 1; // default material is always the last index + + // Initialize the radii of the circles + std::vector sphereRadii; + axom::float64 maxRadius = + gridSize / 2.0; // Note: The choice of divisor is arbitrary + axom::float64 minRadius = + gridSize / 4.0; // Note: The choice of divisor is arbitrary + + axom::float64 radiusDelta; + if(numSpheres <= 1) + radiusDelta = (maxRadius - minRadius); + else + radiusDelta = + (maxRadius - minRadius) / static_cast(numSpheres - 1); + + for(int i = 0; i < numSpheres; ++i) + { + auto rad = minRadius + (i * radiusDelta); + sphereRadii.push_back(rad * rad); + } + + // Make views to wrap the coordset/topology. + auto coordsetView = axom::mir::views::make_explicit_coordset::view( + mesh["coordsets/coords"]); + using CoordsetView = decltype(coordsetView); + using PointType = typename CoordsetView::PointType; + using TopologyView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::HexShape>; + TopologyView topologyView(bputils::make_array_view( + mesh["topologies/mesh/elements/connectivity"])); + + // Initialize all material volume fractions to 0 + std::vector> materialVolumeFractionsData( + numMaterials); + for(int i = 0; i < numMaterials; ++i) + { + materialVolumeFractionsData[i].resize(topologyView.numberOfZones(), 0.); + } + + // all spheres are centered around the same point + const float c = static_cast(gridSize / 2.0); + const auto sphereCenter = PointType({c, c, c}); + + // Use the uniform sampling method to generate volume fractions for each material + axom::Array materialCount(numMaterials, 0); + for(int eID = 0; eID < topologyView.numberOfZones(); ++eID) + { + const auto zone = topologyView.zone(eID); + const auto v0 = coordsetView[zone.getId(0)]; + const auto v1 = coordsetView[zone.getId(1)]; + const auto v3 = coordsetView[zone.getId(3)]; + const auto v4 = coordsetView[zone.getId(4)]; + + // Run the uniform sampling to determine how much of the current cell is composed of each material + for(int i = 0; i < numMaterials; ++i) + { + materialCount[i] = 0; + } + + float delta_x = + axom::utilities::abs(v1[0] - v0[0]) / static_cast(gridSize - 1); + float delta_y = + axom::utilities::abs(v3[1] - v0[1]) / static_cast(gridSize - 1); + float delta_z = + axom::utilities::abs(v4[2] - v0[2]) / static_cast(gridSize - 1); + + for(int z = 0; z < gridSize; ++z) + { + for(int y = 0; y < gridSize; ++y) + { + for(int x = 0; x < gridSize; ++x) + { + const PointType samplePoint({static_cast(delta_x * x + v0[0]), + static_cast(delta_y * y + v0[1]), + static_cast(delta_z * z + v0[2])}); + + bool isPointSampled = false; + for(int cID = 0; cID < numSpheres && !isPointSampled; ++cID) + { + if(primal::squared_distance(samplePoint, sphereCenter) < + sphereRadii[cID]) + { + materialCount[cID]++; + isPointSampled = true; + } + } + if(!isPointSampled) + { + // The point was not within any of the circles, so increment the count for the default material + materialCount[defaultMaterialID]++; + } + } + } + } + + // Assign the element volume fractions based on the count of the samples in each circle + const axom::float64 nzones_inv = + 1. / static_cast(gridSize * gridSize * gridSize); + for(int matID = 0; matID < numMaterials; ++matID) + { + materialVolumeFractionsData[matID][eID] = materialCount[matID] * nzones_inv; + } + } + + addMaterial(topologyView.numberOfZones(), + numMaterials, + materialVolumeFractionsData, + mesh); +} + +//-------------------------------------------------------------------------------- +void MeshTester::generateGrid3D(int gridSize, conduit::Node& mesh) +{ + AXOM_ANNOTATE_SCOPE("generateGrid3D"); + const int nzones = gridSize * gridSize * gridSize; + const int dims[3] = {gridSize + 1, gridSize + 1, gridSize + 1}; + const int nnodes = dims[0] * dims[1] * dims[2]; + + conduit::Node& coordset = mesh["coordsets/coords"]; + conduit::Node& topo = mesh["topologies/mesh"]; + + // Make coordset + coordset["type"] = "explicit"; + conduit::Node& n_x = coordset["values/x"]; + conduit::Node& n_y = coordset["values/y"]; + conduit::Node& n_z = coordset["values/z"]; + n_x.set(conduit::DataType::float32(nnodes)); + n_y.set(conduit::DataType::float32(nnodes)); + n_z.set(conduit::DataType::float32(nnodes)); + auto* x = static_cast(n_x.data_ptr()); + auto* y = static_cast(n_y.data_ptr()); + auto* z = static_cast(n_z.data_ptr()); + + for(int k = 0; k < dims[2]; k++) + { + for(int j = 0; j < dims[1]; j++) + { + for(int i = 0; i < dims[0]; i++) + { + *x++ = i; + *y++ = j; + *z++ = k; + } + } + } + + // Make topology + topo["type"] = "unstructured"; + topo["coordset"] = "coords"; + topo["elements/shape"] = "hex"; + conduit::Node& conn = topo["elements/connectivity"]; + conduit::Node& sizes = topo["elements/sizes"]; + conduit::Node& offsets = topo["elements/offsets"]; + conn.set(conduit::DataType::int32(8 * nzones)); + sizes.set(conduit::DataType::int32(nzones)); + offsets.set(conduit::DataType::int32(nzones)); + auto* conn_ptr = static_cast(conn.data_ptr()); + auto* sizes_ptr = static_cast(sizes.data_ptr()); + auto* offsets_ptr = static_cast(offsets.data_ptr()); + + for(int k = 0; k < gridSize; k++) + { + const int knxny = k * dims[0] * dims[1]; + const int k1nxny = (k + 1) * dims[0] * dims[1]; + for(int j = 0; j < gridSize; j++) + { + const int jnx = j * dims[0]; + const int j1nx = (j + 1) * dims[0]; + for(int i = 0; i < gridSize; i++) + { + conn_ptr[0] = knxny + jnx + i; + conn_ptr[1] = knxny + jnx + i + 1; + conn_ptr[2] = knxny + j1nx + i + 1; + conn_ptr[3] = knxny + j1nx + i; + conn_ptr[4] = k1nxny + jnx + i; + conn_ptr[5] = k1nxny + jnx + i + 1; + conn_ptr[6] = k1nxny + j1nx + i + 1; + conn_ptr[7] = k1nxny + j1nx + i; + + conn_ptr += 8; + } + } + } + for(int i = 0; i < nzones; i++) + { + sizes_ptr[i] = 8; + offsets_ptr[i] = 8 * i; + } +} + +//-------------------------------------------------------------------------------- + +} // namespace mir +} // namespace axom diff --git a/src/axom/mir/MeshTester.hpp b/src/axom/mir/MeshTester.hpp new file mode 100644 index 0000000000..84fcfae54c --- /dev/null +++ b/src/axom/mir/MeshTester.hpp @@ -0,0 +1,187 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/*! + * \file MeshTester.hpp + * + * \brief Contains the specification for the MeshTester class. + * + */ + +#ifndef __AXOM_MIR_MESH_TESTER_HPP__ +#define __AXOM_MIR_MESH_TESTER_HPP__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" // unified header for slam classes and functions +#include "axom/primal.hpp" + +#include +#include + +#include + +namespace axom +{ +namespace mir +{ +/*! + * \class MeshTester + * + * \brief A class used to generate MIRMeshs with specific properties so + * that the reconstruction output can be validated visually. + * + */ +class MeshTester +{ +public: + template + using Vec = std::vector; + + using IndexVec = Vec; + using VolFracVec = Vec; + using VolumeFractions = Vec; + using Point2 = axom::primal::Point; + +public: + /*! + * \brief Default constructor. + */ + MeshTester() = default; + + /*! + * \brief Default destructor. + */ + ~MeshTester() = default; + +public: + /*! + * \brief Initializes an MIRMesh based on the example from Meredith 2004 paper. + * + * \note The mesh is a 3x3 uniform grid of quads with 2 materials. + * + * \return The generated mesh. + */ + void initTestCaseOne(conduit::Node &mesh); + + /*! + * \brief Initializes an MIRMesh based on the example from Meredith and Childs 2010 paper. + * + * \note The mesh is a 3x3 uniform grid of quads with 3 materials. + * + * \return The generated mesh. + */ + void initTestCaseTwo(conduit::Node &mesh); + + /*! + * \brief Initializes an MIRMesh used for testing triangle clipping cases. + * + * \note The mesh is a set of four triangles with 2 materials + * + * \return The generated mesh. + */ + void initTestCaseThree(conduit::Node &mesh); + + /*! + * \brief Intializes a mesh used for testing a single circle of one materials surrounded by another. + * + * \note The mesh is a 3x3 uniform grid with 2 materials and has a single circle in the center composed + * of one material and is surrounded by a second material. + * + * \return The generated mesh. + */ + void initTestCaseFour(conduit::Node &mesh); + + /*! + * \brief Initializes a mesh to be used for testing a set of concentric circles centered in a uniform 2D grid. + * + * \param gridSize The number of elements in the width and the height of the uniform grid. + * \param numCircles The number of concentric circles that are centered in the grid. + * + * \note Each circle is composed of a different material. + * + * \return The generated mesh. + */ + void initTestCaseFive(int gridSize, int numCircles, conduit::Node &mesh); + + /*! + * \brief Initializes a mesh to be used for testing a set of concentric spheres centered in a uniform 3D grid. + * + * \param gridSize The number of elements in the width and the height of the uniform grid. + * \param numSpheres The number of concentric spheres that are centered in the grid. + * + * \note Each sphere is composed of a different material. + * + * \return The generated mesh. + */ + void initTestCaseSix(int gridSize, int numSpheres, conduit::Node &mesh); + + /*! + * \brief Initializes a mesh composed of a uniform grid with a circle of material in it. + * + * \param gridSize The number of elements in the width and height of the uniform grid. + * \param circleCenter The center point of the circle. + * \param circleRadius The radius of the circle. + * + * \return The generated mesh. + */ + void createUniformGridTestCaseMesh(int gridSize, + const Point2 &circleCenter, + axom::float64 circleRadius, + conduit::Node &mesh); + /*! + * \brief Initializes a mesh to be used for validating the results of quad clipping. + * + * \note The mesh is a 3x3 uniform grid with 2 materials and element volume fraction such + * that the mesh would be split horizontally through the middle. + * + * \return The generated mesh. + */ + void initQuadClippingTestMesh(conduit::Node &mesh); + +private: + /*! + * \brief make a 3x3 mesh of quads. + * \param mesh A conduit node that will contain the new mesh. + */ + void mesh3x3(conduit::Node &mesh); + + /*! + * \brief Generates a 2D uniform grid of n x n elements. + * + * \param gridSize The number of elements in the width and height of the uniform grid. + */ + void generateGrid(int gridSize, conduit::Node &mesh); + + /*! + * \brief Generates a 3D uniform grid of n x n x n elements. + * + * \param gridSize The number of elements in the width, height, and depth of the uniform grid. + */ + void generateGrid3D(int gridSize, conduit::Node &mesh); + + /*! + * \brief Calculates the number of corners of the quad that are within the circle. + * + * \param circleCenter The center point of the circle. + * \param circleRadius The radius of the circle. + * \param quadP0 The upper left vertex of the quad. + * \param quadP1 The lower left vertex of the quad. + * \param quadP2 The lower right vertex of the quad. + * \param quadP3 The upper right vertex of the quad. + * + * \return The number of corners of the quad that are within the circle. + */ + int circleQuadCornersOverlaps(const Point2 &circleCenter, + axom::float64 circleRadius, + const Point2 &quadP0, + const Point2 &quadP1, + const Point2 &quadP2, + const Point2 &quadP3); +}; + +} // namespace mir +} // namespace axom + +#endif diff --git a/src/axom/mir/Options.hpp b/src/axom/mir/Options.hpp new file mode 100644 index 0000000000..c4a14497c9 --- /dev/null +++ b/src/axom/mir/Options.hpp @@ -0,0 +1,89 @@ +// 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) +#ifndef AXOM_MIR_OPTIONS_HPP_ +#define AXOM_MIR_OPTIONS_HPP_ + +#include "axom/core.hpp" + +#include +#include + +namespace axom +{ +namespace mir +{ +/*! + * \brief This class provides a kind of schema over options, as well + * as default values, and some utilities functions. + */ +class Options +{ +public: + /*! + * \brief Constructor + * + * \param nzones The total number of zones in the associated topology. + * \param options The node that contains the clipping options. + */ + Options(const conduit::Node &options) : m_options(options) { } + + /*! + * \brief Return the name of the topology to make in the output. + * \param default_value The name to use if the option is not defined. + * \return The name of the topology to make in the output. + */ + std::string topologyName(const std::string &default_value = std::string()) const + { + std::string name(default_value); + if(m_options.has_child("topologyName")) + name = m_options.fetch_existing("topologyName").as_string(); + return name; + } + + /*! + * \brief Return the name of the coordset to make in the output. + * \param default_value The name to use if the option is not defined. + * \return The name of the coordset to make in the output. + */ + std::string coordsetName(const std::string &default_value = std::string()) const + { + std::string name(default_value); + if(m_options.has_child("coordsetName")) + name = m_options.fetch_existing("coordsetName").as_string(); + return name; + } + + /*! + * \brief Extract the names of the fields to process (and their output names) from the + * options or \a n_fields if the options do not contain fields. + * + * \param[out] f A map of the fields that will be processed, as well as their output name in the new fields. + * \return True if the fields were present in the options. False otherwise. + */ + bool fields(std::map &f) const + { + bool retval = m_options.has_child("fields"); + f.clear(); + if(retval) + { + const conduit::Node &n_opt_fields = m_options.fetch_existing("fields"); + for(conduit::index_t i = 0; i < n_opt_fields.number_of_children(); i++) + { + if(n_opt_fields[i].dtype().is_string()) + f[n_opt_fields[i].name()] = n_opt_fields[i].as_string(); + else + f[n_opt_fields[i].name()] = n_opt_fields[i].name(); + } + } + return retval; + } + +protected: + const conduit::Node &m_options; // A reference to the options node. +}; +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/README.md b/src/axom/mir/README.md new file mode 100644 index 0000000000..fa6b7fba6a --- /dev/null +++ b/src/axom/mir/README.md @@ -0,0 +1,8 @@ +MIR: Material Interface Reconstruction {#mirtop} +================================================ + +[MIR](@ref axom::mir) provides algorithms for reconstructing interfaces +between materials in a multimaterial mesh. + +The [Mir user documentation](../../../sphinx/axom_docs/html/axom/mir/docs/sphinx/index.html) +describes these algorithms. diff --git a/src/axom/mir/clipping/BlendGroupBuilder.hpp b/src/axom/mir/clipping/BlendGroupBuilder.hpp new file mode 100644 index 0000000000..2d850c2f08 --- /dev/null +++ b/src/axom/mir/clipping/BlendGroupBuilder.hpp @@ -0,0 +1,545 @@ +// 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) +#ifndef AXOM_MIR_BLEND_GROUP_BUILDER_HPP_ +#define AXOM_MIR_BLEND_GROUP_BUILDER_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/utilities/utilities.hpp" +#include "axom/mir/utilities/blueprint_utilities.hpp" + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +namespace axom +{ +namespace mir +{ +namespace clipping +{ +/*! + * \brief This class encapsulates the logic for building blend groups. + * + * \tparam ExecSpace The execution space where the algorithm will run. + * \tparam NamingPolicyView The type of a view that can access functionality in the naming policy that is used to make nodeids. + * + * \note The class only contains views to data so it can be copied into lambdas. + */ +template +class BlendGroupBuilder +{ +public: + using KeyType = typename NamingPolicyView::KeyType; + + /*! + * \brief This struct holds the views that represent data for blend groups. + */ + struct State + { + // clang-format off + IndexType m_nzones {}; + NamingPolicyView m_namingView {}; + + axom::ArrayView m_blendGroupsView {}; // Number of blend groups in each zone. + axom::ArrayView m_blendGroupsLenView {}; // total size of blend group data for each zone. + axom::ArrayView m_blendOffsetView {}; // The offset of each zone's blend groups. + axom::ArrayView m_blendGroupOffsetsView {}; // Start of each zone's blend group data. + + axom::ArrayView m_blendNamesView {}; // Blend group names + axom::ArrayView m_blendGroupSizesView {}; // Size of individual blend group. + axom::ArrayView m_blendGroupStartView {}; // Start of individual blend group's data in m_blendIdsView/m_blendCoeffView. + axom::ArrayView m_blendIdsView {}; // blend group ids. + axom::ArrayView m_blendCoeffView {}; // blend group weights. + + axom::ArrayView m_blendUniqueNamesView {}; // Unique names of blend groups. + axom::ArrayView m_blendUniqueIndicesView {}; // Indices of the unique names in blend group definitions. + // clang-format on + }; + + /*! + * \brief Access the state views. + * \return A reference to the state. + */ + State &state() { return m_state; } + const State &state() const { return m_state; } + + /*! + * \brief Provide a hint to the naming policy view so it can do narrowing. + * + * \param nnodes The number of nodes in the input mesh. + */ + void setNamingPolicy(const NamingPolicyView &view) + { + m_state.m_namingView = view; + } + + /*! + * \brief Set the number of zones. + * + * \param blendGroupsView The view that holds the number of blend groups for each zone. + * \param blendGroupsLenView The view that holds the size of the blend group data for each zone. + */ + void setBlendGroupSizes(const axom::ArrayView &blendGroupsView, + const axom::ArrayView &blendGroupsLenView) + { + m_state.m_nzones = blendGroupsView.size(); + m_state.m_blendGroupsView = blendGroupsView; + m_state.m_blendGroupsLenView = blendGroupsLenView; + } + + /*! + * \brief Compute the total sizes of blend group storage. + * + * \param[out] bgSum The total number of blend groups for all zones. + * \param[out] bgLenSum The total size of blend group data for all zones. + */ + void computeBlendGroupSizes(IndexType &bgSum, IndexType &bgLenSum) + { + AXOM_ANNOTATE_SCOPE("computeBlendGroupSizes"); + using reduce_policy = + typename axom::execution_space::reduce_policy; + RAJA::ReduceSum blendGroups_sum(0); + RAJA::ReduceSum blendGroupLen_sum(0); + const auto localBlendGroupsView = m_state.m_blendGroupsView; + const auto localBlendGroupsLenView = m_state.m_blendGroupsLenView; + axom::for_all( + m_state.m_nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + blendGroups_sum += localBlendGroupsView[zoneIndex]; + blendGroupLen_sum += localBlendGroupsLenView[zoneIndex]; + }); + bgSum = blendGroups_sum.get(); + bgLenSum = blendGroupLen_sum.get(); + } + + /*! + * \brief Set the views for the blend group offsets and then fill them using a scan. + * + * \param blendOffsetView The offsets to each blend group for views sized: view[blendGroupSum]. + * \param blendGroupOffsetsView The offsets to each zone's blend groups data. + */ + void setBlendGroupOffsets(const axom::ArrayView &blendOffsetView, + const axom::ArrayView &blendGroupOffsetsView) + { + m_state.m_blendOffsetView = blendOffsetView; + m_state.m_blendGroupOffsetsView = blendGroupOffsetsView; + } + + /*! + * brief Compute the blend group offsets that make it easier to store data. + */ + void computeBlendGroupOffsets() + { + AXOM_ANNOTATE_SCOPE("computeBlendGroupOffsets"); + axom::exclusive_scan(m_state.m_blendGroupsLenView, + m_state.m_blendOffsetView); + axom::exclusive_scan(m_state.m_blendGroupsView, + m_state.m_blendGroupOffsetsView); + } + + /*! + * \brief Set the views that we'll use for blend groups. + */ + void setBlendViews(const axom::ArrayView &blendNames, + const axom::ArrayView &blendGroupSizes, + const axom::ArrayView &blendGroupStart, + const axom::ArrayView &blendIds, + const axom::ArrayView &blendCoeff) + { + m_state.m_blendNamesView = blendNames; + m_state.m_blendGroupSizesView = blendGroupSizes; + m_state.m_blendGroupStartView = blendGroupStart; + m_state.m_blendIdsView = blendIds; + m_state.m_blendCoeffView = blendCoeff; + } + + /*! + * \brief Set the unique names and ids views. These are used in mapping blend groups to unique blend groups. + * + * \param uniqueNames A view containing unique, sorted blend group names. + * \param uniqueIndices A view containing the original blend group index for each unique name. + */ + void setUniqueNames(const axom::ArrayView &uniqueNames, + const axom::ArrayView &uniqueIndices) + { + m_state.m_blendUniqueNamesView = uniqueNames; + m_state.m_blendUniqueIndicesView = uniqueIndices; + } + + /*! + * \brief Get the blend names view. + * \return The blend names view. + */ + const axom::ArrayView &blendNames() const + { + return m_state.m_blendNamesView; + } + + /*! + * \brief This class helps us manage blend group creation and usage for blend groups within a single zone. + */ + class zone_blend_groups + { + public: + /*! + * \brief Return the number of blend groups for this zone. + * \return The number of blend groups for this zone. + */ + AXOM_HOST_DEVICE + inline IndexType numGroups() const + { + return m_state->m_blendGroupsView[m_zoneIndex]; + } + + /*! + * \brief Set the number of blend groups and total size of the blend groups for a zone. + * + * \param zoneIndex The index of the zone we're initializing. + * \param nBlendGroups The number of blend groups in this zone. + * \param blendGroupSize The size of all of the blend groups in this zone. + */ + AXOM_HOST_DEVICE + inline void setNumGroups(IndexType nBlendGroups, IndexType blendGroupsSize) + { + m_state->m_blendGroupsView[m_zoneIndex] = nBlendGroups; + m_state->m_blendGroupsLenView[m_zoneIndex] = blendGroupsSize; + } + + /*! + * \brief Start creating a new blend group within the allocated space for this zone. + */ + AXOM_HOST_DEVICE + inline void beginGroup() { m_currentDataOffset = m_startOffset; } + + /*! + * \brief Add a new blend point in the current blend group. + * + * \param id The node id that will be used for blending. + * \param weight The weight that will be used for blending. + */ + AXOM_HOST_DEVICE + inline void add(IndexType id, float weight) + { + m_state->m_blendIdsView[m_currentDataOffset] = id; + m_state->m_blendCoeffView[m_currentDataOffset] = weight; + m_currentDataOffset++; + } + + /*! + * \brief End the current blend group, storing its name, size, etc. + */ + AXOM_HOST_DEVICE + inline void endGroup() + { + IndexType numIds = m_currentDataOffset - m_startOffset; + + // Store where this blendGroup starts in the blendIds,blendCoeff. + m_state->m_blendGroupStartView[m_blendGroupId] = m_startOffset; + + // Save the size for this blend group. + m_state->m_blendGroupSizesView[m_blendGroupId] = numIds; + + // Store "name" of blend group. + KeyType blendName = m_state->m_namingView.makeName( + m_state->m_blendIdsView.data() + m_startOffset, + numIds); + + m_state->m_blendNamesView[m_blendGroupId] = blendName; +#if defined(AXOM_DEBUG) && !defined(AXOM_DEVICE_CODE) + const float w = weightSum(); + constexpr float EPS = 1.e-5; + if(w < (1. - EPS) || w > (1. + EPS)) + { + SLIC_ERROR(fmt::format("Invalid blend group weights w={}.", w)); + print(std::cout); + } +#endif + m_blendGroupId++; + m_startOffset = m_currentDataOffset; + } + + /*! + * \brief Return the sum of the weights for the current blend group. + * \note The blendGroup must have finished construction. + * \return The sum of weights, which should be about 1. + */ + AXOM_HOST_DEVICE + inline float weightSum() const + { + const auto numIds = m_state->m_blendGroupSizesView[m_blendGroupId]; + const auto start = m_state->m_blendGroupStartView[m_blendGroupId]; + float w = 0.f; + for(IndexType i = 0; i < numIds; i++) + w += m_state->m_blendCoeffView[start + i]; + return w; + } + + /*! + * \brief Return the size for the current blend group. + * \note The blendGroup must have finished construction. + * \return The size of the current blend group. + */ + AXOM_HOST_DEVICE + inline IndexType size() const + { + return m_state->m_blendGroupSizesView[m_blendGroupId]; + } + + /*! + * \brief Return the index'th weight in the blend group. + * \note The blendGroup must have finished construction. + * \return The index'th weight. + */ + AXOM_HOST_DEVICE + inline float weight(int index) const + { + const auto start = m_state->m_blendGroupStartView[m_blendGroupId]; + return m_state->m_blendCoeffView[start + index]; + } + + /*! + * \brief Return the index'th weight in the blend group. + * \note The blendGroup must have finished construction. + * \return The index'th weight. + */ + AXOM_HOST_DEVICE + inline IndexType id(int index) const + { + const auto start = m_state->m_blendGroupStartView[m_blendGroupId]; + return m_state->m_blendIdsView[start + index]; + } + + /*! + * \brief Return the name of the current blend group. + * \return The name of the current blend group. + */ + AXOM_HOST_DEVICE + inline KeyType name() const + { + return m_state->m_blendNamesView[m_blendGroupId]; + } + + /*! + * \brief Return index of the current blend group in the unique blend groups. + * \return The unique index of the current blend group. + */ + AXOM_HOST_DEVICE + inline IndexType uniqueBlendGroupIndex() const + { + return axom::mir::utilities::bsearch(name(), + m_state->m_blendUniqueNamesView); + } + + /*! + * \brief Advance to the next blend group. + */ + AXOM_HOST_DEVICE + inline void operator++() { m_blendGroupId++; } + + /*! + * \brief Advance to the next blend group. + */ + AXOM_HOST_DEVICE + inline void operator++(int) { m_blendGroupId++; } + +#if !defined(AXOM_DEVICE_CODE) + /*! + * \brief Print the current blend group to a stream. + * \param os The stream to which the blend group will print. + */ + void print(std::ostream &os) const + { + const auto n = m_state->m_blendGroupSizesView[m_blendGroupId]; + const auto offset = m_state->m_blendGroupStartView[m_blendGroupId]; + os << "-\n"; + os << " zoneIndex: " << m_zoneIndex << std::endl; + os << " blendGroupId: " << m_blendGroupId << std::endl; + os << " size: " << n << std::endl; + os << " offset: " << offset << std::endl; + + const IndexType *ids = m_state->m_blendIdsView.data() + offset; + os << " ids: ["; + for(int bi = 0; bi < n; bi++) + { + if(bi > 0) os << ", "; + os << ids[bi]; + } + os << "]"; + os << "\n"; + const float *weights = m_state->m_blendCoeffView.data() + offset; + os << " weights: ["; + for(int bi = 0; bi < n; bi++) + { + if(bi > 0) os << ", "; + os << weights[bi]; + } + os << "]\n"; + os << " name: " << m_state->m_blendNamesView[m_blendGroupId]; + os << "\n"; + } +#endif + + /*! + * \brief Return the current blend group's ids. + * \return The current blend group's ids. + * \note This method should not be used if blend groups are still being constructed. + */ + axom::ArrayView ids() const + { + const auto n = m_state->m_blendGroupSizesView[m_blendGroupId]; + const auto offset = m_state->m_blendGroupStartView[m_blendGroupId]; + return axom::ArrayView(m_state->m_blendIdsView.data() + offset, + n); + } + + /*! + * \brief Return the current blend group's ids. + * \return The current blend group's ids. + * \note This method should not be used if blend groups are still being constructed. + */ + axom::ArrayView weights() const + { + const auto n = m_state->m_blendGroupSizesView[m_blendGroupId]; + const auto offset = m_state->m_blendGroupStartView[m_blendGroupId]; + return axom::ArrayView(m_state->m_blendCoeffsView.data() + offset, + n); + } + + private: + friend class BlendGroupBuilder; + + IndexType m_zoneIndex; // The zone that owns this set of blend groups. + IndexType m_blendGroupId; // The global blend group index within this current zone. + IndexType m_startOffset; // The data offset for the first ids/weights in this blend group. + IndexType m_currentDataOffset; // The current data offset. + State *m_state; // Pointer to the main state. + }; + + /*! + * \brief Return a zone_blend_groups object for the current zone so we can add blend groups. + * + * \param zoneIndex The zone whose blend groups we want to edit. + * + * \note This method must be marked const because we can call it from an AXOM_LAMBDA. + * we pass a non-const State reference to the zone_blend_groups that we construct + * so we can write into the blend group data. + */ + AXOM_HOST_DEVICE + zone_blend_groups blendGroupsForZone(IndexType zoneIndex) const + { + zone_blend_groups groups; + // The zone that owns this set of blend groups. + groups.m_zoneIndex = zoneIndex; + + // Global blend group id for the first blend group in this zone. + groups.m_blendGroupId = m_state.m_blendGroupOffsetsView[zoneIndex]; + // Global start + groups.m_startOffset = groups.m_currentDataOffset = + m_state.m_blendOffsetView[zoneIndex]; + + groups.m_state = const_cast(&m_state); + return groups; + } + + /*! + * \brief Filter out single node blend groups from the unique. + * + * \param blend The BlendData that describes the blend groups. + * \param[out] newSelectedIndices An array that will contain the data for the + * new selected indices, if we need to make it. + */ + void filterUnique(axom::Array &newUniqueNames, + axom::Array &newUniqueIndices) + { + AXOM_ANNOTATE_SCOPE("filterUnique"); + using reduce_policy = + typename axom::execution_space::reduce_policy; + const auto nIndices = m_state.m_blendUniqueIndicesView.size(); + + if(nIndices > 0) + { + const int allocatorID = axom::execution_space::allocatorID(); + + // Make a mask of selected indices have more than one id in their blend group. + axom::Array mask(nIndices, nIndices, allocatorID); + auto maskView = mask.view(); + RAJA::ReduceSum mask_reduce(0); + State deviceState(m_state); + axom::for_all( + nIndices, + AXOM_LAMBDA(axom::IndexType index) { + const auto uniqueIndex = deviceState.m_blendUniqueIndicesView[index]; + const int m = + (deviceState.m_blendGroupSizesView[uniqueIndex] > 1) ? 1 : 0; + maskView[index] = m; + mask_reduce += m; + }); + // If we need to filter, do it. + const int mask_count = mask_reduce.get(); + + if(mask_count < nIndices) + { + // Make offsets. + axom::Array offset(nIndices, nIndices, allocatorID); + auto offsetView = offset.view(); + axom::exclusive_scan(maskView, offsetView); + + // Make new unique data where we compress out blend groups that had 1 node. + newUniqueNames = + axom::Array(mask_count, mask_count, allocatorID); + newUniqueIndices = + axom::Array(mask_count, mask_count, allocatorID); + + auto newUniqueNamesView = newUniqueNames.view(); + auto newUniqueIndicesView = newUniqueIndices.view(); + axom::for_all( + nIndices, + AXOM_LAMBDA(axom::IndexType index) { + if(maskView[index] > 0) + { + const auto offset = offsetView[index]; + newUniqueNamesView[offset] = + deviceState.m_blendUniqueNamesView[index]; + newUniqueIndicesView[offset] = + deviceState.m_blendUniqueIndicesView[index]; + } + }); + + // Replace the unique names/indices. + m_state.m_blendUniqueNamesView = newUniqueNamesView; + m_state.m_blendUniqueIndicesView = newUniqueIndicesView; + } + } + } + + /*! + * \brief Make a BlendData object from the views in this object. + * + * \return A BlendData object suitable for making new fields and coordsets. + */ + axom::mir::utilities::blueprint::BlendData makeBlendData() const + { + axom::mir::utilities::blueprint::BlendData blend; + + blend.m_selectedIndicesView = + m_state.m_blendUniqueIndicesView; // We'll use these to select just the unique indices + blend.m_blendGroupSizesView = m_state.m_blendGroupSizesView; + blend.m_blendGroupStartView = m_state.m_blendGroupStartView; + blend.m_blendIdsView = m_state.m_blendIdsView; + blend.m_blendCoeffView = m_state.m_blendCoeffView; + + return blend; + } + +private: + State m_state; +}; + +} // end namespace clipping +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/clipping/ClipCases.h b/src/axom/mir/clipping/ClipCases.h new file mode 100644 index 0000000000..8c1ca266f3 --- /dev/null +++ b/src/axom/mir/clipping/ClipCases.h @@ -0,0 +1,179 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt +// Project developers. See the top-level LICENSE file for dates and other +// details. No copyright assignment is required to contribute to VisIt. + +#ifndef AXOM_VISIT_CLIP_CASES_H +#define AXOM_VISIT_CLIP_CASES_H +//--------------------------------------------------------------------------- +// Axom modifications +// NOTE: The values for EA-EL and N0-N3 were reduced. +// clang-format off +//#include +#define VISIT_VTK_LIGHT_API +#include +namespace axom { +namespace mir { +namespace clipping { +namespace visit { +//--------------------------------------------------------------------------- + +// Programmer: Jeremy Meredith +// Date : August 11, 2003 +// +// Modifications: +// Jeremy Meredith, Mon Sep 15 17:24:15 PDT 2003 +// Added NOCOLOR. +// +// Jeremy Meredith, Thu Sep 18 11:29:12 PDT 2003 +// Added quad and triangle cases and output shapes. +// +// Brad Whitlock, Tue Sep 23 09:59:23 PDT 2003 +// Added API so it builds on Windows. +// +// Jeremy Meredith, Wed Jun 23 15:39:58 PDT 2004 +// Added voxel and pixel cases. Not output shapes, though. +// +// Jeremy Meredith, Tue Aug 29 13:52:33 EDT 2006 +// Added line segments and vertexes. +// + +// Points of original cell (up to 8, for the hex) +// Note: we assume P0 is zero in several places. +// Note: we assume these values are contiguous and monotonic. +#define P0 0 +#define P1 1 +#define P2 2 +#define P3 3 +#define P4 4 +#define P5 5 +#define P6 6 +#define P7 7 + +// Edges of original cell (up to 12, for the hex) +// Note: we assume these values are contiguous and monotonic. +#define EA 8 +#define EB 9 +#define EC 10 +#define ED 11 +#define EE 12 +#define EF 13 +#define EG 14 +#define EH 15 +#define EI 16 +#define EJ 17 +#define EK 18 +#define EL 19 + +// New interpolated points (ST_PNT outputs) +// Note: we assume these values are contiguous and monotonic. +#define N0 20 +#define N1 21 +#define N2 22 +#define N3 23 + +// Shapes +#define ST_TET 100 +#define ST_PYR 101 +#define ST_WDG 102 +#define ST_HEX 103 +#define ST_TRI 104 +#define ST_QUA 105 +#define ST_VTX 106 +#define ST_LIN 107 +#define ST_PNT 108 + +// Colors +#define COLOR0 120 +#define COLOR1 121 +#define NOCOLOR 122 + +// Tables +extern VISIT_VTK_LIGHT_API int numClipCasesHex; +extern VISIT_VTK_LIGHT_API int numClipShapesHex[256]; +extern VISIT_VTK_LIGHT_API int startClipShapesHex[256]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesHex[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesVox; +extern VISIT_VTK_LIGHT_API int numClipShapesVox[256]; +extern VISIT_VTK_LIGHT_API int startClipShapesVox[256]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesVox[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesWdg; +extern VISIT_VTK_LIGHT_API int numClipShapesWdg[64]; +extern VISIT_VTK_LIGHT_API int startClipShapesWdg[64]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesWdg[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesPyr; +extern VISIT_VTK_LIGHT_API int numClipShapesPyr[32]; +extern VISIT_VTK_LIGHT_API int startClipShapesPyr[32]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesPyr[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesTet; +extern VISIT_VTK_LIGHT_API int numClipShapesTet[16]; +extern VISIT_VTK_LIGHT_API int startClipShapesTet[16]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesTet[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesQua; +extern VISIT_VTK_LIGHT_API int numClipShapesQua[16]; +extern VISIT_VTK_LIGHT_API int startClipShapesQua[16]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesQua[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesPix; +extern VISIT_VTK_LIGHT_API int numClipShapesPix[16]; +extern VISIT_VTK_LIGHT_API int startClipShapesPix[16]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesPix[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesTri; +extern VISIT_VTK_LIGHT_API int numClipShapesTri[8]; +extern VISIT_VTK_LIGHT_API int startClipShapesTri[8]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesTri[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesLin; +extern VISIT_VTK_LIGHT_API int numClipShapesLin[4]; +extern VISIT_VTK_LIGHT_API int startClipShapesLin[4]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesLin[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesVtx; +extern VISIT_VTK_LIGHT_API int numClipShapesVtx[2]; +extern VISIT_VTK_LIGHT_API int startClipShapesVtx[2]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesVtx[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesPoly5; +extern VISIT_VTK_LIGHT_API int numClipShapesPoly5[32]; +extern VISIT_VTK_LIGHT_API int startClipShapesPoly5[32]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesPoly5[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesPoly6; +extern VISIT_VTK_LIGHT_API int numClipShapesPoly6[64]; +extern VISIT_VTK_LIGHT_API int startClipShapesPoly6[64]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesPoly6[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesPoly7; +extern VISIT_VTK_LIGHT_API int numClipShapesPoly7[128]; +extern VISIT_VTK_LIGHT_API int startClipShapesPoly7[128]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesPoly7[]; + +extern VISIT_VTK_LIGHT_API int numClipCasesPoly8; +extern VISIT_VTK_LIGHT_API int numClipShapesPoly8[256]; +extern VISIT_VTK_LIGHT_API int startClipShapesPoly8[256]; +extern VISIT_VTK_LIGHT_API unsigned char clipShapesPoly8[]; + +//--------------------------------------------------------------------------- +// Axom modifications +#define ST_MIN ST_TET +#define ST_MAX (ST_PNT + 1) +#undef VISIT_VTK_LIGHT_API +extern const size_t clipShapesTriSize; +extern const size_t clipShapesQuaSize; +extern const size_t clipShapesTetSize; +extern const size_t clipShapesPyrSize; +extern const size_t clipShapesWdgSize; +extern const size_t clipShapesHexSize; +} // namespace visit +} // namespace clipping +} // namespace mir +} // namespace axom +// clang-format on +//--------------------------------------------------------------------------- + +#endif diff --git a/src/axom/mir/clipping/ClipCasesHex.cpp b/src/axom/mir/clipping/ClipCasesHex.cpp new file mode 100644 index 0000000000..f10e3d140a --- /dev/null +++ b/src/axom/mir/clipping/ClipCasesHex.cpp @@ -0,0 +1,3508 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt +// Project developers. See the top-level LICENSE file for dates and other +// details. No copyright assignment is required to contribute to VisIt. + +#include "ClipCases.h" +//--------------------------------------------------------------------------- +// Axom modifications +// clang-format off +namespace axom { +namespace mir { +namespace clipping { +namespace visit { +//--------------------------------------------------------------------------- + +// Programmer: Jeremy Meredith +// Date : August 11, 2003 +// +// Modifications: +// Jeremy Meredith, Mon Sep 15 17:30:21 PDT 2003 +// Added ability for Centroid-Points to have an associated color. +// Fixed two inconsistent cases. + +// This file is meant to be read and created by a program other than a +// compiler. If you must modify it by hand, at least be nice to the +// parser and don't add anything else to this file or rearrange it. + +int numClipCasesHex = 256; + +int numClipShapesHex[256] = { + 1, 10, 10, 3, 10, 18, 3, 15, // cases 0 - 7 + 10, 3, 18, 15, 3, 15, 15, 2, // cases 8 - 15 + 10, 3, 18, 15, 8, 18, 18, 17, // cases 16 - 23 + 18, 15, 11, 10, 18, 17, 16, 15, // cases 24 - 31 + 10, 18, 3, 15, 18, 11, 15, 10, // cases 32 - 39 + 8, 18, 18, 17, 18, 16, 17, 15, // cases 40 - 47 + 3, 15, 15, 2, 18, 16, 17, 15, // cases 48 - 55 + 18, 17, 16, 15, 4, 13, 13, 3, // cases 56 - 63 + 10, 8, 18, 18, 3, 18, 15, 17, // cases 64 - 71 + 18, 18, 11, 16, 15, 17, 10, 15, // cases 72 - 79 + 18, 18, 11, 16, 18, 4, 16, 13, // cases 80 - 87 + 11, 16, 9, 8, 16, 13, 8, 7, // cases 88 - 95 + 3, 18, 15, 17, 15, 16, 2, 15, // cases 96 - 103 + 18, 4, 16, 13, 17, 13, 15, 3, // cases 104 - 111 + 15, 17, 10, 15, 17, 13, 15, 3, // cases 112 - 119 + 16, 13, 8, 7, 13, 8, 7, 10, // cases 120 - 127 + 10, 18, 8, 18, 18, 11, 18, 16, // cases 128 - 135 + 3, 15, 18, 17, 15, 10, 17, 15, // cases 136 - 143 + 3, 15, 18, 17, 18, 16, 4, 13, // cases 144 - 151 + 15, 2, 16, 15, 17, 15, 13, 3, // cases 152 - 159 + 18, 11, 18, 16, 11, 9, 16, 8, // cases 160 - 167 + 18, 16, 4, 13, 16, 8, 13, 7, // cases 168 - 175 + 15, 10, 17, 15, 16, 8, 13, 7, // cases 176 - 183 + 17, 15, 13, 3, 13, 7, 8, 10, // cases 184 - 191 + 3, 18, 18, 4, 15, 16, 17, 13, // cases 192 - 199 + 15, 17, 16, 13, 2, 15, 15, 3, // cases 200 - 207 + 15, 17, 16, 13, 17, 13, 13, 8, // cases 208 - 215 + 10, 15, 8, 7, 15, 3, 7, 10, // cases 216 - 223 + 15, 16, 17, 13, 10, 8, 15, 7, // cases 224 - 231 + 17, 13, 13, 8, 15, 7, 3, 10, // cases 232 - 239 + 2, 15, 15, 3, 15, 7, 3, 10, // cases 240 - 247 + 15, 3, 7, 10, 3, 10, 10, 1 // cases 248 - 255 +}; + +int startClipShapesHex[256] = { + 0, 10, 80, 150, 176, 246, 361, 387, // cases 0 - 7 + 488, 558, 584, 699, 800, 826, 927, 1028, // cases 8 - 15 + 1048, 1118, 1144, 1259, 1360, 1412, 1531, 1650, // cases 16 - 23 + 1764, 1879, 1980, 2056, 2124, 2243, 2357, 2465, // cases 24 - 31 + 2566, 2636, 2751, 2777, 2878, 2993, 3069, 3170, // cases 32 - 39 + 3238, 3290, 3409, 3528, 3642, 3761, 3869, 3983, // cases 40 - 47 + 4084, 4110, 4211, 4312, 4332, 4451, 4559, 4673, // cases 48 - 55 + 4774, 4893, 5007, 5115, 5216, 5252, 5343, 5434, // cases 56 - 63 + 5460, 5530, 5582, 5697, 5816, 5842, 5961, 6062, // cases 64 - 71 + 6176, 6291, 6410, 6486, 6594, 6695, 6809, 6877, // cases 72 - 79 + 6978, 7093, 7212, 7288, 7396, 7515, 7551, 7659, // cases 80 - 87 + 7750, 7826, 7934, 7996, 8050, 8158, 8249, 8303, // cases 88 - 95 + 8350, 8376, 8495, 8596, 8710, 8811, 8919, 8939, // cases 96 - 103 + 9040, 9159, 9195, 9303, 9394, 9508, 9599, 9700, // cases 104 - 111 + 9726, 9827, 9941, 10009, 10110, 10224, 10315, 10416, // cases 112 - 119 + 10442, 10550, 10641, 10695, 10742, 10833, 10885, 10932, // cases 120 - 127 + 11002, 11072, 11187, 11239, 11358, 11473, 11549, 11668, // cases 128 - 135 + 11776, 11802, 11903, 12022, 12136, 12237, 12305, 12419, // cases 136 - 143 + 12520, 12546, 12647, 12766, 12880, 12999, 13107, 13143, // cases 144 - 151 + 13234, 13335, 13355, 13463, 13564, 13678, 13779, 13870, // cases 152 - 159 + 13896, 14011, 14087, 14206, 14314, 14390, 14452, 14560, // cases 160 - 167 + 14614, 14733, 14841, 14877, 14968, 15076, 15130, 15221, // cases 168 - 175 + 15268, 15369, 15437, 15551, 15652, 15760, 15814, 15905, // cases 176 - 183 + 15952, 16066, 16167, 16258, 16284, 16375, 16422, 16474, // cases 184 - 191 + 16544, 16570, 16689, 16808, 16844, 16945, 17053, 17167, // cases 192 - 199 + 17258, 17359, 17473, 17581, 17672, 17692, 17793, 17894, // cases 200 - 207 + 17920, 18021, 18135, 18243, 18334, 18448, 18539, 18630, // cases 208 - 215 + 18682, 18750, 18851, 18905, 18952, 19053, 19079, 19126, // cases 216 - 223 + 19196, 19297, 19405, 19519, 19610, 19678, 19732, 19833, // cases 224 - 231 + 19880, 19994, 20085, 20176, 20228, 20329, 20376, 20402, // cases 232 - 239 + 20472, 20492, 20593, 20694, 20720, 20821, 20868, 20894, // cases 240 - 247 + 20964, 21065, 21091, 21138, 21208, 21234, 21304, 21374 // cases 248 - 255 +}; + +unsigned char clipShapesHex[] = { + // Case #0: Unique case #1 + ST_HEX, COLOR0, P0, P1, P2, P3, P4, P5, P6, P7, + // Case #1: Unique case #2 + ST_PNT, 0, COLOR0, 7, P1, P2, P3, P4, P5, P6, P7, + ST_WDG, COLOR0, P1, P3, P4, EA, ED, EI, + ST_TET, COLOR0, P1, P3, P4, N0, + ST_TET, COLOR0, P1, P2, P3, N0, + ST_PYR, COLOR0, P6, P7, P3, P2, N0, + ST_PYR, COLOR0, P5, P6, P2, P1, N0, + ST_PYR, COLOR0, P4, P7, P6, P5, N0, + ST_TET, COLOR0, P3, P7, P4, N0, + ST_TET, COLOR0, P4, P5, P1, N0, + ST_TET, COLOR1, P0, EA, ED, EI, + // Case #2: (cloned #1) + ST_PNT, 0, COLOR0, 7, P5, P4, P0, P2, P6, P7, P3, + ST_WDG, COLOR0, EJ, EA, EB, P5, P0, P2, + ST_TET, COLOR0, P5, P2, P0, N0, + ST_TET, COLOR0, P5, P0, P4, N0, + ST_PYR, COLOR0, P7, P4, P0, P3, N0, + ST_PYR, COLOR0, P6, P5, P4, P7, N0, + ST_PYR, COLOR0, P2, P6, P7, P3, N0, + ST_TET, COLOR0, P0, P2, P3, N0, + ST_TET, COLOR0, P2, P5, P6, N0, + ST_TET, COLOR1, P1, EA, EJ, EB, + // Case #3: Unique case #3 + ST_HEX, COLOR0, EB, P2, P3, ED, EJ, P5, P4, EI, + ST_WDG, COLOR0, P2, P6, P5, P3, P7, P4, + ST_WDG, COLOR1, P1, EB, EJ, P0, ED, EI, + // Case #4: (cloned #1) + ST_PNT, 0, COLOR0, 7, P6, P5, P1, P3, P7, P4, P0, + ST_WDG, COLOR0, EL, EB, EC, P6, P1, P3, + ST_TET, COLOR0, P6, P3, P1, N0, + ST_TET, COLOR0, P6, P1, P5, N0, + ST_PYR, COLOR0, P4, P5, P1, P0, N0, + ST_PYR, COLOR0, P7, P6, P5, P4, N0, + ST_PYR, COLOR0, P3, P7, P4, P0, N0, + ST_TET, COLOR0, P1, P3, P0, N0, + ST_TET, COLOR0, P3, P6, P7, N0, + ST_TET, COLOR1, P2, EB, EL, EC, + // Case #5: Unique case #4 + ST_PNT, 0, NOCOLOR, 2, EI, EL, + ST_PYR, COLOR0, P4, P7, P6, P5, N0, + ST_TET, COLOR0, P5, P6, P1, N0, + ST_TET, COLOR0, P4, P5, P1, N0, + ST_TET, COLOR0, P3, P7, P4, N0, + ST_TET, COLOR0, P6, P7, P3, N0, + ST_PYR, COLOR0, P6, P3, EC, EL, N0, + ST_PYR, COLOR0, P1, P6, EL, EB, N0, + ST_TET, COLOR0, P1, EB, EA, N0, + ST_PYR, COLOR0, P4, P1, EA, EI, N0, + ST_PYR, COLOR0, P4, EI, ED, P3, N0, + ST_TET, COLOR0, P3, ED, EC, N0, + ST_PYR, COLOR1, P0, P2, EC, ED, N0, + ST_PYR, COLOR1, EA, EB, P2, P0, N0, + ST_TET, COLOR1, EB, EL, P2, N0, + ST_TET, COLOR1, P2, EL, EC, N0, + ST_TET, COLOR1, EA, N0, P0, EI, + ST_TET, COLOR1, ED, EI, P0, N0, + // Case #6: (cloned #3) + ST_HEX, COLOR0, EC, P3, P0, EA, EL, P6, P5, EJ, + ST_WDG, COLOR0, P3, P7, P6, P0, P4, P5, + ST_WDG, COLOR1, P2, EC, EL, P1, EA, EJ, + // Case #7: Unique case #5 + ST_PNT, 0, NOCOLOR, 5, EI, EJ, ED, EC, EL, + ST_PYR, COLOR0, P4, P7, P6, P5, N0, + ST_TET, COLOR0, P6, P3, N0, P7, + ST_PYR, COLOR0, P5, P6, EL, EJ, N0, + ST_PYR, COLOR0, EI, P4, P5, EJ, N0, + ST_TET, COLOR0, P3, P7, P4, N0, + ST_PYR, COLOR0, P3, P4, EI, ED, N0, + ST_TET, COLOR0, P3, ED, EC, N0, + ST_PYR, COLOR0, EL, P6, P3, EC, N0, + ST_PYR, COLOR1, EJ, EL, P2, P1, N0, + ST_PYR, COLOR1, EI, EJ, P1, P0, N0, + ST_TET, COLOR1, ED, EI, P0, N0, + ST_TET, COLOR1, P0, P1, P2, N0, + ST_PYR, COLOR1, ED, P0, P2, EC, N0, + ST_TET, COLOR1, P2, EL, EC, N0, + // Case #8: (cloned #1) + ST_PNT, 0, COLOR0, 7, P2, P1, P0, P7, P6, P5, P4, + ST_WDG, COLOR0, EC, ED, EK, P2, P0, P7, + ST_TET, COLOR0, P2, P7, P0, N0, + ST_TET, COLOR0, P2, P0, P1, N0, + ST_PYR, COLOR0, P5, P1, P0, P4, N0, + ST_PYR, COLOR0, P6, P2, P1, P5, N0, + ST_PYR, COLOR0, P7, P6, P5, P4, N0, + ST_TET, COLOR0, P0, P7, P4, N0, + ST_TET, COLOR0, P7, P2, P6, N0, + ST_TET, COLOR1, P3, ED, EC, EK, + // Case #9: (cloned #3) + ST_HEX, COLOR0, EK, P7, P4, EI, EC, P2, P1, EA, + ST_WDG, COLOR0, P7, P6, P2, P4, P5, P1, + ST_WDG, COLOR1, P3, EK, EC, P0, EI, EA, + // Case #10: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, EK, EJ, + ST_PYR, COLOR0, P7, P6, P5, P4, N0, + ST_TET, COLOR0, P6, P2, P5, N0, + ST_TET, COLOR0, P7, P2, P6, N0, + ST_TET, COLOR0, P0, P7, P4, N0, + ST_TET, COLOR0, P5, P0, P4, N0, + ST_PYR, COLOR0, P5, EJ, EA, P0, N0, + ST_PYR, COLOR0, P2, EB, EJ, P5, N0, + ST_TET, COLOR0, P2, EC, EB, N0, + ST_PYR, COLOR0, P7, EK, EC, P2, N0, + ST_PYR, COLOR0, P7, P0, ED, EK, N0, + ST_TET, COLOR0, P0, EA, ED, N0, + ST_PYR, COLOR1, P3, ED, EA, P1, N0, + ST_PYR, COLOR1, EC, P3, P1, EB, N0, + ST_TET, COLOR1, EB, P1, EJ, N0, + ST_TET, COLOR1, P1, EA, EJ, N0, + ST_TET, COLOR1, EC, P3, N0, EK, + ST_TET, COLOR1, ED, P3, EK, N0, + // Case #11: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EJ, EI, EB, EC, EK, + ST_PYR, COLOR0, P5, P4, P7, P6, N0, + ST_TET, COLOR0, P7, N0, P2, P6, + ST_PYR, COLOR0, P4, EI, EK, P7, N0, + ST_PYR, COLOR0, EJ, EI, P4, P5, N0, + ST_TET, COLOR0, P2, P5, P6, N0, + ST_PYR, COLOR0, P2, EB, EJ, P5, N0, + ST_TET, COLOR0, P2, EC, EB, N0, + ST_PYR, COLOR0, EK, EC, P2, P7, N0, + ST_PYR, COLOR1, EI, P0, P3, EK, N0, + ST_PYR, COLOR1, EJ, P1, P0, EI, N0, + ST_TET, COLOR1, EB, P1, EJ, N0, + ST_TET, COLOR1, P1, P3, P0, N0, + ST_PYR, COLOR1, EB, EC, P3, P1, N0, + ST_TET, COLOR1, P3, EC, EK, N0, + // Case #12: (cloned #3) + ST_HEX, COLOR0, EL, P6, P7, EK, EB, P1, P0, ED, + ST_WDG, COLOR0, P0, P4, P7, P1, P5, P6, + ST_WDG, COLOR1, P3, ED, EK, P2, EB, EL, + // Case #13: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EI, EK, EA, EB, EL, + ST_PYR, COLOR0, P4, P7, P6, P5, N0, + ST_TET, COLOR0, P6, N0, P1, P5, + ST_PYR, COLOR0, P7, EK, EL, P6, N0, + ST_PYR, COLOR0, EI, EK, P7, P4, N0, + ST_TET, COLOR0, P1, P4, P5, N0, + ST_PYR, COLOR0, P1, EA, EI, P4, N0, + ST_TET, COLOR0, P1, EB, EA, N0, + ST_PYR, COLOR0, EL, EB, P1, P6, N0, + ST_PYR, COLOR1, EK, P3, P2, EL, N0, + ST_PYR, COLOR1, EI, P0, P3, EK, N0, + ST_TET, COLOR1, EA, P0, EI, N0, + ST_TET, COLOR1, P0, P2, P3, N0, + ST_PYR, COLOR1, EA, EB, P2, P0, N0, + ST_TET, COLOR1, P2, EB, EL, N0, + // Case #14: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EK, EL, ED, EA, EJ, + ST_PYR, COLOR0, P7, P6, P5, P4, N0, + ST_TET, COLOR0, P5, N0, P0, P4, + ST_PYR, COLOR0, P6, EL, EJ, P5, N0, + ST_PYR, COLOR0, EK, EL, P6, P7, N0, + ST_TET, COLOR0, P0, P7, P4, N0, + ST_PYR, COLOR0, P0, ED, EK, P7, N0, + ST_TET, COLOR0, P0, EA, ED, N0, + ST_PYR, COLOR0, EJ, EA, P0, P5, N0, + ST_PYR, COLOR1, EL, P2, P1, EJ, N0, + ST_PYR, COLOR1, EK, P3, P2, EL, N0, + ST_TET, COLOR1, ED, P3, EK, N0, + ST_TET, COLOR1, P3, P1, P2, N0, + ST_PYR, COLOR1, ED, EA, P1, P3, N0, + ST_TET, COLOR1, P1, EA, EJ, N0, + // Case #15: Unique case #6 + ST_HEX, COLOR0, EI, EJ, EL, EK, P4, P5, P6, P7, + ST_HEX, COLOR1, P0, P1, P2, P3, EI, EJ, EL, EK, + // Case #16: (cloned #1) + ST_PNT, 0, COLOR0, 7, P5, P1, P0, P7, P6, P2, P3, + ST_WDG, COLOR0, P5, P0, P7, EE, EI, EH, + ST_TET, COLOR0, P5, P0, P7, N0, + ST_TET, COLOR0, P5, P1, P0, N0, + ST_PYR, COLOR0, P2, P3, P0, P1, N0, + ST_PYR, COLOR0, P6, P2, P1, P5, N0, + ST_PYR, COLOR0, P7, P3, P2, P6, N0, + ST_TET, COLOR0, P0, P3, P7, N0, + ST_TET, COLOR0, P7, P6, P5, N0, + ST_TET, COLOR1, P4, EE, EI, EH, + // Case #17: (cloned #3) + ST_HEX, COLOR0, EE, P5, P1, EA, EH, P7, P3, ED, + ST_WDG, COLOR0, P3, P2, P1, P7, P6, P5, + ST_WDG, COLOR1, P0, ED, EA, P4, EH, EE, + // Case #18: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, EH, EB, + ST_PYR, COLOR0, P7, P3, P2, P6, N0, + ST_TET, COLOR0, P6, P2, P5, N0, + ST_TET, COLOR0, P7, P6, P5, N0, + ST_TET, COLOR0, P0, P3, P7, N0, + ST_TET, COLOR0, P2, P3, P0, N0, + ST_PYR, COLOR0, P2, P0, EA, EB, N0, + ST_PYR, COLOR0, P5, P2, EB, EJ, N0, + ST_TET, COLOR0, P5, EJ, EE, N0, + ST_PYR, COLOR0, P7, P5, EE, EH, N0, + ST_PYR, COLOR0, P7, EH, EI, P0, N0, + ST_TET, COLOR0, P0, EI, EA, N0, + ST_PYR, COLOR1, P4, P1, EA, EI, N0, + ST_PYR, COLOR1, EE, EJ, P1, P4, N0, + ST_TET, COLOR1, EJ, EB, P1, N0, + ST_TET, COLOR1, P1, EB, EA, N0, + ST_TET, COLOR1, EE, N0, P4, EH, + ST_TET, COLOR1, EI, EH, P4, N0, + // Case #19: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EB, ED, EJ, EE, EH, + ST_PYR, COLOR0, P2, P6, P7, P3, N0, + ST_TET, COLOR0, P7, P5, N0, P6, + ST_PYR, COLOR0, P3, P7, EH, ED, N0, + ST_PYR, COLOR0, EB, P2, P3, ED, N0, + ST_TET, COLOR0, P5, P6, P2, N0, + ST_PYR, COLOR0, P5, P2, EB, EJ, N0, + ST_TET, COLOR0, P5, EJ, EE, N0, + ST_PYR, COLOR0, EH, P7, P5, EE, N0, + ST_PYR, COLOR1, ED, EH, P4, P0, N0, + ST_PYR, COLOR1, EB, ED, P0, P1, N0, + ST_TET, COLOR1, EJ, EB, P1, N0, + ST_TET, COLOR1, P1, P0, P4, N0, + ST_PYR, COLOR1, EJ, P1, P4, EE, N0, + ST_TET, COLOR1, P4, EH, EE, N0, + // Case #20: Unique case #7 + ST_WDG, COLOR0, EB, EC, EL, P1, P3, P6, + ST_WDG, COLOR0, P0, P7, P5, EI, EH, EE, + ST_TET, COLOR0, P3, P1, P6, P7, + ST_TET, COLOR0, P5, P7, P6, P1, + ST_TET, COLOR0, P0, P5, P1, P7, + ST_TET, COLOR0, P3, P7, P0, P1, + ST_TET, COLOR1, P4, EE, EI, EH, + ST_TET, COLOR1, P2, EC, EB, EL, + // Case #21: Unique case #8 + ST_PNT, 0, NOCOLOR, 4, EE, EH, EL, EL, + ST_PYR, COLOR0, P6, P3, EC, EL, N0, + ST_TET, COLOR0, EC, P3, ED, N0, + ST_PYR, COLOR0, P7, EH, ED, P3, N0, + ST_TET, COLOR0, P6, P7, P3, N0, + ST_TET, COLOR0, P1, EB, EA, N0, + ST_TET, COLOR0, P5, P6, P1, N0, + ST_PYR, COLOR0, P1, P6, EL, EB, N0, + ST_TET, COLOR0, P5, P7, P6, N0, + ST_PYR, COLOR0, P5, EE, EH, P7, N0, + ST_PYR, COLOR0, P5, P1, EA, EE, N0, + ST_PYR, COLOR1, P2, EC, ED, P0, N0, + ST_PYR, COLOR1, EA, EB, P2, P0, N0, + ST_TET, COLOR1, P2, EL, EC, N0, + ST_TET, COLOR1, EB, EL, P2, N0, + ST_PYR, COLOR1, ED, EH, P4, P0, N0, + ST_PYR, COLOR1, P0, P4, EE, EA, N0, + ST_TET, COLOR1, EE, P4, EH, N0, + // Case #22: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EL, EC, EH, EH, + ST_PYR, COLOR0, P7, EH, EI, P0, N0, + ST_TET, COLOR0, EI, EA, P0, N0, + ST_PYR, COLOR0, P3, P0, EA, EC, N0, + ST_TET, COLOR0, P7, P0, P3, N0, + ST_TET, COLOR0, P5, EJ, EE, N0, + ST_TET, COLOR0, P6, P5, P7, N0, + ST_PYR, COLOR0, P5, EE, EH, P7, N0, + ST_TET, COLOR0, P6, P7, P3, N0, + ST_PYR, COLOR0, P6, P3, EC, EL, N0, + ST_PYR, COLOR0, P6, EL, EJ, P5, N0, + ST_PYR, COLOR1, P4, P1, EA, EI, N0, + ST_PYR, COLOR1, EJ, P1, P4, EE, N0, + ST_TET, COLOR1, P4, EI, EH, N0, + ST_TET, COLOR1, EE, P4, EH, N0, + ST_PYR, COLOR1, EA, P1, P2, EC, N0, + ST_PYR, COLOR1, P1, EJ, EL, P2, N0, + ST_TET, COLOR1, EL, EC, P2, N0, + // Case #23: Unique case #9 + ST_PNT, 0, NOCOLOR, 6, ED, EC, EL, EJ, EE, EH, + ST_TET, COLOR0, P6, P5, P7, N0, + ST_PYR, COLOR0, P6, EL, EJ, P5, N0, + ST_TET, COLOR0, P3, P6, P7, N0, + ST_PYR, COLOR0, P3, EC, EL, P6, N0, + ST_TET, COLOR0, ED, EC, P3, N0, + ST_PYR, COLOR0, P3, P7, EH, ED, N0, + ST_PYR, COLOR0, EH, P7, P5, EE, N0, + ST_TET, COLOR0, P5, EJ, EE, N0, + ST_TET, COLOR1, P0, P1, P2, N0, + ST_PYR, COLOR1, ED, P0, P2, EC, N0, + ST_PYR, COLOR1, ED, EH, P4, P0, N0, + ST_TET, COLOR1, P4, P1, P0, N0, + ST_TET, COLOR1, P4, EH, EE, N0, + ST_PYR, COLOR1, P4, EE, EJ, P1, N0, + ST_PYR, COLOR1, EJ, EL, P2, P1, N0, + ST_TET, COLOR1, EL, EC, P2, N0, + // Case #24: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, EC, EE, + ST_PYR, COLOR0, P2, P1, P5, P6, N0, + ST_TET, COLOR0, P6, P5, P7, N0, + ST_TET, COLOR0, P2, P6, P7, N0, + ST_TET, COLOR0, P0, P1, P2, N0, + ST_TET, COLOR0, P5, P1, P0, N0, + ST_PYR, COLOR0, P5, P0, EI, EE, N0, + ST_PYR, COLOR0, P7, P5, EE, EH, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_PYR, COLOR0, P2, P7, EK, EC, N0, + ST_PYR, COLOR0, P2, EC, ED, P0, N0, + ST_TET, COLOR0, P0, ED, EI, N0, + ST_PYR, COLOR1, P3, P4, EI, ED, N0, + ST_PYR, COLOR1, EK, EH, P4, P3, N0, + ST_TET, COLOR1, EH, EE, P4, N0, + ST_TET, COLOR1, P4, EE, EI, N0, + ST_TET, COLOR1, EK, N0, P3, EC, + ST_TET, COLOR1, ED, EC, P3, N0, + // Case #25: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EE, EA, EH, EK, EC, + ST_PYR, COLOR0, P5, P6, P2, P1, N0, + ST_TET, COLOR0, P2, P7, N0, P6, + ST_PYR, COLOR0, P1, P2, EC, EA, N0, + ST_PYR, COLOR0, EE, P5, P1, EA, N0, + ST_TET, COLOR0, P7, P6, P5, N0, + ST_PYR, COLOR0, P7, P5, EE, EH, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_PYR, COLOR0, EC, P2, P7, EK, N0, + ST_PYR, COLOR1, EA, EC, P3, P0, N0, + ST_PYR, COLOR1, EE, EA, P0, P4, N0, + ST_TET, COLOR1, EH, EE, P4, N0, + ST_TET, COLOR1, P4, P0, P3, N0, + ST_PYR, COLOR1, EH, P4, P3, EK, N0, + ST_TET, COLOR1, P3, EC, EK, N0, + // Case #26: Unique case #10 + ST_TET, COLOR0, P0, EA, ED, EI, + ST_TET, COLOR0, P5, P7, P6, P2, + ST_PYR, COLOR0, EC, P2, P7, EK, EH, + ST_PYR, COLOR0, EB, EJ, P5, P2, EE, + ST_PYR, COLOR0, P7, P5, EE, EH, P2, + ST_PYR, COLOR0, EH, EE, EB, EC, P2, + ST_WDG, COLOR1, ED, EA, EI, P3, P1, P4, + ST_PYR, COLOR1, P3, EK, EH, P4, EC, + ST_PYR, COLOR1, EE, EJ, P1, P4, EB, + ST_PYR, COLOR1, EC, P3, P1, EB, P4, + ST_PYR, COLOR1, EC, EB, EE, EH, P4, + // Case #27: Unique case #11 + ST_TET, COLOR0, P5, P7, P6, P2, + ST_PYR, COLOR0, EC, P2, P7, EK, EH, + ST_PYR, COLOR0, EB, EJ, P5, P2, EE, + ST_PYR, COLOR0, P7, P5, EE, EH, P2, + ST_PYR, COLOR0, EH, EE, EB, EC, P2, + ST_TET, COLOR1, P0, P1, P3, P4, + ST_PYR, COLOR1, EH, P4, P3, EK, EC, + ST_PYR, COLOR1, EE, EJ, P1, P4, EB, + ST_PYR, COLOR1, P3, P1, EB, EC, P4, + ST_PYR, COLOR1, EH, EC, EB, EE, P4, + // Case #28: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EL, EB, EE, EE, + ST_PYR, COLOR0, P5, P0, EI, EE, N0, + ST_TET, COLOR0, EI, P0, ED, N0, + ST_PYR, COLOR0, P1, EB, ED, P0, N0, + ST_TET, COLOR0, P5, P1, P0, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_TET, COLOR0, P6, P5, P7, N0, + ST_PYR, COLOR0, P7, P5, EE, EH, N0, + ST_TET, COLOR0, P6, P1, P5, N0, + ST_PYR, COLOR0, P6, EL, EB, P1, N0, + ST_PYR, COLOR0, P6, P7, EK, EL, N0, + ST_PYR, COLOR1, P4, EI, ED, P3, N0, + ST_PYR, COLOR1, EK, EH, P4, P3, N0, + ST_TET, COLOR1, P4, EE, EI, N0, + ST_TET, COLOR1, EH, EE, P4, N0, + ST_PYR, COLOR1, ED, EB, P2, P3, N0, + ST_PYR, COLOR1, P3, P2, EL, EK, N0, + ST_TET, COLOR1, EL, P2, EB, N0, + // Case #29: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EA, EB, EL, EK, EH, EE, + ST_TET, COLOR0, P6, P5, P7, N0, + ST_PYR, COLOR0, P6, P7, EK, EL, N0, + ST_TET, COLOR0, P1, P5, P6, N0, + ST_PYR, COLOR0, P1, P6, EL, EB, N0, + ST_TET, COLOR0, EA, P1, EB, N0, + ST_PYR, COLOR0, P1, EA, EE, P5, N0, + ST_PYR, COLOR0, EE, EH, P7, P5, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_TET, COLOR1, P0, P2, P3, N0, + ST_PYR, COLOR1, EA, EB, P2, P0, N0, + ST_PYR, COLOR1, EA, P0, P4, EE, N0, + ST_TET, COLOR1, P4, P0, P3, N0, + ST_TET, COLOR1, P4, EH, EE, N0, + ST_PYR, COLOR1, P4, P3, EK, EH, N0, + ST_PYR, COLOR1, EK, P3, P2, EL, N0, + ST_TET, COLOR1, EL, P2, EB, N0, + // Case #30: Unique case #12 + ST_PNT, 0, NOCOLOR, 5, EL, EJ, EK, EH, EE, + ST_TET, COLOR0, P0, EA, ED, EI, + ST_PYR, COLOR0, P5, P6, EL, EJ, N0, + ST_PYR, COLOR0, P6, P7, EK, EL, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_TET, COLOR0, P6, P5, P7, N0, + ST_PYR, COLOR0, P7, P5, EE, EH, N0, + ST_TET, COLOR0, P5, EJ, EE, N0, + ST_WDG, COLOR1, ED, EA, EI, P3, P1, P4, + ST_TET, COLOR1, P1, P3, P4, N0, + ST_PYR, COLOR1, P3, EK, EH, P4, N0, + ST_TET, COLOR1, P2, P3, P1, N0, + ST_PYR, COLOR1, EJ, EL, P2, P1, N0, + ST_PYR, COLOR1, EL, EK, P3, P2, N0, + ST_PYR, COLOR1, P4, EE, EJ, P1, N0, + ST_TET, COLOR1, EH, EE, P4, N0, + // Case #31: Unique case #13 + ST_PNT, 0, NOCOLOR, 5, EJ, EL, EK, EE, EH, + ST_PYR, COLOR0, P6, P7, EK, EL, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_PYR, COLOR0, P5, P6, EL, EJ, N0, + ST_TET, COLOR0, EE, P5, EJ, N0, + ST_PYR, COLOR0, EH, P7, P5, EE, N0, + ST_TET, COLOR0, P7, P6, P5, N0, + ST_PYR, COLOR1, P0, P1, P2, P3, N0, + ST_TET, COLOR1, P3, P4, P0, N0, + ST_TET, COLOR1, P4, P1, P0, N0, + ST_PYR, COLOR1, P4, EE, EJ, P1, N0, + ST_PYR, COLOR1, EJ, EL, P2, P1, N0, + ST_PYR, COLOR1, EL, EK, P3, P2, N0, + ST_PYR, COLOR1, EK, EH, P4, P3, N0, + ST_TET, COLOR1, EE, P4, EH, N0, + // Case #32: (cloned #1) + ST_PNT, 0, COLOR0, 7, P6, P2, P1, P4, P7, P3, P0, + ST_WDG, COLOR0, P6, P1, P4, EF, EJ, EE, + ST_TET, COLOR0, P6, P1, P4, N0, + ST_TET, COLOR0, P6, P2, P1, N0, + ST_PYR, COLOR0, P3, P0, P1, P2, N0, + ST_PYR, COLOR0, P7, P3, P2, P6, N0, + ST_PYR, COLOR0, P4, P0, P3, P7, N0, + ST_TET, COLOR0, P1, P0, P4, N0, + ST_TET, COLOR0, P4, P7, P6, N0, + ST_TET, COLOR1, P5, EF, EJ, EE, + // Case #33: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, ED, EF, + ST_PYR, COLOR0, P3, P2, P6, P7, N0, + ST_TET, COLOR0, P2, P1, P6, N0, + ST_TET, COLOR0, P3, P1, P2, N0, + ST_TET, COLOR0, P4, P3, P7, N0, + ST_TET, COLOR0, P6, P4, P7, N0, + ST_PYR, COLOR0, P6, EF, EE, P4, N0, + ST_PYR, COLOR0, P1, EJ, EF, P6, N0, + ST_TET, COLOR0, P1, EA, EJ, N0, + ST_PYR, COLOR0, P3, ED, EA, P1, N0, + ST_PYR, COLOR0, P3, P4, EI, ED, N0, + ST_TET, COLOR0, P4, EE, EI, N0, + ST_PYR, COLOR1, P0, EI, EE, P5, N0, + ST_PYR, COLOR1, EA, P0, P5, EJ, N0, + ST_TET, COLOR1, EJ, P5, EF, N0, + ST_TET, COLOR1, P5, EE, EF, N0, + ST_TET, COLOR1, EA, P0, N0, ED, + ST_TET, COLOR1, EI, P0, ED, N0, + // Case #34: (cloned #3) + ST_HEX, COLOR0, EF, P6, P2, EB, EE, P4, P0, EA, + ST_WDG, COLOR0, P0, P3, P2, P4, P7, P6, + ST_WDG, COLOR1, P1, EA, EB, P5, EE, EF, + // Case #35: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, ED, EB, EI, EE, EF, + ST_PYR, COLOR0, P3, P2, P6, P7, N0, + ST_TET, COLOR0, P6, N0, P4, P7, + ST_PYR, COLOR0, P2, EB, EF, P6, N0, + ST_PYR, COLOR0, ED, EB, P2, P3, N0, + ST_TET, COLOR0, P4, P3, P7, N0, + ST_PYR, COLOR0, P4, EI, ED, P3, N0, + ST_TET, COLOR0, P4, EE, EI, N0, + ST_PYR, COLOR0, EF, EE, P4, P6, N0, + ST_PYR, COLOR1, EB, P1, P5, EF, N0, + ST_PYR, COLOR1, ED, P0, P1, EB, N0, + ST_TET, COLOR1, EI, P0, ED, N0, + ST_TET, COLOR1, P0, P5, P1, N0, + ST_PYR, COLOR1, EI, EE, P5, P0, N0, + ST_TET, COLOR1, P5, EE, EF, N0, + // Case #36: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, EC, EE, + ST_PYR, COLOR0, P3, P7, P4, P0, N0, + ST_TET, COLOR0, P7, P6, P4, N0, + ST_TET, COLOR0, P3, P6, P7, N0, + ST_TET, COLOR0, P1, P3, P0, N0, + ST_TET, COLOR0, P4, P1, P0, N0, + ST_PYR, COLOR0, P4, EE, EJ, P1, N0, + ST_PYR, COLOR0, P6, EF, EE, P4, N0, + ST_TET, COLOR0, P6, EL, EF, N0, + ST_PYR, COLOR0, P3, EC, EL, P6, N0, + ST_PYR, COLOR0, P3, P1, EB, EC, N0, + ST_TET, COLOR0, P1, EJ, EB, N0, + ST_PYR, COLOR1, P2, EB, EJ, P5, N0, + ST_PYR, COLOR1, EL, P2, P5, EF, N0, + ST_TET, COLOR1, EF, P5, EE, N0, + ST_TET, COLOR1, P5, EJ, EE, N0, + ST_TET, COLOR1, EL, P2, N0, EC, + ST_TET, COLOR1, EB, P2, EC, N0, + // Case #37: (cloned #26) + ST_TET, COLOR0, P1, EA, EJ, EB, + ST_TET, COLOR0, P6, P7, P3, P4, + ST_PYR, COLOR0, EI, ED, P3, P4, EC, + ST_PYR, COLOR0, EE, P4, P6, EF, EL, + ST_PYR, COLOR0, P3, EC, EL, P6, P4, + ST_PYR, COLOR0, EC, EI, EE, EL, P4, + ST_WDG, COLOR1, P0, P5, P2, EA, EJ, EB, + ST_PYR, COLOR1, P0, P2, EC, ED, EI, + ST_PYR, COLOR1, EL, P2, P5, EF, EE, + ST_PYR, COLOR1, EI, EE, P5, P0, P2, + ST_PYR, COLOR1, EI, EC, EL, EE, P2, + // Case #38: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EE, EA, EF, EL, EC, + ST_PYR, COLOR0, P4, P0, P3, P7, N0, + ST_TET, COLOR0, P3, N0, P6, P7, + ST_PYR, COLOR0, P0, EA, EC, P3, N0, + ST_PYR, COLOR0, EE, EA, P0, P4, N0, + ST_TET, COLOR0, P6, P4, P7, N0, + ST_PYR, COLOR0, P6, EF, EE, P4, N0, + ST_TET, COLOR0, P6, EL, EF, N0, + ST_PYR, COLOR0, EC, EL, P6, P3, N0, + ST_PYR, COLOR1, EA, P1, P2, EC, N0, + ST_PYR, COLOR1, EE, P5, P1, EA, N0, + ST_TET, COLOR1, EF, P5, EE, N0, + ST_TET, COLOR1, P5, P2, P1, N0, + ST_PYR, COLOR1, EF, EL, P2, P5, N0, + ST_TET, COLOR1, P2, EL, EC, N0, + // Case #39: (cloned #27) + ST_TET, COLOR0, P6, P7, P3, P4, + ST_PYR, COLOR0, EI, ED, P3, P4, EC, + ST_PYR, COLOR0, EE, P4, P6, EF, EL, + ST_PYR, COLOR0, P3, EC, EL, P6, P4, + ST_PYR, COLOR0, EC, EI, EE, EL, P4, + ST_TET, COLOR1, P1, P0, P5, P2, + ST_PYR, COLOR1, EC, ED, P0, P2, EI, + ST_PYR, COLOR1, EL, P2, P5, EF, EE, + ST_PYR, COLOR1, P0, EI, EE, P5, P2, + ST_PYR, COLOR1, EC, EL, EE, EI, P2, + // Case #40: (cloned #20) + ST_WDG, COLOR0, P1, P4, P6, EJ, EE, EF, + ST_WDG, COLOR0, ED, EK, EC, P0, P7, P2, + ST_TET, COLOR0, P4, P6, P1, P7, + ST_TET, COLOR0, P2, P6, P7, P1, + ST_TET, COLOR0, P0, P1, P2, P7, + ST_TET, COLOR0, P4, P0, P7, P1, + ST_TET, COLOR1, P3, ED, EC, EK, + ST_TET, COLOR1, P5, EJ, EE, EF, + // Case #41: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EC, EK, EF, EF, + ST_PYR, COLOR0, P6, EF, EE, P4, N0, + ST_TET, COLOR0, EE, EI, P4, N0, + ST_PYR, COLOR0, P7, P4, EI, EK, N0, + ST_TET, COLOR0, P6, P4, P7, N0, + ST_TET, COLOR0, P1, EA, EJ, N0, + ST_TET, COLOR0, P2, P1, P6, N0, + ST_PYR, COLOR0, P1, EJ, EF, P6, N0, + ST_TET, COLOR0, P2, P6, P7, N0, + ST_PYR, COLOR0, P2, P7, EK, EC, N0, + ST_PYR, COLOR0, P2, EC, EA, P1, N0, + ST_PYR, COLOR1, P5, P0, EI, EE, N0, + ST_PYR, COLOR1, EA, P0, P5, EJ, N0, + ST_TET, COLOR1, P5, EE, EF, N0, + ST_TET, COLOR1, EJ, P5, EF, N0, + ST_PYR, COLOR1, EI, P0, P3, EK, N0, + ST_PYR, COLOR1, P0, EA, EC, P3, N0, + ST_TET, COLOR1, EC, EK, P3, N0, + // Case #42: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EF, EE, EK, EK, + ST_PYR, COLOR0, P7, P0, ED, EK, N0, + ST_TET, COLOR0, ED, P0, EA, N0, + ST_PYR, COLOR0, P4, EE, EA, P0, N0, + ST_TET, COLOR0, P7, P4, P0, N0, + ST_TET, COLOR0, P2, EC, EB, N0, + ST_TET, COLOR0, P6, P7, P2, N0, + ST_PYR, COLOR0, P2, P7, EK, EC, N0, + ST_TET, COLOR0, P6, P4, P7, N0, + ST_PYR, COLOR0, P6, EF, EE, P4, N0, + ST_PYR, COLOR0, P6, P2, EB, EF, N0, + ST_PYR, COLOR1, P3, ED, EA, P1, N0, + ST_PYR, COLOR1, EB, EC, P3, P1, N0, + ST_TET, COLOR1, P3, EK, ED, N0, + ST_TET, COLOR1, EC, EK, P3, N0, + ST_PYR, COLOR1, EA, EE, P5, P1, N0, + ST_PYR, COLOR1, P1, P5, EF, EB, N0, + ST_TET, COLOR1, EF, P5, EE, N0, + // Case #43: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EI, EE, EF, EB, EC, EK, + ST_TET, COLOR0, P6, P7, P2, N0, + ST_PYR, COLOR0, P6, P2, EB, EF, N0, + ST_TET, COLOR0, P4, P7, P6, N0, + ST_PYR, COLOR0, P4, P6, EF, EE, N0, + ST_TET, COLOR0, EI, P4, EE, N0, + ST_PYR, COLOR0, P4, EI, EK, P7, N0, + ST_PYR, COLOR0, EK, EC, P2, P7, N0, + ST_TET, COLOR0, P2, EC, EB, N0, + ST_TET, COLOR1, P0, P5, P1, N0, + ST_PYR, COLOR1, EI, EE, P5, P0, N0, + ST_PYR, COLOR1, EI, P0, P3, EK, N0, + ST_TET, COLOR1, P3, P0, P1, N0, + ST_TET, COLOR1, P3, EC, EK, N0, + ST_PYR, COLOR1, P3, P1, EB, EC, N0, + ST_PYR, COLOR1, EB, P1, P5, EF, N0, + ST_TET, COLOR1, EF, P5, EE, N0, + // Case #44: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EK, ED, EE, EE, + ST_PYR, COLOR0, P4, EE, EJ, P1, N0, + ST_TET, COLOR0, EJ, EB, P1, N0, + ST_PYR, COLOR0, P0, P1, EB, ED, N0, + ST_TET, COLOR0, P4, P1, P0, N0, + ST_TET, COLOR0, P6, EL, EF, N0, + ST_TET, COLOR0, P7, P6, P4, N0, + ST_PYR, COLOR0, P6, EF, EE, P4, N0, + ST_TET, COLOR0, P7, P4, P0, N0, + ST_PYR, COLOR0, P7, P0, ED, EK, N0, + ST_PYR, COLOR0, P7, EK, EL, P6, N0, + ST_PYR, COLOR1, P5, P2, EB, EJ, N0, + ST_PYR, COLOR1, EL, P2, P5, EF, N0, + ST_TET, COLOR1, P5, EJ, EE, N0, + ST_TET, COLOR1, EF, P5, EE, N0, + ST_PYR, COLOR1, EB, P2, P3, ED, N0, + ST_PYR, COLOR1, P2, EL, EK, P3, N0, + ST_TET, COLOR1, EK, ED, P3, N0, + // Case #45: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EK, EL, EI, EE, EF, + ST_TET, COLOR0, P1, EB, EA, EJ, + ST_PYR, COLOR0, P6, P7, EK, EL, N0, + ST_PYR, COLOR0, P7, P4, EI, EK, N0, + ST_TET, COLOR0, P4, EE, EI, N0, + ST_TET, COLOR0, P7, P6, P4, N0, + ST_PYR, COLOR0, P4, P6, EF, EE, N0, + ST_TET, COLOR0, P6, EL, EF, N0, + ST_WDG, COLOR1, EA, EB, EJ, P0, P2, P5, + ST_TET, COLOR1, P2, P0, P5, N0, + ST_PYR, COLOR1, P0, EI, EE, P5, N0, + ST_TET, COLOR1, P3, P0, P2, N0, + ST_PYR, COLOR1, EL, EK, P3, P2, N0, + ST_PYR, COLOR1, EK, EI, P0, P3, N0, + ST_PYR, COLOR1, P5, EF, EL, P2, N0, + ST_TET, COLOR1, EE, EF, P5, N0, + // Case #46: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EA, ED, EK, EL, EF, EE, + ST_TET, COLOR0, P7, P6, P4, N0, + ST_PYR, COLOR0, P7, EK, EL, P6, N0, + ST_TET, COLOR0, P0, P7, P4, N0, + ST_PYR, COLOR0, P0, ED, EK, P7, N0, + ST_TET, COLOR0, EA, ED, P0, N0, + ST_PYR, COLOR0, P0, P4, EE, EA, N0, + ST_PYR, COLOR0, EE, P4, P6, EF, N0, + ST_TET, COLOR0, P6, EL, EF, N0, + ST_TET, COLOR1, P1, P2, P3, N0, + ST_PYR, COLOR1, EA, P1, P3, ED, N0, + ST_PYR, COLOR1, EA, EE, P5, P1, N0, + ST_TET, COLOR1, P5, P2, P1, N0, + ST_TET, COLOR1, P5, EE, EF, N0, + ST_PYR, COLOR1, P5, EF, EL, P2, N0, + ST_PYR, COLOR1, EL, EK, P3, P2, N0, + ST_TET, COLOR1, EK, ED, P3, N0, + // Case #47: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EL, EK, EI, EF, EE, + ST_PYR, COLOR0, P7, P4, EI, EK, N0, + ST_TET, COLOR0, P4, EE, EI, N0, + ST_PYR, COLOR0, P6, P7, EK, EL, N0, + ST_TET, COLOR0, EF, P6, EL, N0, + ST_PYR, COLOR0, EE, P4, P6, EF, N0, + ST_TET, COLOR0, P4, P7, P6, N0, + ST_PYR, COLOR1, P1, P2, P3, P0, N0, + ST_TET, COLOR1, P0, P5, P1, N0, + ST_TET, COLOR1, P5, P2, P1, N0, + ST_PYR, COLOR1, P5, EF, EL, P2, N0, + ST_PYR, COLOR1, EL, EK, P3, P2, N0, + ST_PYR, COLOR1, EK, EI, P0, P3, N0, + ST_PYR, COLOR1, EI, EE, P5, P0, N0, + ST_TET, COLOR1, EF, P5, EE, N0, + // Case #48: (cloned #3) + ST_HEX, COLOR0, EJ, P1, P0, EI, EF, P6, P7, EH, + ST_WDG, COLOR0, P1, P2, P6, P0, P3, P7, + ST_WDG, COLOR1, P5, EJ, EF, P4, EI, EH, + // Case #49: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, ED, EH, EA, EJ, EF, + ST_PYR, COLOR0, P3, P2, P6, P7, N0, + ST_TET, COLOR0, P6, P1, N0, P2, + ST_PYR, COLOR0, P7, P6, EF, EH, N0, + ST_PYR, COLOR0, ED, P3, P7, EH, N0, + ST_TET, COLOR0, P1, P2, P3, N0, + ST_PYR, COLOR0, P1, P3, ED, EA, N0, + ST_TET, COLOR0, P1, EA, EJ, N0, + ST_PYR, COLOR0, EF, P6, P1, EJ, N0, + ST_PYR, COLOR1, EH, EF, P5, P4, N0, + ST_PYR, COLOR1, ED, EH, P4, P0, N0, + ST_TET, COLOR1, EA, ED, P0, N0, + ST_TET, COLOR1, P0, P4, P5, N0, + ST_PYR, COLOR1, EA, P0, P5, EJ, N0, + ST_TET, COLOR1, P5, EF, EJ, N0, + // Case #50: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EH, EF, EI, EA, EB, + ST_PYR, COLOR0, P7, P3, P2, P6, N0, + ST_TET, COLOR0, P2, P0, N0, P3, + ST_PYR, COLOR0, P6, P2, EB, EF, N0, + ST_PYR, COLOR0, EH, P7, P6, EF, N0, + ST_TET, COLOR0, P0, P3, P7, N0, + ST_PYR, COLOR0, P0, P7, EH, EI, N0, + ST_TET, COLOR0, P0, EI, EA, N0, + ST_PYR, COLOR0, EB, P2, P0, EA, N0, + ST_PYR, COLOR1, EF, EB, P1, P5, N0, + ST_PYR, COLOR1, EH, EF, P5, P4, N0, + ST_TET, COLOR1, EI, EH, P4, N0, + ST_TET, COLOR1, P4, P5, P1, N0, + ST_PYR, COLOR1, EI, P4, P1, EA, N0, + ST_TET, COLOR1, P1, EB, EA, N0, + // Case #51: (cloned #15) + ST_HEX, COLOR0, P3, P2, P6, P7, ED, EB, EF, EH, + ST_HEX, COLOR1, ED, EB, EF, EH, P0, P1, P5, P4, + // Case #52: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EH, EI, EC, EC, + ST_PYR, COLOR0, P3, P1, EB, EC, N0, + ST_TET, COLOR0, EB, P1, EJ, N0, + ST_PYR, COLOR0, P0, EI, EJ, P1, N0, + ST_TET, COLOR0, P3, P0, P1, N0, + ST_TET, COLOR0, P6, EL, EF, N0, + ST_TET, COLOR0, P7, P3, P6, N0, + ST_PYR, COLOR0, P6, P3, EC, EL, N0, + ST_TET, COLOR0, P7, P0, P3, N0, + ST_PYR, COLOR0, P7, EH, EI, P0, N0, + ST_PYR, COLOR0, P7, P6, EF, EH, N0, + ST_PYR, COLOR1, P2, EB, EJ, P5, N0, + ST_PYR, COLOR1, EF, EL, P2, P5, N0, + ST_TET, COLOR1, P2, EC, EB, N0, + ST_TET, COLOR1, EL, EC, P2, N0, + ST_PYR, COLOR1, EJ, EI, P4, P5, N0, + ST_PYR, COLOR1, P5, P4, EH, EF, N0, + ST_TET, COLOR1, EH, P4, EI, N0, + // Case #53: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EH, EF, ED, EC, EL, + ST_TET, COLOR0, P1, EA, EJ, EB, + ST_PYR, COLOR0, P6, EF, EH, P7, N0, + ST_PYR, COLOR0, P7, EH, ED, P3, N0, + ST_TET, COLOR0, P3, ED, EC, N0, + ST_TET, COLOR0, P7, P3, P6, N0, + ST_PYR, COLOR0, P3, EC, EL, P6, N0, + ST_TET, COLOR0, P6, EL, EF, N0, + ST_WDG, COLOR1, P0, P5, P2, EA, EJ, EB, + ST_TET, COLOR1, P5, P2, P0, N0, + ST_PYR, COLOR1, P0, P2, EC, ED, N0, + ST_TET, COLOR1, P4, P5, P0, N0, + ST_PYR, COLOR1, EF, P5, P4, EH, N0, + ST_PYR, COLOR1, EH, P4, P0, ED, N0, + ST_PYR, COLOR1, P2, P5, EF, EL, N0, + ST_TET, COLOR1, EC, P2, EL, N0, + // Case #54: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EA, EI, EH, EF, EL, EC, + ST_TET, COLOR0, P7, P3, P6, N0, + ST_PYR, COLOR0, P7, P6, EF, EH, N0, + ST_TET, COLOR0, P0, P3, P7, N0, + ST_PYR, COLOR0, P0, P7, EH, EI, N0, + ST_TET, COLOR0, EA, P0, EI, N0, + ST_PYR, COLOR0, P0, EA, EC, P3, N0, + ST_PYR, COLOR0, EC, EL, P6, P3, N0, + ST_TET, COLOR0, P6, EL, EF, N0, + ST_TET, COLOR1, P1, P4, P5, N0, + ST_PYR, COLOR1, EA, EI, P4, P1, N0, + ST_PYR, COLOR1, EA, P1, P2, EC, N0, + ST_TET, COLOR1, P2, P1, P5, N0, + ST_TET, COLOR1, P2, EL, EC, N0, + ST_PYR, COLOR1, P2, P5, EF, EL, N0, + ST_PYR, COLOR1, EF, P5, P4, EH, N0, + ST_TET, COLOR1, EH, P4, EI, N0, + // Case #55: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EF, EH, ED, EL, EC, + ST_PYR, COLOR0, P7, EH, ED, P3, N0, + ST_TET, COLOR0, P3, ED, EC, N0, + ST_PYR, COLOR0, P6, EF, EH, P7, N0, + ST_TET, COLOR0, EL, EF, P6, N0, + ST_PYR, COLOR0, EC, EL, P6, P3, N0, + ST_TET, COLOR0, P3, P6, P7, N0, + ST_PYR, COLOR1, P1, P0, P4, P5, N0, + ST_TET, COLOR1, P0, P1, P2, N0, + ST_TET, COLOR1, P2, P1, P5, N0, + ST_PYR, COLOR1, P2, P5, EF, EL, N0, + ST_PYR, COLOR1, EF, P5, P4, EH, N0, + ST_PYR, COLOR1, EH, P4, P0, ED, N0, + ST_PYR, COLOR1, ED, P0, P2, EC, N0, + ST_TET, COLOR1, EL, EC, P2, N0, + // Case #56: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EF, EJ, EC, EC, + ST_PYR, COLOR0, P2, EC, ED, P0, N0, + ST_TET, COLOR0, ED, EI, P0, N0, + ST_PYR, COLOR0, P1, P0, EI, EJ, N0, + ST_TET, COLOR0, P2, P0, P1, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_TET, COLOR0, P6, P7, P2, N0, + ST_PYR, COLOR0, P7, EK, EC, P2, N0, + ST_TET, COLOR0, P6, P2, P1, N0, + ST_PYR, COLOR0, P6, P1, EJ, EF, N0, + ST_PYR, COLOR0, P6, EF, EH, P7, N0, + ST_PYR, COLOR1, P3, P4, EI, ED, N0, + ST_PYR, COLOR1, EH, P4, P3, EK, N0, + ST_TET, COLOR1, P3, ED, EC, N0, + ST_TET, COLOR1, EK, P3, EC, N0, + ST_PYR, COLOR1, EI, P4, P5, EJ, N0, + ST_PYR, COLOR1, P4, EH, EF, P5, N0, + ST_TET, COLOR1, EF, EJ, P5, N0, + // Case #57: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EA, EJ, EF, EH, EK, EC, + ST_TET, COLOR0, P6, P7, P2, N0, + ST_PYR, COLOR0, P6, EF, EH, P7, N0, + ST_TET, COLOR0, P1, P6, P2, N0, + ST_PYR, COLOR0, P1, EJ, EF, P6, N0, + ST_TET, COLOR0, EA, EJ, P1, N0, + ST_PYR, COLOR0, P1, P2, EC, EA, N0, + ST_PYR, COLOR0, EC, P2, P7, EK, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_TET, COLOR1, P0, P4, P5, N0, + ST_PYR, COLOR1, EA, P0, P5, EJ, N0, + ST_PYR, COLOR1, EA, EC, P3, P0, N0, + ST_TET, COLOR1, P3, P4, P0, N0, + ST_TET, COLOR1, P3, EC, EK, N0, + ST_PYR, COLOR1, P3, EK, EH, P4, N0, + ST_PYR, COLOR1, EH, EF, P5, P4, N0, + ST_TET, COLOR1, EF, EJ, P5, N0, + // Case #58: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EF, EB, EH, EK, EC, + ST_TET, COLOR0, P0, EI, EA, ED, + ST_PYR, COLOR0, P2, EB, EF, P6, N0, + ST_PYR, COLOR0, P6, EF, EH, P7, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_TET, COLOR0, P6, P7, P2, N0, + ST_PYR, COLOR0, P7, EK, EC, P2, N0, + ST_TET, COLOR0, P2, EC, EB, N0, + ST_WDG, COLOR1, P4, P1, P3, EI, EA, ED, + ST_TET, COLOR1, P1, P3, P4, N0, + ST_PYR, COLOR1, P4, P3, EK, EH, N0, + ST_TET, COLOR1, P5, P1, P4, N0, + ST_PYR, COLOR1, EB, P1, P5, EF, N0, + ST_PYR, COLOR1, EF, P5, P4, EH, N0, + ST_PYR, COLOR1, P3, P1, EB, EC, N0, + ST_TET, COLOR1, EK, P3, EC, N0, + // Case #59: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EB, EF, EH, EC, EK, + ST_PYR, COLOR0, P6, EF, EH, P7, N0, + ST_TET, COLOR0, P7, EH, EK, N0, + ST_PYR, COLOR0, P2, EB, EF, P6, N0, + ST_TET, COLOR0, EC, EB, P2, N0, + ST_PYR, COLOR0, EK, EC, P2, P7, N0, + ST_TET, COLOR0, P7, P2, P6, N0, + ST_PYR, COLOR1, P0, P4, P5, P1, N0, + ST_TET, COLOR1, P4, P0, P3, N0, + ST_TET, COLOR1, P3, P0, P1, N0, + ST_PYR, COLOR1, P3, P1, EB, EC, N0, + ST_PYR, COLOR1, EB, P1, P5, EF, N0, + ST_PYR, COLOR1, EF, P5, P4, EH, N0, + ST_PYR, COLOR1, EH, P4, P3, EK, N0, + ST_TET, COLOR1, EC, EK, P3, N0, + // Case #60: Unique case #14 + ST_WDG, COLOR0, P1, EB, EJ, P0, ED, EI, + ST_WDG, COLOR0, P6, EF, EL, P7, EH, EK, + ST_HEX, COLOR1, P3, P4, P5, P2, EK, EH, EF, EL, + ST_HEX, COLOR1, ED, EI, EJ, EB, P3, P4, P5, P2, + // Case #61: Unique case #15 + ST_PNT, 0, COLOR1, 6, P0, P2, P3, P4, EF, EH, + ST_WDG, COLOR0, EH, P7, EK, EF, P6, EL, + ST_TET, COLOR0, EA, P1, EB, EJ, + ST_WDG, COLOR1, P0, P5, P2, EA, EJ, EB, + ST_PYR, COLOR1, EH, EF, P5, P4, N0, + ST_TET, COLOR1, P4, P5, P0, N0, + ST_TET, COLOR1, P4, P0, P3, N0, + ST_PYR, COLOR1, EK, EH, P4, P3, N0, + ST_PYR, COLOR1, EL, EK, P3, P2, N0, + ST_TET, COLOR1, P3, P0, P2, N0, + ST_PYR, COLOR1, EF, EH, EK, EL, N0, + ST_TET, COLOR1, P2, P0, P5, N0, + ST_PYR, COLOR1, EF, EL, P2, P5, N0, + // Case #62: (cloned #61) + ST_PNT, 0, COLOR1, 6, P1, P3, P2, P5, EH, EF, + ST_WDG, COLOR0, EH, P7, EK, EF, P6, EL, + ST_TET, COLOR0, EA, ED, P0, EI, + ST_WDG, COLOR1, EA, EI, ED, P1, P4, P3, + ST_PYR, COLOR1, EF, P5, P4, EH, N0, + ST_TET, COLOR1, P5, P1, P4, N0, + ST_TET, COLOR1, P5, P2, P1, N0, + ST_PYR, COLOR1, EL, P2, P5, EF, N0, + ST_PYR, COLOR1, EK, P3, P2, EL, N0, + ST_TET, COLOR1, P2, P3, P1, N0, + ST_PYR, COLOR1, EH, EK, EL, EF, N0, + ST_TET, COLOR1, P3, P4, P1, N0, + ST_PYR, COLOR1, EH, P4, P3, EK, N0, + // Case #63: Unique case #16 + ST_WDG, COLOR0, P7, EK, EH, P6, EL, EF, + ST_HEX, COLOR1, P3, P4, P5, P2, EK, EH, EF, EL, + ST_WDG, COLOR1, P1, P2, P5, P0, P3, P4, + // Case #64: (cloned #1) + ST_PNT, 0, COLOR0, 7, P7, P4, P5, P2, P3, P0, P1, + ST_WDG, COLOR0, EG, EF, EL, P7, P5, P2, + ST_TET, COLOR0, P7, P2, P5, N0, + ST_TET, COLOR0, P7, P5, P4, N0, + ST_PYR, COLOR0, P0, P4, P5, P1, N0, + ST_PYR, COLOR0, P3, P7, P4, P0, N0, + ST_PYR, COLOR0, P2, P3, P0, P1, N0, + ST_TET, COLOR0, P5, P2, P1, N0, + ST_TET, COLOR0, P2, P7, P3, N0, + ST_TET, COLOR1, P6, EF, EG, EL, + // Case #65: (cloned #20) + ST_WDG, COLOR0, P5, P7, P2, EF, EG, EL, + ST_WDG, COLOR0, EI, ED, EA, P4, P3, P1, + ST_TET, COLOR0, P7, P2, P5, P3, + ST_TET, COLOR0, P1, P2, P3, P5, + ST_TET, COLOR0, P4, P5, P1, P3, + ST_TET, COLOR0, P7, P4, P3, P5, + ST_TET, COLOR1, P0, EI, EA, ED, + ST_TET, COLOR1, P6, EF, EG, EL, + // Case #66: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, EA, EG, + ST_PYR, COLOR0, P0, P3, P7, P4, N0, + ST_TET, COLOR0, P4, P7, P5, N0, + ST_TET, COLOR0, P0, P4, P5, N0, + ST_TET, COLOR0, P2, P3, P0, N0, + ST_TET, COLOR0, P7, P3, P2, N0, + ST_PYR, COLOR0, P7, P2, EL, EG, N0, + ST_PYR, COLOR0, P5, P7, EG, EF, N0, + ST_TET, COLOR0, P5, EF, EJ, N0, + ST_PYR, COLOR0, P0, P5, EJ, EA, N0, + ST_PYR, COLOR0, P0, EA, EB, P2, N0, + ST_TET, COLOR0, P2, EB, EL, N0, + ST_PYR, COLOR1, P1, P6, EL, EB, N0, + ST_PYR, COLOR1, EJ, EF, P6, P1, N0, + ST_TET, COLOR1, EF, EG, P6, N0, + ST_TET, COLOR1, P6, EG, EL, N0, + ST_TET, COLOR1, EJ, N0, P1, EA, + ST_TET, COLOR1, EB, EA, P1, N0, + // Case #67: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EI, ED, EG, EG, + ST_PYR, COLOR0, P7, P2, EL, EG, N0, + ST_TET, COLOR0, EL, P2, EB, N0, + ST_PYR, COLOR0, P3, ED, EB, P2, N0, + ST_TET, COLOR0, P7, P3, P2, N0, + ST_TET, COLOR0, P5, EF, EJ, N0, + ST_TET, COLOR0, P4, P7, P5, N0, + ST_PYR, COLOR0, P5, P7, EG, EF, N0, + ST_TET, COLOR0, P4, P3, P7, N0, + ST_PYR, COLOR0, P4, EI, ED, P3, N0, + ST_PYR, COLOR0, P4, P5, EJ, EI, N0, + ST_PYR, COLOR1, P6, EL, EB, P1, N0, + ST_PYR, COLOR1, EJ, EF, P6, P1, N0, + ST_TET, COLOR1, P6, EG, EL, N0, + ST_TET, COLOR1, EF, EG, P6, N0, + ST_PYR, COLOR1, EB, ED, P0, P1, N0, + ST_PYR, COLOR1, P1, P0, EI, EJ, N0, + ST_TET, COLOR1, EI, P0, ED, N0, + // Case #68: (cloned #3) + ST_HEX, COLOR0, EG, P7, P3, EC, EF, P5, P1, EB, + ST_WDG, COLOR0, P1, P0, P3, P5, P4, P7, + ST_WDG, COLOR1, P2, EB, EC, P6, EF, EG, + // Case #69: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EG, EF, EI, EI, + ST_PYR, COLOR0, P4, P1, EA, EI, N0, + ST_TET, COLOR0, EA, P1, EB, N0, + ST_PYR, COLOR0, P5, EF, EB, P1, N0, + ST_TET, COLOR0, P4, P5, P1, N0, + ST_TET, COLOR0, P3, ED, EC, N0, + ST_TET, COLOR0, P7, P4, P3, N0, + ST_PYR, COLOR0, P3, P4, EI, ED, N0, + ST_TET, COLOR0, P7, P5, P4, N0, + ST_PYR, COLOR0, P7, EG, EF, P5, N0, + ST_PYR, COLOR0, P7, P3, EC, EG, N0, + ST_PYR, COLOR1, P0, EA, EB, P2, N0, + ST_PYR, COLOR1, EC, ED, P0, P2, N0, + ST_TET, COLOR1, P0, EI, EA, N0, + ST_TET, COLOR1, ED, EI, P0, N0, + ST_PYR, COLOR1, EB, EF, P6, P2, N0, + ST_PYR, COLOR1, P2, P6, EG, EC, N0, + ST_TET, COLOR1, EG, P6, EF, N0, + // Case #70: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EA, EC, EJ, EF, EG, + ST_PYR, COLOR0, P0, P3, P7, P4, N0, + ST_TET, COLOR0, P7, N0, P5, P4, + ST_PYR, COLOR0, P3, EC, EG, P7, N0, + ST_PYR, COLOR0, EA, EC, P3, P0, N0, + ST_TET, COLOR0, P5, P0, P4, N0, + ST_PYR, COLOR0, P5, EJ, EA, P0, N0, + ST_TET, COLOR0, P5, EF, EJ, N0, + ST_PYR, COLOR0, EG, EF, P5, P7, N0, + ST_PYR, COLOR1, EC, P2, P6, EG, N0, + ST_PYR, COLOR1, EA, P1, P2, EC, N0, + ST_TET, COLOR1, EJ, P1, EA, N0, + ST_TET, COLOR1, P1, P6, P2, N0, + ST_PYR, COLOR1, EJ, EF, P6, P1, N0, + ST_TET, COLOR1, P6, EF, EG, N0, + // Case #71: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EJ, EF, EG, EC, ED, EI, + ST_TET, COLOR0, P7, P4, P3, N0, + ST_PYR, COLOR0, P7, P3, EC, EG, N0, + ST_TET, COLOR0, P5, P4, P7, N0, + ST_PYR, COLOR0, P5, P7, EG, EF, N0, + ST_TET, COLOR0, EJ, P5, EF, N0, + ST_PYR, COLOR0, P5, EJ, EI, P4, N0, + ST_PYR, COLOR0, EI, ED, P3, P4, N0, + ST_TET, COLOR0, P3, ED, EC, N0, + ST_TET, COLOR1, P1, P6, P2, N0, + ST_PYR, COLOR1, EJ, EF, P6, P1, N0, + ST_PYR, COLOR1, EJ, P1, P0, EI, N0, + ST_TET, COLOR1, P0, P1, P2, N0, + ST_TET, COLOR1, P0, ED, EI, N0, + ST_PYR, COLOR1, P0, P2, EC, ED, N0, + ST_PYR, COLOR1, EC, P2, P6, EG, N0, + ST_TET, COLOR1, EG, P6, EF, N0, + // Case #72: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, ED, EF, + ST_PYR, COLOR0, P0, P4, P5, P1, N0, + ST_TET, COLOR0, P1, P5, P2, N0, + ST_TET, COLOR0, P0, P1, P2, N0, + ST_TET, COLOR0, P7, P4, P0, N0, + ST_TET, COLOR0, P5, P4, P7, N0, + ST_PYR, COLOR0, P5, P7, EG, EF, N0, + ST_PYR, COLOR0, P2, P5, EF, EL, N0, + ST_TET, COLOR0, P2, EL, EC, N0, + ST_PYR, COLOR0, P0, P2, EC, ED, N0, + ST_PYR, COLOR0, P0, ED, EK, P7, N0, + ST_TET, COLOR0, P7, EK, EG, N0, + ST_PYR, COLOR1, P3, P6, EG, EK, N0, + ST_PYR, COLOR1, EC, EL, P6, P3, N0, + ST_TET, COLOR1, EL, EF, P6, N0, + ST_TET, COLOR1, P6, EF, EG, N0, + ST_TET, COLOR1, EC, N0, P3, ED, + ST_TET, COLOR1, EK, ED, P3, N0, + // Case #73: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EA, EI, EF, EF, + ST_PYR, COLOR0, P5, P7, EG, EF, N0, + ST_TET, COLOR0, EG, P7, EK, N0, + ST_PYR, COLOR0, P4, EI, EK, P7, N0, + ST_TET, COLOR0, P5, P4, P7, N0, + ST_TET, COLOR0, P2, EL, EC, N0, + ST_TET, COLOR0, P1, P5, P2, N0, + ST_PYR, COLOR0, P2, P5, EF, EL, N0, + ST_TET, COLOR0, P1, P4, P5, N0, + ST_PYR, COLOR0, P1, EA, EI, P4, N0, + ST_PYR, COLOR0, P1, P2, EC, EA, N0, + ST_PYR, COLOR1, P6, EG, EK, P3, N0, + ST_PYR, COLOR1, EC, EL, P6, P3, N0, + ST_TET, COLOR1, P6, EF, EG, N0, + ST_TET, COLOR1, EL, EF, P6, N0, + ST_PYR, COLOR1, EK, EI, P0, P3, N0, + ST_PYR, COLOR1, P3, P0, EA, EC, N0, + ST_TET, COLOR1, EA, P0, EI, N0, + // Case #74: (cloned #26) + ST_TET, COLOR0, P2, EB, EL, EC, + ST_TET, COLOR0, P7, P4, P0, P5, + ST_PYR, COLOR0, EJ, EA, P0, P5, ED, + ST_PYR, COLOR0, EF, P5, P7, EG, EK, + ST_PYR, COLOR0, P0, ED, EK, P7, P5, + ST_PYR, COLOR0, ED, EJ, EF, EK, P5, + ST_WDG, COLOR1, P1, P6, P3, EB, EL, EC, + ST_PYR, COLOR1, P1, P3, ED, EA, EJ, + ST_PYR, COLOR1, EK, P3, P6, EG, EF, + ST_PYR, COLOR1, EJ, EF, P6, P1, P3, + ST_PYR, COLOR1, EJ, ED, EK, EF, P3, + // Case #75: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EI, EK, EJ, EF, EG, + ST_TET, COLOR0, P2, EC, EB, EL, + ST_PYR, COLOR0, P7, P4, EI, EK, N0, + ST_PYR, COLOR0, P4, P5, EJ, EI, N0, + ST_TET, COLOR0, P5, EF, EJ, N0, + ST_TET, COLOR0, P4, P7, P5, N0, + ST_PYR, COLOR0, P5, P7, EG, EF, N0, + ST_TET, COLOR0, P7, EK, EG, N0, + ST_WDG, COLOR1, EB, EC, EL, P1, P3, P6, + ST_TET, COLOR1, P3, P1, P6, N0, + ST_PYR, COLOR1, P1, EJ, EF, P6, N0, + ST_TET, COLOR1, P0, P1, P3, N0, + ST_PYR, COLOR1, EK, EI, P0, P3, N0, + ST_PYR, COLOR1, EI, EJ, P1, P0, N0, + ST_PYR, COLOR1, P6, EG, EK, P3, N0, + ST_TET, COLOR1, EF, EG, P6, N0, + // Case #76: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, ED, EB, EK, EG, EF, + ST_PYR, COLOR0, P0, P4, P5, P1, N0, + ST_TET, COLOR0, P5, P7, N0, P4, + ST_PYR, COLOR0, P1, P5, EF, EB, N0, + ST_PYR, COLOR0, ED, P0, P1, EB, N0, + ST_TET, COLOR0, P7, P4, P0, N0, + ST_PYR, COLOR0, P7, P0, ED, EK, N0, + ST_TET, COLOR0, P7, EK, EG, N0, + ST_PYR, COLOR0, EF, P5, P7, EG, N0, + ST_PYR, COLOR1, EB, EF, P6, P2, N0, + ST_PYR, COLOR1, ED, EB, P2, P3, N0, + ST_TET, COLOR1, EK, ED, P3, N0, + ST_TET, COLOR1, P3, P2, P6, N0, + ST_PYR, COLOR1, EK, P3, P6, EG, N0, + ST_TET, COLOR1, P6, EF, EG, N0, + // Case #77: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EK, EG, EF, EB, EA, EI, + ST_TET, COLOR0, P5, P1, P4, N0, + ST_PYR, COLOR0, P5, EF, EB, P1, N0, + ST_TET, COLOR0, P7, P5, P4, N0, + ST_PYR, COLOR0, P7, EG, EF, P5, N0, + ST_TET, COLOR0, EK, EG, P7, N0, + ST_PYR, COLOR0, P7, P4, EI, EK, N0, + ST_PYR, COLOR0, EI, P4, P1, EA, N0, + ST_TET, COLOR0, P1, EB, EA, N0, + ST_TET, COLOR1, P3, P2, P6, N0, + ST_PYR, COLOR1, EK, P3, P6, EG, N0, + ST_PYR, COLOR1, EK, EI, P0, P3, N0, + ST_TET, COLOR1, P0, P2, P3, N0, + ST_TET, COLOR1, P0, EI, EA, N0, + ST_PYR, COLOR1, P0, EA, EB, P2, N0, + ST_PYR, COLOR1, EB, EF, P6, P2, N0, + ST_TET, COLOR1, EF, EG, P6, N0, + // Case #78: (cloned #27) + ST_TET, COLOR0, P7, P4, P0, P5, + ST_PYR, COLOR0, EJ, EA, P0, P5, ED, + ST_PYR, COLOR0, EF, P5, P7, EG, EK, + ST_PYR, COLOR0, P0, ED, EK, P7, P5, + ST_PYR, COLOR0, ED, EJ, EF, EK, P5, + ST_TET, COLOR1, P2, P1, P6, P3, + ST_PYR, COLOR1, ED, EA, P1, P3, EJ, + ST_PYR, COLOR1, EK, P3, P6, EG, EF, + ST_PYR, COLOR1, P1, EJ, EF, P6, P3, + ST_PYR, COLOR1, ED, EK, EF, EJ, P3, + // Case #79: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EK, EI, EJ, EG, EF, + ST_PYR, COLOR0, P4, P5, EJ, EI, N0, + ST_TET, COLOR0, P5, EF, EJ, N0, + ST_PYR, COLOR0, P7, P4, EI, EK, N0, + ST_TET, COLOR0, EG, P7, EK, N0, + ST_PYR, COLOR0, EF, P5, P7, EG, N0, + ST_TET, COLOR0, P5, P4, P7, N0, + ST_PYR, COLOR1, P2, P3, P0, P1, N0, + ST_TET, COLOR1, P1, P6, P2, N0, + ST_TET, COLOR1, P6, P3, P2, N0, + ST_PYR, COLOR1, P6, EG, EK, P3, N0, + ST_PYR, COLOR1, EK, EI, P0, P3, N0, + ST_PYR, COLOR1, EI, EJ, P1, P0, N0, + ST_PYR, COLOR1, EJ, EF, P6, P1, N0, + ST_TET, COLOR1, EG, P6, EF, N0, + // Case #80: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, EI, EL, + ST_PYR, COLOR0, P0, P1, P2, P3, N0, + ST_TET, COLOR0, P1, P5, P2, N0, + ST_TET, COLOR0, P0, P5, P1, N0, + ST_TET, COLOR0, P7, P0, P3, N0, + ST_TET, COLOR0, P2, P7, P3, N0, + ST_PYR, COLOR0, P2, EL, EG, P7, N0, + ST_PYR, COLOR0, P5, EF, EL, P2, N0, + ST_TET, COLOR0, P5, EE, EF, N0, + ST_PYR, COLOR0, P0, EI, EE, P5, N0, + ST_PYR, COLOR0, P0, P7, EH, EI, N0, + ST_TET, COLOR0, P7, EG, EH, N0, + ST_PYR, COLOR1, P4, EH, EG, P6, N0, + ST_PYR, COLOR1, EE, P4, P6, EF, N0, + ST_TET, COLOR1, EF, P6, EL, N0, + ST_TET, COLOR1, P6, EG, EL, N0, + ST_TET, COLOR1, EE, P4, N0, EI, + ST_TET, COLOR1, EH, P4, EI, N0, + // Case #81: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EA, ED, EL, EL, + ST_PYR, COLOR0, P2, EL, EG, P7, N0, + ST_TET, COLOR0, EG, EH, P7, N0, + ST_PYR, COLOR0, P3, P7, EH, ED, N0, + ST_TET, COLOR0, P2, P7, P3, N0, + ST_TET, COLOR0, P5, EE, EF, N0, + ST_TET, COLOR0, P1, P5, P2, N0, + ST_PYR, COLOR0, P5, EF, EL, P2, N0, + ST_TET, COLOR0, P1, P2, P3, N0, + ST_PYR, COLOR0, P1, P3, ED, EA, N0, + ST_PYR, COLOR0, P1, EA, EE, P5, N0, + ST_PYR, COLOR1, P6, P4, EH, EG, N0, + ST_PYR, COLOR1, EE, P4, P6, EF, N0, + ST_TET, COLOR1, P6, EG, EL, N0, + ST_TET, COLOR1, EF, P6, EL, N0, + ST_PYR, COLOR1, EH, P4, P0, ED, N0, + ST_PYR, COLOR1, P4, EE, EA, P0, N0, + ST_TET, COLOR1, EA, ED, P0, N0, + // Case #82: (cloned #26) + ST_TET, COLOR0, P5, EF, EJ, EE, + ST_TET, COLOR0, P7, P0, P3, P2, + ST_PYR, COLOR0, EB, P2, P0, EA, EI, + ST_PYR, COLOR0, EL, EG, P7, P2, EH, + ST_PYR, COLOR0, P0, P7, EH, EI, P2, + ST_PYR, COLOR0, EI, EH, EL, EB, P2, + ST_WDG, COLOR1, EJ, EF, EE, P1, P6, P4, + ST_PYR, COLOR1, P1, EA, EI, P4, EB, + ST_PYR, COLOR1, EH, EG, P6, P4, EL, + ST_PYR, COLOR1, EB, P1, P6, EL, P4, + ST_PYR, COLOR1, EB, EL, EH, EI, P4, + // Case #83: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, ED, EH, EB, EL, EG, + ST_TET, COLOR0, P5, EJ, EE, EF, + ST_PYR, COLOR0, P7, EH, ED, P3, N0, + ST_PYR, COLOR0, P3, ED, EB, P2, N0, + ST_TET, COLOR0, P2, EB, EL, N0, + ST_TET, COLOR0, P3, P2, P7, N0, + ST_PYR, COLOR0, P2, EL, EG, P7, N0, + ST_TET, COLOR0, P7, EG, EH, N0, + ST_WDG, COLOR1, P1, P4, P6, EJ, EE, EF, + ST_TET, COLOR1, P4, P6, P1, N0, + ST_PYR, COLOR1, P1, P6, EL, EB, N0, + ST_TET, COLOR1, P0, P4, P1, N0, + ST_PYR, COLOR1, EH, P4, P0, ED, N0, + ST_PYR, COLOR1, ED, P0, P1, EB, N0, + ST_PYR, COLOR1, P6, P4, EH, EG, N0, + ST_TET, COLOR1, EL, P6, EG, N0, + // Case #84: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EC, EB, EI, EI, + ST_PYR, COLOR0, P0, EI, EE, P5, N0, + ST_TET, COLOR0, EE, EF, P5, N0, + ST_PYR, COLOR0, P1, P5, EF, EB, N0, + ST_TET, COLOR0, P0, P5, P1, N0, + ST_TET, COLOR0, P7, EG, EH, N0, + ST_TET, COLOR0, P3, P7, P0, N0, + ST_PYR, COLOR0, P7, EH, EI, P0, N0, + ST_TET, COLOR0, P3, P0, P1, N0, + ST_PYR, COLOR0, P3, P1, EB, EC, N0, + ST_PYR, COLOR0, P3, EC, EG, P7, N0, + ST_PYR, COLOR1, P4, P6, EF, EE, N0, + ST_PYR, COLOR1, EG, P6, P4, EH, N0, + ST_TET, COLOR1, P4, EE, EI, N0, + ST_TET, COLOR1, EH, P4, EI, N0, + ST_PYR, COLOR1, EF, P6, P2, EB, N0, + ST_PYR, COLOR1, P6, EG, EC, P2, N0, + ST_TET, COLOR1, EC, EB, P2, N0, + // Case #85: (cloned #60) + ST_WDG, COLOR0, P7, EH, EG, P3, ED, EC, + ST_WDG, COLOR0, P5, EF, EE, P1, EB, EA, + ST_HEX, COLOR1, P0, P2, P6, P4, EA, EB, EF, EE, + ST_HEX, COLOR1, ED, EC, EG, EH, P0, P2, P6, P4, + // Case #86: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EC, EG, EA, EI, EH, + ST_TET, COLOR0, P5, EF, EJ, EE, + ST_PYR, COLOR0, P7, P3, EC, EG, N0, + ST_PYR, COLOR0, P3, P0, EA, EC, N0, + ST_TET, COLOR0, P0, EI, EA, N0, + ST_TET, COLOR0, P3, P7, P0, N0, + ST_PYR, COLOR0, P0, P7, EH, EI, N0, + ST_TET, COLOR0, P7, EG, EH, N0, + ST_WDG, COLOR1, EJ, EF, EE, P1, P6, P4, + ST_TET, COLOR1, P6, P1, P4, N0, + ST_PYR, COLOR1, P1, EA, EI, P4, N0, + ST_TET, COLOR1, P2, P1, P6, N0, + ST_PYR, COLOR1, EG, EC, P2, P6, N0, + ST_PYR, COLOR1, EC, EA, P1, P2, N0, + ST_PYR, COLOR1, P4, EH, EG, P6, N0, + ST_TET, COLOR1, EI, EH, P4, N0, + // Case #87: (cloned #61) + ST_PNT, 0, COLOR1, 6, P1, P4, P0, P2, EG, EC, + ST_WDG, COLOR0, EG, P7, EH, EC, P3, ED, + ST_TET, COLOR0, EJ, EE, P5, EF, + ST_WDG, COLOR1, EJ, EF, EE, P1, P6, P4, + ST_PYR, COLOR1, EC, P2, P6, EG, N0, + ST_TET, COLOR1, P2, P1, P6, N0, + ST_TET, COLOR1, P2, P0, P1, N0, + ST_PYR, COLOR1, ED, P0, P2, EC, N0, + ST_PYR, COLOR1, EH, P4, P0, ED, N0, + ST_TET, COLOR1, P0, P4, P1, N0, + ST_PYR, COLOR1, EG, EH, ED, EC, N0, + ST_TET, COLOR1, P4, P6, P1, N0, + ST_PYR, COLOR1, EG, P6, P4, EH, N0, + // Case #88: (cloned #26) + ST_TET, COLOR0, P7, EG, EH, EK, + ST_TET, COLOR0, P2, P0, P1, P5, + ST_PYR, COLOR0, EE, P5, P0, EI, ED, + ST_PYR, COLOR0, EF, EL, P2, P5, EC, + ST_PYR, COLOR0, P0, P2, EC, ED, P5, + ST_PYR, COLOR0, ED, EC, EF, EE, P5, + ST_WDG, COLOR1, EH, EG, EK, P4, P6, P3, + ST_PYR, COLOR1, P4, EI, ED, P3, EE, + ST_PYR, COLOR1, EC, EL, P6, P3, EF, + ST_PYR, COLOR1, EE, P4, P6, EF, P3, + ST_PYR, COLOR1, EE, EF, EC, ED, P3, + // Case #89: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EA, EC, EE, EF, EL, + ST_TET, COLOR0, P7, EH, EK, EG, + ST_PYR, COLOR0, P2, EC, EA, P1, N0, + ST_PYR, COLOR0, P1, EA, EE, P5, N0, + ST_TET, COLOR0, P5, EE, EF, N0, + ST_TET, COLOR0, P1, P5, P2, N0, + ST_PYR, COLOR0, P5, EF, EL, P2, N0, + ST_TET, COLOR0, P2, EL, EC, N0, + ST_WDG, COLOR1, P4, P3, P6, EH, EK, EG, + ST_TET, COLOR1, P3, P6, P4, N0, + ST_PYR, COLOR1, P4, P6, EF, EE, N0, + ST_TET, COLOR1, P0, P3, P4, N0, + ST_PYR, COLOR1, EC, P3, P0, EA, N0, + ST_PYR, COLOR1, EA, P0, P4, EE, N0, + ST_PYR, COLOR1, P6, P3, EC, EL, N0, + ST_TET, COLOR1, EF, P6, EL, N0, + // Case #90: Unique case #17 + ST_TET, COLOR0, EH, EG, EK, P7, + ST_TET, COLOR0, EI, ED, EA, P0, + ST_TET, COLOR0, EE, EJ, EF, P5, + ST_TET, COLOR0, EB, EC, EL, P2, + ST_WDG, COLOR1, EB, EC, EL, P1, P3, P6, + ST_TET, COLOR1, P1, P6, P3, P4, + ST_WDG, COLOR1, P4, P6, P1, EE, EF, EJ, + ST_WDG, COLOR1, P3, P4, P1, ED, EI, EA, + ST_WDG, COLOR1, P6, P4, P3, EG, EH, EK, + // Case #91: Unique case #18 + ST_TET, COLOR0, EE, EJ, EF, P5, + ST_TET, COLOR0, EH, EK, P7, EG, + ST_TET, COLOR0, EB, P2, EC, EL, + ST_WDG, COLOR1, P6, P3, P1, EL, EC, EB, + ST_WDG, COLOR1, EH, EG, EK, P4, P6, P3, + ST_WDG, COLOR1, P4, P6, P1, EE, EF, EJ, + ST_TET, COLOR1, P6, P3, P1, P4, + ST_TET, COLOR1, P4, P1, P0, P3, + // Case #92: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EB, EF, ED, EI, EE, + ST_TET, COLOR0, P7, EK, EG, EH, + ST_PYR, COLOR0, P5, EF, EB, P1, N0, + ST_PYR, COLOR0, P1, EB, ED, P0, N0, + ST_TET, COLOR0, P0, ED, EI, N0, + ST_TET, COLOR0, P1, P0, P5, N0, + ST_PYR, COLOR0, P0, EI, EE, P5, N0, + ST_TET, COLOR0, P5, EE, EF, N0, + ST_WDG, COLOR1, P3, P6, P4, EK, EG, EH, + ST_TET, COLOR1, P6, P4, P3, N0, + ST_PYR, COLOR1, P3, P4, EI, ED, N0, + ST_TET, COLOR1, P2, P6, P3, N0, + ST_PYR, COLOR1, EF, P6, P2, EB, N0, + ST_PYR, COLOR1, EB, P2, P3, ED, N0, + ST_PYR, COLOR1, P4, P6, EF, EE, N0, + ST_TET, COLOR1, EI, P4, EE, N0, + // Case #93: (cloned #61) + ST_PNT, 0, COLOR1, 6, P3, P4, P0, P2, EF, EB, + ST_WDG, COLOR0, EB, P1, EA, EF, P5, EE, + ST_TET, COLOR0, EK, P7, EH, EG, + ST_WDG, COLOR1, P3, P6, P4, EK, EG, EH, + ST_PYR, COLOR1, EB, EF, P6, P2, N0, + ST_TET, COLOR1, P2, P6, P3, N0, + ST_TET, COLOR1, P2, P3, P0, N0, + ST_PYR, COLOR1, EA, EB, P2, P0, N0, + ST_PYR, COLOR1, EE, EA, P0, P4, N0, + ST_TET, COLOR1, P0, P3, P4, N0, + ST_PYR, COLOR1, EF, EB, EA, EE, N0, + ST_TET, COLOR1, P4, P3, P6, N0, + ST_PYR, COLOR1, EF, EE, P4, P6, N0, + // Case #94: (cloned #91) + ST_TET, COLOR0, EK, EH, EG, P7, + ST_TET, COLOR0, ED, P0, EA, EI, + ST_TET, COLOR0, EF, EJ, P5, EE, + ST_WDG, COLOR1, EE, EJ, EF, P4, P1, P6, + ST_WDG, COLOR1, P3, P4, P1, ED, EI, EA, + ST_WDG, COLOR1, EK, EH, EG, P3, P4, P6, + ST_TET, COLOR1, P4, P6, P1, P3, + ST_TET, COLOR1, P3, P2, P6, P1, + // Case #95: Unique case #19 + ST_TET, COLOR0, EG, EK, EH, P7, + ST_TET, COLOR0, EF, EE, EJ, P5, + ST_WDG, COLOR1, P4, P6, P1, EE, EF, EJ, + ST_WDG, COLOR1, EH, EG, EK, P4, P6, P3, + ST_PYR, COLOR1, P0, P1, P2, P3, P4, + ST_TET, COLOR1, P6, P3, P2, P4, + ST_TET, COLOR1, P6, P2, P1, P4, + // Case #96: (cloned #3) + ST_HEX, COLOR0, EL, P2, P1, EJ, EG, P7, P4, EE, + ST_WDG, COLOR0, P2, P3, P7, P1, P0, P4, + ST_WDG, COLOR1, P6, EL, EG, P5, EJ, EE, + // Case #97: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EG, EL, ED, ED, + ST_PYR, COLOR0, P3, ED, EA, P1, N0, + ST_TET, COLOR0, EA, EJ, P1, N0, + ST_PYR, COLOR0, P2, P1, EJ, EL, N0, + ST_TET, COLOR0, P3, P1, P2, N0, + ST_TET, COLOR0, P4, EE, EI, N0, + ST_TET, COLOR0, P7, P4, P3, N0, + ST_PYR, COLOR0, P4, EI, ED, P3, N0, + ST_TET, COLOR0, P7, P3, P2, N0, + ST_PYR, COLOR0, P7, P2, EL, EG, N0, + ST_PYR, COLOR0, P7, EG, EE, P4, N0, + ST_PYR, COLOR1, P0, P5, EJ, EA, N0, + ST_PYR, COLOR1, EE, P5, P0, EI, N0, + ST_TET, COLOR1, P0, EA, ED, N0, + ST_TET, COLOR1, EI, P0, ED, N0, + ST_PYR, COLOR1, EJ, P5, P6, EL, N0, + ST_PYR, COLOR1, P5, EE, EG, P6, N0, + ST_TET, COLOR1, EG, EL, P6, N0, + // Case #98: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EA, EE, EB, EL, EG, + ST_PYR, COLOR0, P0, P3, P7, P4, N0, + ST_TET, COLOR0, P7, P2, N0, P3, + ST_PYR, COLOR0, P4, P7, EG, EE, N0, + ST_PYR, COLOR0, EA, P0, P4, EE, N0, + ST_TET, COLOR0, P2, P3, P0, N0, + ST_PYR, COLOR0, P2, P0, EA, EB, N0, + ST_TET, COLOR0, P2, EB, EL, N0, + ST_PYR, COLOR0, EG, P7, P2, EL, N0, + ST_PYR, COLOR1, EE, EG, P6, P5, N0, + ST_PYR, COLOR1, EA, EE, P5, P1, N0, + ST_TET, COLOR1, EB, EA, P1, N0, + ST_TET, COLOR1, P1, P5, P6, N0, + ST_PYR, COLOR1, EB, P1, P6, EL, N0, + ST_TET, COLOR1, P6, EG, EL, N0, + // Case #99: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EB, EL, EG, EE, EI, ED, + ST_TET, COLOR0, P7, P4, P3, N0, + ST_PYR, COLOR0, P7, EG, EE, P4, N0, + ST_TET, COLOR0, P2, P7, P3, N0, + ST_PYR, COLOR0, P2, EL, EG, P7, N0, + ST_TET, COLOR0, EB, EL, P2, N0, + ST_PYR, COLOR0, P2, P3, ED, EB, N0, + ST_PYR, COLOR0, ED, P3, P4, EI, N0, + ST_TET, COLOR0, P4, EE, EI, N0, + ST_TET, COLOR1, P1, P5, P6, N0, + ST_PYR, COLOR1, EB, P1, P6, EL, N0, + ST_PYR, COLOR1, EB, ED, P0, P1, N0, + ST_TET, COLOR1, P0, P5, P1, N0, + ST_TET, COLOR1, P0, ED, EI, N0, + ST_PYR, COLOR1, P0, EI, EE, P5, N0, + ST_PYR, COLOR1, EE, EG, P6, P5, N0, + ST_TET, COLOR1, EG, EL, P6, N0, + // Case #100: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EC, EG, EB, EJ, EE, + ST_PYR, COLOR0, P3, P7, P4, P0, N0, + ST_TET, COLOR0, P4, N0, P1, P0, + ST_PYR, COLOR0, P7, EG, EE, P4, N0, + ST_PYR, COLOR0, EC, EG, P7, P3, N0, + ST_TET, COLOR0, P1, P3, P0, N0, + ST_PYR, COLOR0, P1, EB, EC, P3, N0, + ST_TET, COLOR0, P1, EJ, EB, N0, + ST_PYR, COLOR0, EE, EJ, P1, P4, N0, + ST_PYR, COLOR1, EG, P6, P5, EE, N0, + ST_PYR, COLOR1, EC, P2, P6, EG, N0, + ST_TET, COLOR1, EB, P2, EC, N0, + ST_TET, COLOR1, P2, P5, P6, N0, + ST_PYR, COLOR1, EB, EJ, P5, P2, N0, + ST_TET, COLOR1, P5, EJ, EE, N0, + // Case #101: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EG, EE, EC, ED, EI, + ST_TET, COLOR0, P1, EJ, EB, EA, + ST_PYR, COLOR0, P4, P7, EG, EE, N0, + ST_PYR, COLOR0, P7, P3, EC, EG, N0, + ST_TET, COLOR0, P3, ED, EC, N0, + ST_TET, COLOR0, P7, P4, P3, N0, + ST_PYR, COLOR0, P3, P4, EI, ED, N0, + ST_TET, COLOR0, P4, EE, EI, N0, + ST_WDG, COLOR1, EB, EJ, EA, P2, P5, P0, + ST_TET, COLOR1, P5, P2, P0, N0, + ST_PYR, COLOR1, P2, EC, ED, P0, N0, + ST_TET, COLOR1, P6, P2, P5, N0, + ST_PYR, COLOR1, EE, EG, P6, P5, N0, + ST_PYR, COLOR1, EG, EC, P2, P6, N0, + ST_PYR, COLOR1, P0, EI, EE, P5, N0, + ST_TET, COLOR1, ED, EI, P0, N0, + // Case #102: (cloned #15) + ST_HEX, COLOR0, EA, EE, EG, EC, P0, P4, P7, P3, + ST_HEX, COLOR1, P1, P5, P6, P2, EA, EE, EG, EC, + // Case #103: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EE, EG, EC, EI, ED, + ST_PYR, COLOR0, P7, P3, EC, EG, N0, + ST_TET, COLOR0, P3, ED, EC, N0, + ST_PYR, COLOR0, P4, P7, EG, EE, N0, + ST_TET, COLOR0, EI, P4, EE, N0, + ST_PYR, COLOR0, ED, P3, P4, EI, N0, + ST_TET, COLOR0, P3, P7, P4, N0, + ST_PYR, COLOR1, P1, P5, P6, P2, N0, + ST_TET, COLOR1, P2, P0, P1, N0, + ST_TET, COLOR1, P0, P5, P1, N0, + ST_PYR, COLOR1, P0, EI, EE, P5, N0, + ST_PYR, COLOR1, EE, EG, P6, P5, N0, + ST_PYR, COLOR1, EG, EC, P2, P6, N0, + ST_PYR, COLOR1, EC, ED, P0, P2, N0, + ST_TET, COLOR1, EI, P0, ED, N0, + // Case #104: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EE, EJ, ED, ED, + ST_PYR, COLOR0, P0, P2, EC, ED, N0, + ST_TET, COLOR0, EC, P2, EL, N0, + ST_PYR, COLOR0, P1, EJ, EL, P2, N0, + ST_TET, COLOR0, P0, P1, P2, N0, + ST_TET, COLOR0, P7, EK, EG, N0, + ST_TET, COLOR0, P4, P0, P7, N0, + ST_PYR, COLOR0, P7, P0, ED, EK, N0, + ST_TET, COLOR0, P4, P1, P0, N0, + ST_PYR, COLOR0, P4, EE, EJ, P1, N0, + ST_PYR, COLOR0, P4, P7, EG, EE, N0, + ST_PYR, COLOR1, P3, EC, EL, P6, N0, + ST_PYR, COLOR1, EG, EK, P3, P6, N0, + ST_TET, COLOR1, P3, ED, EC, N0, + ST_TET, COLOR1, EK, ED, P3, N0, + ST_PYR, COLOR1, EL, EJ, P5, P6, N0, + ST_PYR, COLOR1, P6, P5, EE, EG, N0, + ST_TET, COLOR1, EE, P5, EJ, N0, + // Case #105: (cloned #60) + ST_WDG, COLOR0, P4, EI, EE, P7, EK, EG, + ST_WDG, COLOR0, P1, EJ, EA, P2, EL, EC, + ST_HEX, COLOR1, EA, EJ, EL, EC, P0, P5, P6, P3, + ST_HEX, COLOR1, P0, P5, P6, P3, EI, EE, EG, EK, + // Case #106: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EE, EG, EA, ED, EK, + ST_TET, COLOR0, P2, EB, EL, EC, + ST_PYR, COLOR0, P7, EG, EE, P4, N0, + ST_PYR, COLOR0, P4, EE, EA, P0, N0, + ST_TET, COLOR0, P0, EA, ED, N0, + ST_TET, COLOR0, P4, P0, P7, N0, + ST_PYR, COLOR0, P0, ED, EK, P7, N0, + ST_TET, COLOR0, P7, EK, EG, N0, + ST_WDG, COLOR1, P1, P6, P3, EB, EL, EC, + ST_TET, COLOR1, P6, P3, P1, N0, + ST_PYR, COLOR1, P1, P3, ED, EA, N0, + ST_TET, COLOR1, P5, P6, P1, N0, + ST_PYR, COLOR1, EG, P6, P5, EE, N0, + ST_PYR, COLOR1, EE, P5, P1, EA, N0, + ST_PYR, COLOR1, P3, P6, EG, EK, N0, + ST_TET, COLOR1, ED, P3, EK, N0, + // Case #107: (cloned #61) + ST_PNT, 0, COLOR1, 6, P1, P3, P0, P5, EG, EE, + ST_WDG, COLOR0, EE, P4, EI, EG, P7, EK, + ST_TET, COLOR0, EB, P2, EC, EL, + ST_WDG, COLOR1, P1, P6, P3, EB, EL, EC, + ST_PYR, COLOR1, EE, EG, P6, P5, N0, + ST_TET, COLOR1, P5, P6, P1, N0, + ST_TET, COLOR1, P5, P1, P0, N0, + ST_PYR, COLOR1, EI, EE, P5, P0, N0, + ST_PYR, COLOR1, EK, EI, P0, P3, N0, + ST_TET, COLOR1, P0, P1, P3, N0, + ST_PYR, COLOR1, EG, EE, EI, EK, N0, + ST_TET, COLOR1, P3, P1, P6, N0, + ST_PYR, COLOR1, EG, EK, P3, P6, N0, + // Case #108: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EB, EJ, EE, EG, EK, ED, + ST_TET, COLOR0, P4, P0, P7, N0, + ST_PYR, COLOR0, P4, P7, EG, EE, N0, + ST_TET, COLOR0, P1, P0, P4, N0, + ST_PYR, COLOR0, P1, P4, EE, EJ, N0, + ST_TET, COLOR0, EB, P1, EJ, N0, + ST_PYR, COLOR0, P1, EB, ED, P0, N0, + ST_PYR, COLOR0, ED, EK, P7, P0, N0, + ST_TET, COLOR0, P7, EK, EG, N0, + ST_TET, COLOR1, P2, P5, P6, N0, + ST_PYR, COLOR1, EB, EJ, P5, P2, N0, + ST_PYR, COLOR1, EB, P2, P3, ED, N0, + ST_TET, COLOR1, P3, P2, P6, N0, + ST_TET, COLOR1, P3, EK, ED, N0, + ST_PYR, COLOR1, P3, P6, EG, EK, N0, + ST_PYR, COLOR1, EG, P6, P5, EE, N0, + ST_TET, COLOR1, EE, P5, EJ, N0, + // Case #109: (cloned #61) + ST_PNT, 0, COLOR1, 6, P2, P0, P3, P6, EE, EG, + ST_WDG, COLOR0, EE, P4, EI, EG, P7, EK, + ST_TET, COLOR0, EB, EA, P1, EJ, + ST_WDG, COLOR1, EB, EJ, EA, P2, P5, P0, + ST_PYR, COLOR1, EG, P6, P5, EE, N0, + ST_TET, COLOR1, P6, P2, P5, N0, + ST_TET, COLOR1, P6, P3, P2, N0, + ST_PYR, COLOR1, EK, P3, P6, EG, N0, + ST_PYR, COLOR1, EI, P0, P3, EK, N0, + ST_TET, COLOR1, P3, P0, P2, N0, + ST_PYR, COLOR1, EE, EI, EK, EG, N0, + ST_TET, COLOR1, P0, P5, P2, N0, + ST_PYR, COLOR1, EE, P5, P0, EI, N0, + // Case #110: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EG, EE, EA, EK, ED, + ST_PYR, COLOR0, P4, EE, EA, P0, N0, + ST_TET, COLOR0, P0, EA, ED, N0, + ST_PYR, COLOR0, P7, EG, EE, P4, N0, + ST_TET, COLOR0, EK, EG, P7, N0, + ST_PYR, COLOR0, ED, EK, P7, P0, N0, + ST_TET, COLOR0, P0, P7, P4, N0, + ST_PYR, COLOR1, P2, P1, P5, P6, N0, + ST_TET, COLOR1, P1, P2, P3, N0, + ST_TET, COLOR1, P3, P2, P6, N0, + ST_PYR, COLOR1, P3, P6, EG, EK, N0, + ST_PYR, COLOR1, EG, P6, P5, EE, N0, + ST_PYR, COLOR1, EE, P5, P1, EA, N0, + ST_PYR, COLOR1, EA, P1, P3, ED, N0, + ST_TET, COLOR1, EK, ED, P3, N0, + // Case #111: (cloned #63) + ST_WDG, COLOR0, P4, EI, EE, P7, EK, EG, + ST_HEX, COLOR1, P0, P5, P6, P3, EI, EE, EG, EK, + ST_WDG, COLOR1, P2, P3, P6, P1, P0, P5, + // Case #112: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EI, EJ, EH, EG, EL, + ST_PYR, COLOR0, P0, P1, P2, P3, N0, + ST_TET, COLOR0, P2, N0, P7, P3, + ST_PYR, COLOR0, P1, EJ, EL, P2, N0, + ST_PYR, COLOR0, EI, EJ, P1, P0, N0, + ST_TET, COLOR0, P7, P0, P3, N0, + ST_PYR, COLOR0, P7, EH, EI, P0, N0, + ST_TET, COLOR0, P7, EG, EH, N0, + ST_PYR, COLOR0, EL, EG, P7, P2, N0, + ST_PYR, COLOR1, EJ, P5, P6, EL, N0, + ST_PYR, COLOR1, EI, P4, P5, EJ, N0, + ST_TET, COLOR1, EH, P4, EI, N0, + ST_TET, COLOR1, P4, P6, P5, N0, + ST_PYR, COLOR1, EH, EG, P6, P4, N0, + ST_TET, COLOR1, P6, EG, EL, N0, + // Case #113: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EH, EG, EL, EJ, EA, ED, + ST_TET, COLOR0, P2, P3, P1, N0, + ST_PYR, COLOR0, P2, P1, EJ, EL, N0, + ST_TET, COLOR0, P7, P3, P2, N0, + ST_PYR, COLOR0, P7, P2, EL, EG, N0, + ST_TET, COLOR0, EH, P7, EG, N0, + ST_PYR, COLOR0, P7, EH, ED, P3, N0, + ST_PYR, COLOR0, ED, EA, P1, P3, N0, + ST_TET, COLOR0, P1, EA, EJ, N0, + ST_TET, COLOR1, P4, P6, P5, N0, + ST_PYR, COLOR1, EH, EG, P6, P4, N0, + ST_PYR, COLOR1, EH, P4, P0, ED, N0, + ST_TET, COLOR1, P0, P4, P5, N0, + ST_TET, COLOR1, P0, EA, ED, N0, + ST_PYR, COLOR1, P0, P5, EJ, EA, N0, + ST_PYR, COLOR1, EJ, P5, P6, EL, N0, + ST_TET, COLOR1, EL, P6, EG, N0, + // Case #114: (cloned #27) + ST_TET, COLOR0, P7, P0, P3, P2, + ST_PYR, COLOR0, EB, P2, P0, EA, EI, + ST_PYR, COLOR0, EL, EG, P7, P2, EH, + ST_PYR, COLOR0, P0, P7, EH, EI, P2, + ST_PYR, COLOR0, EI, EH, EL, EB, P2, + ST_TET, COLOR1, P5, P6, P1, P4, + ST_PYR, COLOR1, EI, P4, P1, EA, EB, + ST_PYR, COLOR1, EH, EG, P6, P4, EL, + ST_PYR, COLOR1, P1, P6, EL, EB, P4, + ST_PYR, COLOR1, EI, EB, EL, EH, P4, + // Case #115: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EH, ED, EB, EG, EL, + ST_PYR, COLOR0, P3, ED, EB, P2, N0, + ST_TET, COLOR0, P2, EB, EL, N0, + ST_PYR, COLOR0, P7, EH, ED, P3, N0, + ST_TET, COLOR0, EG, EH, P7, N0, + ST_PYR, COLOR0, EL, EG, P7, P2, N0, + ST_TET, COLOR0, P2, P7, P3, N0, + ST_PYR, COLOR1, P5, P1, P0, P4, N0, + ST_TET, COLOR1, P1, P5, P6, N0, + ST_TET, COLOR1, P6, P5, P4, N0, + ST_PYR, COLOR1, P6, P4, EH, EG, N0, + ST_PYR, COLOR1, EH, P4, P0, ED, N0, + ST_PYR, COLOR1, ED, P0, P1, EB, N0, + ST_PYR, COLOR1, EB, P1, P6, EL, N0, + ST_TET, COLOR1, EG, EL, P6, N0, + // Case #116: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EJ, EB, EC, EG, EH, EI, + ST_TET, COLOR0, P3, P7, P0, N0, + ST_PYR, COLOR0, P3, EC, EG, P7, N0, + ST_TET, COLOR0, P1, P3, P0, N0, + ST_PYR, COLOR0, P1, EB, EC, P3, N0, + ST_TET, COLOR0, EJ, EB, P1, N0, + ST_PYR, COLOR0, P1, P0, EI, EJ, N0, + ST_PYR, COLOR0, EI, P0, P7, EH, N0, + ST_TET, COLOR0, P7, EG, EH, N0, + ST_TET, COLOR1, P5, P6, P2, N0, + ST_PYR, COLOR1, EJ, P5, P2, EB, N0, + ST_PYR, COLOR1, EJ, EI, P4, P5, N0, + ST_TET, COLOR1, P4, P6, P5, N0, + ST_TET, COLOR1, P4, EI, EH, N0, + ST_PYR, COLOR1, P4, EH, EG, P6, N0, + ST_PYR, COLOR1, EG, EC, P2, P6, N0, + ST_TET, COLOR1, EC, EB, P2, N0, + // Case #117: (cloned #61) + ST_PNT, 0, COLOR1, 6, P5, P0, P4, P6, EC, EG, + ST_WDG, COLOR0, EG, P7, EH, EC, P3, ED, + ST_TET, COLOR0, EJ, P1, EA, EB, + ST_WDG, COLOR1, P5, P2, P0, EJ, EB, EA, + ST_PYR, COLOR1, EG, EC, P2, P6, N0, + ST_TET, COLOR1, P6, P2, P5, N0, + ST_TET, COLOR1, P6, P5, P4, N0, + ST_PYR, COLOR1, EH, EG, P6, P4, N0, + ST_PYR, COLOR1, ED, EH, P4, P0, N0, + ST_TET, COLOR1, P4, P5, P0, N0, + ST_PYR, COLOR1, EC, EG, EH, ED, N0, + ST_TET, COLOR1, P0, P5, P2, N0, + ST_PYR, COLOR1, EC, ED, P0, P2, N0, + // Case #118: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EG, EC, EA, EH, EI, + ST_PYR, COLOR0, P3, P0, EA, EC, N0, + ST_TET, COLOR0, P0, EI, EA, N0, + ST_PYR, COLOR0, P7, P3, EC, EG, N0, + ST_TET, COLOR0, EH, P7, EG, N0, + ST_PYR, COLOR0, EI, P0, P7, EH, N0, + ST_TET, COLOR0, P0, P3, P7, N0, + ST_PYR, COLOR1, P5, P6, P2, P1, N0, + ST_TET, COLOR1, P1, P4, P5, N0, + ST_TET, COLOR1, P4, P6, P5, N0, + ST_PYR, COLOR1, P4, EH, EG, P6, N0, + ST_PYR, COLOR1, EG, EC, P2, P6, N0, + ST_PYR, COLOR1, EC, EA, P1, P2, N0, + ST_PYR, COLOR1, EA, EI, P4, P1, N0, + ST_TET, COLOR1, EH, P4, EI, N0, + // Case #119: (cloned #63) + ST_WDG, COLOR0, P7, EH, EG, P3, ED, EC, + ST_HEX, COLOR1, ED, EC, EG, EH, P0, P2, P6, P4, + ST_WDG, COLOR1, P1, P0, P2, P5, P4, P6, + // Case #120: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EJ, EL, EI, ED, EC, + ST_TET, COLOR0, P7, EG, EH, EK, + ST_PYR, COLOR0, P2, P1, EJ, EL, N0, + ST_PYR, COLOR0, P1, P0, EI, EJ, N0, + ST_TET, COLOR0, P0, ED, EI, N0, + ST_TET, COLOR0, P1, P2, P0, N0, + ST_PYR, COLOR0, P0, P2, EC, ED, N0, + ST_TET, COLOR0, P2, EL, EC, N0, + ST_WDG, COLOR1, EH, EG, EK, P4, P6, P3, + ST_TET, COLOR1, P6, P4, P3, N0, + ST_PYR, COLOR1, P4, EI, ED, P3, N0, + ST_TET, COLOR1, P5, P4, P6, N0, + ST_PYR, COLOR1, EL, EJ, P5, P6, N0, + ST_PYR, COLOR1, EJ, EI, P4, P5, N0, + ST_PYR, COLOR1, P3, EC, EL, P6, N0, + ST_TET, COLOR1, ED, EC, P3, N0, + // Case #121: (cloned #61) + ST_PNT, 0, COLOR1, 6, P4, P3, P0, P5, EL, EJ, + ST_WDG, COLOR0, EL, P2, EC, EJ, P1, EA, + ST_TET, COLOR0, EH, EK, P7, EG, + ST_WDG, COLOR1, EH, EG, EK, P4, P6, P3, + ST_PYR, COLOR1, EJ, P5, P6, EL, N0, + ST_TET, COLOR1, P5, P4, P6, N0, + ST_TET, COLOR1, P5, P0, P4, N0, + ST_PYR, COLOR1, EA, P0, P5, EJ, N0, + ST_PYR, COLOR1, EC, P3, P0, EA, N0, + ST_TET, COLOR1, P0, P3, P4, N0, + ST_PYR, COLOR1, EL, EC, EA, EJ, N0, + ST_TET, COLOR1, P3, P6, P4, N0, + ST_PYR, COLOR1, EL, P6, P3, EC, N0, + // Case #122: (cloned #91) + ST_TET, COLOR0, EH, EG, EK, P7, + ST_TET, COLOR0, EI, EA, P0, ED, + ST_TET, COLOR0, EL, P2, EB, EC, + ST_WDG, COLOR1, P3, P1, P6, EC, EB, EL, + ST_WDG, COLOR1, EI, ED, EA, P4, P3, P1, + ST_WDG, COLOR1, P4, P3, P6, EH, EK, EG, + ST_TET, COLOR1, P3, P1, P6, P4, + ST_TET, COLOR1, P4, P6, P5, P1, + // Case #123: (cloned #95) + ST_TET, COLOR0, EG, EK, EH, P7, + ST_TET, COLOR0, EL, EB, EC, P2, + ST_WDG, COLOR1, EC, EL, EB, P3, P6, P1, + ST_WDG, COLOR1, P3, P6, P4, EK, EG, EH, + ST_PYR, COLOR1, P0, P4, P5, P1, P3, + ST_TET, COLOR1, P6, P5, P4, P3, + ST_TET, COLOR1, P6, P1, P5, P3, + // Case #124: (cloned #61) + ST_PNT, 0, COLOR1, 6, P6, P4, P5, P2, ED, EB, + ST_WDG, COLOR0, ED, P0, EI, EB, P1, EJ, + ST_TET, COLOR0, EG, EH, P7, EK, + ST_WDG, COLOR1, EG, EK, EH, P6, P3, P4, + ST_PYR, COLOR1, EB, P2, P3, ED, N0, + ST_TET, COLOR1, P2, P6, P3, N0, + ST_TET, COLOR1, P2, P5, P6, N0, + ST_PYR, COLOR1, EJ, P5, P2, EB, N0, + ST_PYR, COLOR1, EI, P4, P5, EJ, N0, + ST_TET, COLOR1, P5, P4, P6, N0, + ST_PYR, COLOR1, ED, EI, EJ, EB, N0, + ST_TET, COLOR1, P4, P3, P6, N0, + ST_PYR, COLOR1, ED, P3, P4, EI, N0, + // Case #125: Unique case #20 + ST_TET, COLOR0, EG, EK, EH, P7, + ST_TET, COLOR0, EJ, EA, EB, P1, + ST_WDG, COLOR1, EH, EG, EK, P4, P6, P3, + ST_WDG, COLOR1, EJ, EA, EB, P5, P0, P2, + ST_TET, COLOR1, P2, P3, P0, P5, + ST_TET, COLOR1, P5, P4, P6, P3, + ST_TET, COLOR1, P4, P5, P0, P3, + ST_TET, COLOR1, P5, P6, P2, P3, + // Case #126: (cloned #95) + ST_TET, COLOR0, EI, ED, EA, P0, + ST_TET, COLOR0, EH, EG, EK, P7, + ST_WDG, COLOR1, EK, EH, EG, P3, P4, P6, + ST_WDG, COLOR1, P3, P4, P1, ED, EI, EA, + ST_PYR, COLOR1, P2, P1, P5, P6, P3, + ST_TET, COLOR1, P4, P5, P1, P3, + ST_TET, COLOR1, P4, P6, P5, P3, + // Case #127: Unique case #21 + ST_PNT, 0, COLOR1, 7, P0, P1, P2, P3, P4, P5, P6, + ST_TET, COLOR0, EH, EG, EK, P7, + ST_WDG, COLOR1, EH, EG, EK, P4, P6, P3, + ST_TET, COLOR1, P4, P3, P6, N0, + ST_PYR, COLOR1, P5, P6, P2, P1, N0, + ST_TET, COLOR1, P6, P3, P2, N0, + ST_PYR, COLOR1, P0, P1, P2, P3, N0, + ST_TET, COLOR1, P0, P3, P4, N0, + ST_PYR, COLOR1, P0, P4, P5, P1, N0, + ST_TET, COLOR1, P4, P6, P5, N0, + // Case #128: (cloned #1) + ST_PNT, 0, COLOR0, 7, P6, P5, P4, P3, P2, P1, P0, + ST_WDG, COLOR0, P6, P4, P3, EG, EH, EK, + ST_TET, COLOR0, P6, P4, P3, N0, + ST_TET, COLOR0, P6, P5, P4, N0, + ST_PYR, COLOR0, P1, P0, P4, P5, N0, + ST_PYR, COLOR0, P2, P1, P5, P6, N0, + ST_PYR, COLOR0, P3, P0, P1, P2, N0, + ST_TET, COLOR0, P4, P0, P3, N0, + ST_TET, COLOR0, P3, P2, P6, N0, + ST_TET, COLOR1, P7, EG, EH, EK, + // Case #129: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, EA, EG, + ST_PYR, COLOR0, P1, P5, P6, P2, N0, + ST_TET, COLOR0, P5, P4, P6, N0, + ST_TET, COLOR0, P1, P4, P5, N0, + ST_TET, COLOR0, P3, P1, P2, N0, + ST_TET, COLOR0, P6, P3, P2, N0, + ST_PYR, COLOR0, P6, EG, EK, P3, N0, + ST_PYR, COLOR0, P4, EH, EG, P6, N0, + ST_TET, COLOR0, P4, EI, EH, N0, + ST_PYR, COLOR0, P1, EA, EI, P4, N0, + ST_PYR, COLOR0, P1, P3, ED, EA, N0, + ST_TET, COLOR0, P3, EK, ED, N0, + ST_PYR, COLOR1, P0, ED, EK, P7, N0, + ST_PYR, COLOR1, EI, P0, P7, EH, N0, + ST_TET, COLOR1, EH, P7, EG, N0, + ST_TET, COLOR1, P7, EK, EG, N0, + ST_TET, COLOR1, EI, P0, N0, EA, + ST_TET, COLOR1, ED, P0, EA, N0, + // Case #130: (cloned #20) + ST_WDG, COLOR0, P4, P3, P6, EH, EK, EG, + ST_WDG, COLOR0, EA, EB, EJ, P0, P2, P5, + ST_TET, COLOR0, P3, P6, P4, P2, + ST_TET, COLOR0, P5, P6, P2, P4, + ST_TET, COLOR0, P0, P4, P5, P2, + ST_TET, COLOR0, P3, P0, P2, P4, + ST_TET, COLOR1, P1, EA, EJ, EB, + ST_TET, COLOR1, P7, EH, EK, EG, + // Case #131: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EJ, EB, EG, EG, + ST_PYR, COLOR0, P6, EG, EK, P3, N0, + ST_TET, COLOR0, EK, ED, P3, N0, + ST_PYR, COLOR0, P2, P3, ED, EB, N0, + ST_TET, COLOR0, P6, P3, P2, N0, + ST_TET, COLOR0, P4, EI, EH, N0, + ST_TET, COLOR0, P5, P4, P6, N0, + ST_PYR, COLOR0, P4, EH, EG, P6, N0, + ST_TET, COLOR0, P5, P6, P2, N0, + ST_PYR, COLOR0, P5, P2, EB, EJ, N0, + ST_PYR, COLOR0, P5, EJ, EI, P4, N0, + ST_PYR, COLOR1, P7, P0, ED, EK, N0, + ST_PYR, COLOR1, EI, P0, P7, EH, N0, + ST_TET, COLOR1, P7, EK, EG, N0, + ST_TET, COLOR1, EH, P7, EG, N0, + ST_PYR, COLOR1, ED, P0, P1, EB, N0, + ST_PYR, COLOR1, P0, EI, EJ, P1, N0, + ST_TET, COLOR1, EJ, EB, P1, N0, + // Case #132: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, EH, EB, + ST_PYR, COLOR0, P4, P5, P1, P0, N0, + ST_TET, COLOR0, P5, P6, P1, N0, + ST_TET, COLOR0, P4, P6, P5, N0, + ST_TET, COLOR0, P3, P4, P0, N0, + ST_TET, COLOR0, P1, P3, P0, N0, + ST_PYR, COLOR0, P1, EB, EC, P3, N0, + ST_PYR, COLOR0, P6, EL, EB, P1, N0, + ST_TET, COLOR0, P6, EG, EL, N0, + ST_PYR, COLOR0, P4, EH, EG, P6, N0, + ST_PYR, COLOR0, P4, P3, EK, EH, N0, + ST_TET, COLOR0, P3, EC, EK, N0, + ST_PYR, COLOR1, P7, EK, EC, P2, N0, + ST_PYR, COLOR1, EG, P7, P2, EL, N0, + ST_TET, COLOR1, EL, P2, EB, N0, + ST_TET, COLOR1, P2, EC, EB, N0, + ST_TET, COLOR1, EG, P7, N0, EH, + ST_TET, COLOR1, EK, P7, EH, N0, + // Case #133: (cloned #26) + ST_TET, COLOR0, P3, ED, EC, EK, + ST_TET, COLOR0, P6, P5, P4, P1, + ST_PYR, COLOR0, EA, EI, P4, P1, EH, + ST_PYR, COLOR0, EB, P1, P6, EL, EG, + ST_PYR, COLOR0, P4, EH, EG, P6, P1, + ST_PYR, COLOR0, EH, EA, EB, EG, P1, + ST_WDG, COLOR1, P0, P2, P7, ED, EC, EK, + ST_PYR, COLOR1, P0, P7, EH, EI, EA, + ST_PYR, COLOR1, EG, P7, P2, EL, EB, + ST_PYR, COLOR1, EA, EB, P2, P0, P7, + ST_PYR, COLOR1, EA, EH, EG, EB, P7, + // Case #134: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EJ, EA, EH, EH, + ST_PYR, COLOR0, P4, P3, EK, EH, N0, + ST_TET, COLOR0, EK, P3, EC, N0, + ST_PYR, COLOR0, P0, EA, EC, P3, N0, + ST_TET, COLOR0, P4, P0, P3, N0, + ST_TET, COLOR0, P6, EG, EL, N0, + ST_TET, COLOR0, P5, P4, P6, N0, + ST_PYR, COLOR0, P6, P4, EH, EG, N0, + ST_TET, COLOR0, P5, P0, P4, N0, + ST_PYR, COLOR0, P5, EJ, EA, P0, N0, + ST_PYR, COLOR0, P5, P6, EL, EJ, N0, + ST_PYR, COLOR1, P7, EK, EC, P2, N0, + ST_PYR, COLOR1, EL, EG, P7, P2, N0, + ST_TET, COLOR1, P7, EH, EK, N0, + ST_TET, COLOR1, EG, EH, P7, N0, + ST_PYR, COLOR1, EC, EA, P1, P2, N0, + ST_PYR, COLOR1, P2, P1, EJ, EL, N0, + ST_TET, COLOR1, EJ, P1, EA, N0, + // Case #135: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EJ, EL, EI, EH, EG, + ST_TET, COLOR0, P3, ED, EC, EK, + ST_PYR, COLOR0, P6, EL, EJ, P5, N0, + ST_PYR, COLOR0, P5, EJ, EI, P4, N0, + ST_TET, COLOR0, P4, EI, EH, N0, + ST_TET, COLOR0, P5, P4, P6, N0, + ST_PYR, COLOR0, P4, EH, EG, P6, N0, + ST_TET, COLOR0, P6, EG, EL, N0, + ST_WDG, COLOR1, P0, P2, P7, ED, EC, EK, + ST_TET, COLOR1, P2, P7, P0, N0, + ST_PYR, COLOR1, P0, P7, EH, EI, N0, + ST_TET, COLOR1, P1, P2, P0, N0, + ST_PYR, COLOR1, EL, P2, P1, EJ, N0, + ST_PYR, COLOR1, EJ, P1, P0, EI, N0, + ST_PYR, COLOR1, P7, P2, EL, EG, N0, + ST_TET, COLOR1, EH, P7, EG, N0, + // Case #136: (cloned #3) + ST_HEX, COLOR0, EH, P4, P0, ED, EG, P6, P2, EC, + ST_WDG, COLOR0, P4, P5, P6, P0, P1, P2, + ST_WDG, COLOR1, P7, EH, EG, P3, ED, EC, + // Case #137: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EA, EC, EI, EH, EG, + ST_PYR, COLOR0, P1, P5, P6, P2, N0, + ST_TET, COLOR0, P6, P4, N0, P5, + ST_PYR, COLOR0, P2, P6, EG, EC, N0, + ST_PYR, COLOR0, EA, P1, P2, EC, N0, + ST_TET, COLOR0, P4, P5, P1, N0, + ST_PYR, COLOR0, P4, P1, EA, EI, N0, + ST_TET, COLOR0, P4, EI, EH, N0, + ST_PYR, COLOR0, EG, P6, P4, EH, N0, + ST_PYR, COLOR1, EC, EG, P7, P3, N0, + ST_PYR, COLOR1, EA, EC, P3, P0, N0, + ST_TET, COLOR1, EI, EA, P0, N0, + ST_TET, COLOR1, P0, P3, P7, N0, + ST_PYR, COLOR1, EI, P0, P7, EH, N0, + ST_TET, COLOR1, P7, EG, EH, N0, + // Case #138: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EG, EH, EJ, EJ, + ST_PYR, COLOR0, P5, EJ, EA, P0, N0, + ST_TET, COLOR0, EA, ED, P0, N0, + ST_PYR, COLOR0, P4, P0, ED, EH, N0, + ST_TET, COLOR0, P5, P0, P4, N0, + ST_TET, COLOR0, P2, EC, EB, N0, + ST_TET, COLOR0, P6, P2, P5, N0, + ST_PYR, COLOR0, P2, EB, EJ, P5, N0, + ST_TET, COLOR0, P6, P5, P4, N0, + ST_PYR, COLOR0, P6, P4, EH, EG, N0, + ST_PYR, COLOR0, P6, EG, EC, P2, N0, + ST_PYR, COLOR1, P1, P3, ED, EA, N0, + ST_PYR, COLOR1, EC, P3, P1, EB, N0, + ST_TET, COLOR1, P1, EA, EJ, N0, + ST_TET, COLOR1, EB, P1, EJ, N0, + ST_PYR, COLOR1, ED, P3, P7, EH, N0, + ST_PYR, COLOR1, P3, EC, EG, P7, N0, + ST_TET, COLOR1, EG, EH, P7, N0, + // Case #139: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EI, EH, EG, EC, EB, EJ, + ST_TET, COLOR0, P6, P2, P5, N0, + ST_PYR, COLOR0, P6, EG, EC, P2, N0, + ST_TET, COLOR0, P4, P6, P5, N0, + ST_PYR, COLOR0, P4, EH, EG, P6, N0, + ST_TET, COLOR0, EI, EH, P4, N0, + ST_PYR, COLOR0, P4, P5, EJ, EI, N0, + ST_PYR, COLOR0, EJ, P5, P2, EB, N0, + ST_TET, COLOR0, P2, EC, EB, N0, + ST_TET, COLOR1, P0, P3, P7, N0, + ST_PYR, COLOR1, EI, P0, P7, EH, N0, + ST_PYR, COLOR1, EI, EJ, P1, P0, N0, + ST_TET, COLOR1, P1, P3, P0, N0, + ST_TET, COLOR1, P1, EJ, EB, N0, + ST_PYR, COLOR1, P1, EB, EC, P3, N0, + ST_PYR, COLOR1, EC, EG, P7, P3, N0, + ST_TET, COLOR1, EG, EH, P7, N0, + // Case #140: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EB, ED, EL, EG, EH, + ST_PYR, COLOR0, P1, P0, P4, P5, N0, + ST_TET, COLOR0, P4, N0, P6, P5, + ST_PYR, COLOR0, P0, ED, EH, P4, N0, + ST_PYR, COLOR0, EB, ED, P0, P1, N0, + ST_TET, COLOR0, P6, P1, P5, N0, + ST_PYR, COLOR0, P6, EL, EB, P1, N0, + ST_TET, COLOR0, P6, EG, EL, N0, + ST_PYR, COLOR0, EH, EG, P6, P4, N0, + ST_PYR, COLOR1, ED, P3, P7, EH, N0, + ST_PYR, COLOR1, EB, P2, P3, ED, N0, + ST_TET, COLOR1, EL, P2, EB, N0, + ST_TET, COLOR1, P2, P7, P3, N0, + ST_PYR, COLOR1, EL, EG, P7, P2, N0, + ST_TET, COLOR1, P7, EG, EH, N0, + // Case #141: (cloned #27) + ST_TET, COLOR0, P6, P5, P4, P1, + ST_PYR, COLOR0, EA, EI, P4, P1, EH, + ST_PYR, COLOR0, EB, P1, P6, EL, EG, + ST_PYR, COLOR0, P4, EH, EG, P6, P1, + ST_PYR, COLOR0, EH, EA, EB, EG, P1, + ST_TET, COLOR1, P3, P0, P2, P7, + ST_PYR, COLOR1, EH, EI, P0, P7, EA, + ST_PYR, COLOR1, EG, P7, P2, EL, EB, + ST_PYR, COLOR1, P0, EA, EB, P2, P7, + ST_PYR, COLOR1, EH, EG, EB, EA, P7, + // Case #142: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, ED, EA, EJ, EL, EG, EH, + ST_TET, COLOR0, P5, P4, P6, N0, + ST_PYR, COLOR0, P5, P6, EL, EJ, N0, + ST_TET, COLOR0, P0, P4, P5, N0, + ST_PYR, COLOR0, P0, P5, EJ, EA, N0, + ST_TET, COLOR0, ED, P0, EA, N0, + ST_PYR, COLOR0, P0, ED, EH, P4, N0, + ST_PYR, COLOR0, EH, EG, P6, P4, N0, + ST_TET, COLOR0, P6, EG, EL, N0, + ST_TET, COLOR1, P3, P1, P2, N0, + ST_PYR, COLOR1, ED, EA, P1, P3, N0, + ST_PYR, COLOR1, ED, P3, P7, EH, N0, + ST_TET, COLOR1, P7, P3, P2, N0, + ST_TET, COLOR1, P7, EG, EH, N0, + ST_PYR, COLOR1, P7, P2, EL, EG, N0, + ST_PYR, COLOR1, EL, P2, P1, EJ, N0, + ST_TET, COLOR1, EJ, P1, EA, N0, + // Case #143: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EL, EJ, EI, EG, EH, + ST_PYR, COLOR0, P5, EJ, EI, P4, N0, + ST_TET, COLOR0, P4, EI, EH, N0, + ST_PYR, COLOR0, P6, EL, EJ, P5, N0, + ST_TET, COLOR0, EG, EL, P6, N0, + ST_PYR, COLOR0, EH, EG, P6, P4, N0, + ST_TET, COLOR0, P4, P6, P5, N0, + ST_PYR, COLOR1, P3, P0, P1, P2, N0, + ST_TET, COLOR1, P0, P3, P7, N0, + ST_TET, COLOR1, P7, P3, P2, N0, + ST_PYR, COLOR1, P7, P2, EL, EG, N0, + ST_PYR, COLOR1, EL, P2, P1, EJ, N0, + ST_PYR, COLOR1, EJ, P1, P0, EI, N0, + ST_PYR, COLOR1, EI, P0, P7, EH, N0, + ST_TET, COLOR1, EG, EH, P7, N0, + // Case #144: (cloned #3) + ST_HEX, COLOR0, EG, P6, P5, EE, EK, P3, P0, EI, + ST_WDG, COLOR0, P0, P1, P5, P3, P2, P6, + ST_WDG, COLOR1, P4, EI, EE, P7, EK, EG, + // Case #145: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EA, EE, ED, EK, EG, + ST_PYR, COLOR0, P1, P5, P6, P2, N0, + ST_TET, COLOR0, P6, N0, P3, P2, + ST_PYR, COLOR0, P5, EE, EG, P6, N0, + ST_PYR, COLOR0, EA, EE, P5, P1, N0, + ST_TET, COLOR0, P3, P1, P2, N0, + ST_PYR, COLOR0, P3, ED, EA, P1, N0, + ST_TET, COLOR0, P3, EK, ED, N0, + ST_PYR, COLOR0, EG, EK, P3, P6, N0, + ST_PYR, COLOR1, EE, P4, P7, EG, N0, + ST_PYR, COLOR1, EA, P0, P4, EE, N0, + ST_TET, COLOR1, ED, P0, EA, N0, + ST_TET, COLOR1, P0, P7, P4, N0, + ST_PYR, COLOR1, ED, EK, P7, P0, N0, + ST_TET, COLOR1, P7, EK, EG, N0, + // Case #146: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EG, EK, EB, EB, + ST_PYR, COLOR0, P2, P0, EA, EB, N0, + ST_TET, COLOR0, EA, P0, EI, N0, + ST_PYR, COLOR0, P3, EK, EI, P0, N0, + ST_TET, COLOR0, P2, P3, P0, N0, + ST_TET, COLOR0, P5, EJ, EE, N0, + ST_TET, COLOR0, P6, P2, P5, N0, + ST_PYR, COLOR0, P5, P2, EB, EJ, N0, + ST_TET, COLOR0, P6, P3, P2, N0, + ST_PYR, COLOR0, P6, EG, EK, P3, N0, + ST_PYR, COLOR0, P6, P5, EE, EG, N0, + ST_PYR, COLOR1, P1, EA, EI, P4, N0, + ST_PYR, COLOR1, EE, EJ, P1, P4, N0, + ST_TET, COLOR1, P1, EB, EA, N0, + ST_TET, COLOR1, EJ, EB, P1, N0, + ST_PYR, COLOR1, EI, EK, P7, P4, N0, + ST_PYR, COLOR1, P4, P7, EG, EE, N0, + ST_TET, COLOR1, EG, P7, EK, N0, + // Case #147: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, ED, EK, EG, EE, EJ, EB, + ST_TET, COLOR0, P6, P2, P5, N0, + ST_PYR, COLOR0, P6, P5, EE, EG, N0, + ST_TET, COLOR0, P3, P2, P6, N0, + ST_PYR, COLOR0, P3, P6, EG, EK, N0, + ST_TET, COLOR0, ED, P3, EK, N0, + ST_PYR, COLOR0, P3, ED, EB, P2, N0, + ST_PYR, COLOR0, EB, EJ, P5, P2, N0, + ST_TET, COLOR0, P5, EJ, EE, N0, + ST_TET, COLOR1, P0, P7, P4, N0, + ST_PYR, COLOR1, ED, EK, P7, P0, N0, + ST_PYR, COLOR1, ED, P0, P1, EB, N0, + ST_TET, COLOR1, P1, P0, P4, N0, + ST_TET, COLOR1, P1, EJ, EB, N0, + ST_PYR, COLOR1, P1, P4, EE, EJ, N0, + ST_PYR, COLOR1, EE, P4, P7, EG, N0, + ST_TET, COLOR1, EG, P7, EK, N0, + // Case #148: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EE, EI, EB, EB, + ST_PYR, COLOR0, P1, EB, EC, P3, N0, + ST_TET, COLOR0, EC, EK, P3, N0, + ST_PYR, COLOR0, P0, P3, EK, EI, N0, + ST_TET, COLOR0, P1, P3, P0, N0, + ST_TET, COLOR0, P6, EG, EL, N0, + ST_TET, COLOR0, P5, P6, P1, N0, + ST_PYR, COLOR0, P6, EL, EB, P1, N0, + ST_TET, COLOR0, P5, P1, P0, N0, + ST_PYR, COLOR0, P5, P0, EI, EE, N0, + ST_PYR, COLOR0, P5, EE, EG, P6, N0, + ST_PYR, COLOR1, P2, P7, EK, EC, N0, + ST_PYR, COLOR1, EG, P7, P2, EL, N0, + ST_TET, COLOR1, P2, EC, EB, N0, + ST_TET, COLOR1, EL, P2, EB, N0, + ST_PYR, COLOR1, EK, P7, P4, EI, N0, + ST_PYR, COLOR1, P7, EG, EE, P4, N0, + ST_TET, COLOR1, EE, EI, P4, N0, + // Case #149: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EE, EG, EA, EB, EL, + ST_TET, COLOR0, P3, EK, ED, EC, + ST_PYR, COLOR0, P6, P5, EE, EG, N0, + ST_PYR, COLOR0, P5, P1, EA, EE, N0, + ST_TET, COLOR0, P1, EB, EA, N0, + ST_TET, COLOR0, P5, P6, P1, N0, + ST_PYR, COLOR0, P1, P6, EL, EB, N0, + ST_TET, COLOR0, P6, EG, EL, N0, + ST_WDG, COLOR1, ED, EK, EC, P0, P7, P2, + ST_TET, COLOR1, P7, P0, P2, N0, + ST_PYR, COLOR1, P0, EA, EB, P2, N0, + ST_TET, COLOR1, P4, P0, P7, N0, + ST_PYR, COLOR1, EG, EE, P4, P7, N0, + ST_PYR, COLOR1, EE, EA, P0, P4, N0, + ST_PYR, COLOR1, P2, EL, EG, P7, N0, + ST_TET, COLOR1, EB, EL, P2, N0, + // Case #150: (cloned #60) + ST_WDG, COLOR0, P3, EK, EC, P0, EI, EA, + ST_WDG, COLOR0, P6, EL, EG, P5, EJ, EE, + ST_HEX, COLOR1, P4, P1, P2, P7, EE, EJ, EL, EG, + ST_HEX, COLOR1, EI, EA, EC, EK, P4, P1, P2, P7, + // Case #151: (cloned #61) + ST_PNT, 0, COLOR1, 6, P0, P7, P4, P1, EL, EJ, + ST_WDG, COLOR0, EJ, P5, EE, EL, P6, EG, + ST_TET, COLOR0, ED, P3, EK, EC, + ST_WDG, COLOR1, P0, P2, P7, ED, EC, EK, + ST_PYR, COLOR1, EJ, EL, P2, P1, N0, + ST_TET, COLOR1, P1, P2, P0, N0, + ST_TET, COLOR1, P1, P0, P4, N0, + ST_PYR, COLOR1, EE, EJ, P1, P4, N0, + ST_PYR, COLOR1, EG, EE, P4, P7, N0, + ST_TET, COLOR1, P4, P0, P7, N0, + ST_PYR, COLOR1, EL, EJ, EE, EG, N0, + ST_TET, COLOR1, P7, P0, P2, N0, + ST_PYR, COLOR1, EL, EG, P7, P2, N0, + // Case #152: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EC, EG, ED, EI, EE, + ST_PYR, COLOR0, P2, P1, P5, P6, N0, + ST_TET, COLOR0, P5, P0, N0, P1, + ST_PYR, COLOR0, P6, P5, EE, EG, N0, + ST_PYR, COLOR0, EC, P2, P6, EG, N0, + ST_TET, COLOR0, P0, P1, P2, N0, + ST_PYR, COLOR0, P0, P2, EC, ED, N0, + ST_TET, COLOR0, P0, ED, EI, N0, + ST_PYR, COLOR0, EE, P5, P0, EI, N0, + ST_PYR, COLOR1, EG, EE, P4, P7, N0, + ST_PYR, COLOR1, EC, EG, P7, P3, N0, + ST_TET, COLOR1, ED, EC, P3, N0, + ST_TET, COLOR1, P3, P7, P4, N0, + ST_PYR, COLOR1, ED, P3, P4, EI, N0, + ST_TET, COLOR1, P4, EE, EI, N0, + // Case #153: (cloned #15) + ST_HEX, COLOR0, P1, P5, P6, P2, EA, EE, EG, EC, + ST_HEX, COLOR1, EA, EE, EG, EC, P0, P4, P7, P3, + // Case #154: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EG, EE, EC, EB, EJ, + ST_TET, COLOR0, P0, ED, EI, EA, + ST_PYR, COLOR0, P5, EE, EG, P6, N0, + ST_PYR, COLOR0, P6, EG, EC, P2, N0, + ST_TET, COLOR0, P2, EC, EB, N0, + ST_TET, COLOR0, P6, P2, P5, N0, + ST_PYR, COLOR0, P2, EB, EJ, P5, N0, + ST_TET, COLOR0, P5, EJ, EE, N0, + ST_WDG, COLOR1, P3, P4, P1, ED, EI, EA, + ST_TET, COLOR1, P4, P1, P3, N0, + ST_PYR, COLOR1, P3, P1, EB, EC, N0, + ST_TET, COLOR1, P7, P4, P3, N0, + ST_PYR, COLOR1, EE, P4, P7, EG, N0, + ST_PYR, COLOR1, EG, P7, P3, EC, N0, + ST_PYR, COLOR1, P1, P4, EE, EJ, N0, + ST_TET, COLOR1, EB, P1, EJ, N0, + // Case #155: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EE, EG, EC, EJ, EB, + ST_PYR, COLOR0, P6, EG, EC, P2, N0, + ST_TET, COLOR0, P2, EC, EB, N0, + ST_PYR, COLOR0, P5, EE, EG, P6, N0, + ST_TET, COLOR0, EJ, EE, P5, N0, + ST_PYR, COLOR0, EB, EJ, P5, P2, N0, + ST_TET, COLOR0, P2, P5, P6, N0, + ST_PYR, COLOR1, P0, P3, P7, P4, N0, + ST_TET, COLOR1, P3, P0, P1, N0, + ST_TET, COLOR1, P1, P0, P4, N0, + ST_PYR, COLOR1, P1, P4, EE, EJ, N0, + ST_PYR, COLOR1, EE, P4, P7, EG, N0, + ST_PYR, COLOR1, EG, P7, P3, EC, N0, + ST_PYR, COLOR1, EC, P3, P1, EB, N0, + ST_TET, COLOR1, EJ, EB, P1, N0, + // Case #156: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, ED, EI, EE, EG, EL, EB, + ST_TET, COLOR0, P5, P6, P1, N0, + ST_PYR, COLOR0, P5, EE, EG, P6, N0, + ST_TET, COLOR0, P0, P5, P1, N0, + ST_PYR, COLOR0, P0, EI, EE, P5, N0, + ST_TET, COLOR0, ED, EI, P0, N0, + ST_PYR, COLOR0, P0, P1, EB, ED, N0, + ST_PYR, COLOR0, EB, P1, P6, EL, N0, + ST_TET, COLOR0, P6, EG, EL, N0, + ST_TET, COLOR1, P3, P7, P4, N0, + ST_PYR, COLOR1, ED, P3, P4, EI, N0, + ST_PYR, COLOR1, ED, EB, P2, P3, N0, + ST_TET, COLOR1, P2, P7, P3, N0, + ST_TET, COLOR1, P2, EB, EL, N0, + ST_PYR, COLOR1, P2, EL, EG, P7, N0, + ST_PYR, COLOR1, EG, EE, P4, P7, N0, + ST_TET, COLOR1, EE, EI, P4, N0, + // Case #157: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EG, EE, EA, EL, EB, + ST_PYR, COLOR0, P5, P1, EA, EE, N0, + ST_TET, COLOR0, P1, EB, EA, N0, + ST_PYR, COLOR0, P6, P5, EE, EG, N0, + ST_TET, COLOR0, EL, P6, EG, N0, + ST_PYR, COLOR0, EB, P1, P6, EL, N0, + ST_TET, COLOR0, P1, P5, P6, N0, + ST_PYR, COLOR1, P3, P7, P4, P0, N0, + ST_TET, COLOR1, P0, P2, P3, N0, + ST_TET, COLOR1, P2, P7, P3, N0, + ST_PYR, COLOR1, P2, EL, EG, P7, N0, + ST_PYR, COLOR1, EG, EE, P4, P7, N0, + ST_PYR, COLOR1, EE, EA, P0, P4, N0, + ST_PYR, COLOR1, EA, EB, P2, P0, N0, + ST_TET, COLOR1, EL, P2, EB, N0, + // Case #158: (cloned #61) + ST_PNT, 0, COLOR1, 6, P3, P4, P7, P2, EJ, EL, + ST_WDG, COLOR0, EJ, P5, EE, EL, P6, EG, + ST_TET, COLOR0, ED, EI, P0, EA, + ST_WDG, COLOR1, ED, EA, EI, P3, P1, P4, + ST_PYR, COLOR1, EL, P2, P1, EJ, N0, + ST_TET, COLOR1, P2, P3, P1, N0, + ST_TET, COLOR1, P2, P7, P3, N0, + ST_PYR, COLOR1, EG, P7, P2, EL, N0, + ST_PYR, COLOR1, EE, P4, P7, EG, N0, + ST_TET, COLOR1, P7, P4, P3, N0, + ST_PYR, COLOR1, EJ, EE, EG, EL, N0, + ST_TET, COLOR1, P4, P1, P3, N0, + ST_PYR, COLOR1, EJ, P1, P4, EE, N0, + // Case #159: (cloned #63) + ST_WDG, COLOR0, P5, EE, EJ, P6, EG, EL, + ST_HEX, COLOR1, P4, P1, P2, P7, EE, EJ, EL, EG, + ST_WDG, COLOR1, P3, P7, P2, P0, P4, P1, + // Case #160: (cloned #5) + ST_PNT, 0, NOCOLOR, 2, EK, EJ, + ST_PYR, COLOR0, P3, P0, P1, P2, N0, + ST_TET, COLOR0, P2, P1, P6, N0, + ST_TET, COLOR0, P3, P2, P6, N0, + ST_TET, COLOR0, P4, P0, P3, N0, + ST_TET, COLOR0, P1, P0, P4, N0, + ST_PYR, COLOR0, P1, P4, EE, EJ, N0, + ST_PYR, COLOR0, P6, P1, EJ, EF, N0, + ST_TET, COLOR0, P6, EF, EG, N0, + ST_PYR, COLOR0, P3, P6, EG, EK, N0, + ST_PYR, COLOR0, P3, EK, EH, P4, N0, + ST_TET, COLOR0, P4, EH, EE, N0, + ST_PYR, COLOR1, P7, P5, EE, EH, N0, + ST_PYR, COLOR1, EG, EF, P5, P7, N0, + ST_TET, COLOR1, EF, EJ, P5, N0, + ST_TET, COLOR1, P5, EJ, EE, N0, + ST_TET, COLOR1, EG, N0, P7, EK, + ST_TET, COLOR1, EH, EK, P7, N0, + // Case #161: (cloned #26) + ST_TET, COLOR0, P4, EE, EI, EH, + ST_TET, COLOR0, P6, P3, P2, P1, + ST_PYR, COLOR0, EA, P1, P3, ED, EK, + ST_PYR, COLOR0, EJ, EF, P6, P1, EG, + ST_PYR, COLOR0, P3, P6, EG, EK, P1, + ST_PYR, COLOR0, EK, EG, EJ, EA, P1, + ST_WDG, COLOR1, EI, EE, EH, P0, P5, P7, + ST_PYR, COLOR1, P0, ED, EK, P7, EA, + ST_PYR, COLOR1, EG, EF, P5, P7, EJ, + ST_PYR, COLOR1, EA, P0, P5, EJ, P7, + ST_PYR, COLOR1, EA, EJ, EG, EK, P7, + // Case #162: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EB, EA, EK, EK, + ST_PYR, COLOR0, P3, EK, EH, P4, N0, + ST_TET, COLOR0, EH, EE, P4, N0, + ST_PYR, COLOR0, P0, P4, EE, EA, N0, + ST_TET, COLOR0, P3, P4, P0, N0, + ST_TET, COLOR0, P6, EF, EG, N0, + ST_TET, COLOR0, P2, P6, P3, N0, + ST_PYR, COLOR0, P6, EG, EK, P3, N0, + ST_TET, COLOR0, P2, P3, P0, N0, + ST_PYR, COLOR0, P2, P0, EA, EB, N0, + ST_PYR, COLOR0, P2, EB, EF, P6, N0, + ST_PYR, COLOR1, P7, P5, EE, EH, N0, + ST_PYR, COLOR1, EF, P5, P7, EG, N0, + ST_TET, COLOR1, P7, EH, EK, N0, + ST_TET, COLOR1, EG, P7, EK, N0, + ST_PYR, COLOR1, EE, P5, P1, EA, N0, + ST_PYR, COLOR1, P5, EF, EB, P1, N0, + ST_TET, COLOR1, EB, EA, P1, N0, + // Case #163: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EB, EF, ED, EK, EG, + ST_TET, COLOR0, P4, EE, EI, EH, + ST_PYR, COLOR0, P6, P2, EB, EF, N0, + ST_PYR, COLOR0, P2, P3, ED, EB, N0, + ST_TET, COLOR0, P3, EK, ED, N0, + ST_TET, COLOR0, P2, P6, P3, N0, + ST_PYR, COLOR0, P3, P6, EG, EK, N0, + ST_TET, COLOR0, P6, EF, EG, N0, + ST_WDG, COLOR1, EI, EE, EH, P0, P5, P7, + ST_TET, COLOR1, P5, P0, P7, N0, + ST_PYR, COLOR1, P0, ED, EK, P7, N0, + ST_TET, COLOR1, P1, P0, P5, N0, + ST_PYR, COLOR1, EF, EB, P1, P5, N0, + ST_PYR, COLOR1, EB, ED, P0, P1, N0, + ST_PYR, COLOR1, P7, EG, EF, P5, N0, + ST_TET, COLOR1, EK, EG, P7, N0, + // Case #164: (cloned #26) + ST_TET, COLOR0, P6, EF, EG, EL, + ST_TET, COLOR0, P3, P0, P1, P4, + ST_PYR, COLOR0, EE, EJ, P1, P4, EB, + ST_PYR, COLOR0, EH, P4, P3, EK, EC, + ST_PYR, COLOR0, P1, EB, EC, P3, P4, + ST_PYR, COLOR0, EB, EE, EH, EC, P4, + ST_WDG, COLOR1, P5, P7, P2, EF, EG, EL, + ST_PYR, COLOR1, P5, P2, EB, EJ, EE, + ST_PYR, COLOR1, EC, P2, P7, EK, EH, + ST_PYR, COLOR1, EE, EH, P7, P5, P2, + ST_PYR, COLOR1, EE, EB, EC, EH, P2, + // Case #165: (cloned #90) + ST_TET, COLOR0, EH, EI, EE, P4, + ST_TET, COLOR0, EK, EC, ED, P3, + ST_TET, COLOR0, EG, EF, EL, P6, + ST_TET, COLOR0, EB, EJ, EA, P1, + ST_WDG, COLOR1, P2, P0, P5, EB, EA, EJ, + ST_TET, COLOR1, P2, P0, P5, P7, + ST_WDG, COLOR1, EG, EF, EL, P7, P5, P2, + ST_WDG, COLOR1, ED, EK, EC, P0, P7, P2, + ST_WDG, COLOR1, EE, EH, EI, P5, P7, P0, + // Case #166: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EA, EC, EE, EH, EK, + ST_TET, COLOR0, P6, EL, EF, EG, + ST_PYR, COLOR0, P3, P0, EA, EC, N0, + ST_PYR, COLOR0, P0, P4, EE, EA, N0, + ST_TET, COLOR0, P4, EH, EE, N0, + ST_TET, COLOR0, P0, P3, P4, N0, + ST_PYR, COLOR0, P4, P3, EK, EH, N0, + ST_TET, COLOR0, P3, EC, EK, N0, + ST_WDG, COLOR1, EF, EL, EG, P5, P2, P7, + ST_TET, COLOR1, P2, P5, P7, N0, + ST_PYR, COLOR1, P5, EE, EH, P7, N0, + ST_TET, COLOR1, P1, P5, P2, N0, + ST_PYR, COLOR1, EC, EA, P1, P2, N0, + ST_PYR, COLOR1, EA, EE, P5, P1, N0, + ST_PYR, COLOR1, P7, EK, EC, P2, N0, + ST_TET, COLOR1, EH, EK, P7, N0, + // Case #167: (cloned #91) + ST_TET, COLOR0, EL, EG, EF, P6, + ST_TET, COLOR0, EC, P3, ED, EK, + ST_TET, COLOR0, EE, EI, P4, EH, + ST_WDG, COLOR1, EH, EI, EE, P7, P0, P5, + ST_WDG, COLOR1, P2, P7, P0, EC, EK, ED, + ST_WDG, COLOR1, EL, EG, EF, P2, P7, P5, + ST_TET, COLOR1, P7, P5, P0, P2, + ST_TET, COLOR1, P2, P1, P5, P0, + // Case #168: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EC, ED, EJ, EJ, + ST_PYR, COLOR0, P1, P4, EE, EJ, N0, + ST_TET, COLOR0, EE, P4, EH, N0, + ST_PYR, COLOR0, P0, ED, EH, P4, N0, + ST_TET, COLOR0, P1, P0, P4, N0, + ST_TET, COLOR0, P6, EF, EG, N0, + ST_TET, COLOR0, P2, P1, P6, N0, + ST_PYR, COLOR0, P6, P1, EJ, EF, N0, + ST_TET, COLOR0, P2, P0, P1, N0, + ST_PYR, COLOR0, P2, EC, ED, P0, N0, + ST_PYR, COLOR0, P2, P6, EG, EC, N0, + ST_PYR, COLOR1, P5, EE, EH, P7, N0, + ST_PYR, COLOR1, EG, EF, P5, P7, N0, + ST_TET, COLOR1, P5, EJ, EE, N0, + ST_TET, COLOR1, EF, EJ, P5, N0, + ST_PYR, COLOR1, EH, ED, P3, P7, N0, + ST_PYR, COLOR1, P7, P3, EC, EG, N0, + ST_TET, COLOR1, EC, P3, ED, N0, + // Case #169: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EC, EG, EA, EJ, EF, + ST_TET, COLOR0, P4, EI, EH, EE, + ST_PYR, COLOR0, P6, EG, EC, P2, N0, + ST_PYR, COLOR0, P2, EC, EA, P1, N0, + ST_TET, COLOR0, P1, EA, EJ, N0, + ST_TET, COLOR0, P2, P1, P6, N0, + ST_PYR, COLOR0, P1, EJ, EF, P6, N0, + ST_TET, COLOR0, P6, EF, EG, N0, + ST_WDG, COLOR1, P0, P7, P5, EI, EH, EE, + ST_TET, COLOR1, P7, P5, P0, N0, + ST_PYR, COLOR1, P0, P5, EJ, EA, N0, + ST_TET, COLOR1, P3, P7, P0, N0, + ST_PYR, COLOR1, EG, P7, P3, EC, N0, + ST_PYR, COLOR1, EC, P3, P0, EA, N0, + ST_PYR, COLOR1, P5, P7, EG, EF, N0, + ST_TET, COLOR1, EJ, P5, EF, N0, + // Case #170: (cloned #60) + ST_WDG, COLOR0, P0, ED, EA, P4, EH, EE, + ST_WDG, COLOR0, P2, EB, EC, P6, EF, EG, + ST_HEX, COLOR1, EC, EB, EF, EG, P3, P1, P5, P7, + ST_HEX, COLOR1, P3, P1, P5, P7, ED, EA, EE, EH, + // Case #171: (cloned #61) + ST_PNT, 0, COLOR1, 6, P0, P7, P3, P1, EF, EB, + ST_WDG, COLOR0, EF, P6, EG, EB, P2, EC, + ST_TET, COLOR0, EI, EH, P4, EE, + ST_WDG, COLOR1, EI, EE, EH, P0, P5, P7, + ST_PYR, COLOR1, EB, P1, P5, EF, N0, + ST_TET, COLOR1, P1, P0, P5, N0, + ST_TET, COLOR1, P1, P3, P0, N0, + ST_PYR, COLOR1, EC, P3, P1, EB, N0, + ST_PYR, COLOR1, EG, P7, P3, EC, N0, + ST_TET, COLOR1, P3, P7, P0, N0, + ST_PYR, COLOR1, EF, EG, EC, EB, N0, + ST_TET, COLOR1, P7, P5, P0, N0, + ST_PYR, COLOR1, EF, P5, P7, EG, N0, + // Case #172: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, ED, EH, EB, EJ, EE, + ST_TET, COLOR0, P6, EG, EL, EF, + ST_PYR, COLOR0, P4, P0, ED, EH, N0, + ST_PYR, COLOR0, P0, P1, EB, ED, N0, + ST_TET, COLOR0, P1, EJ, EB, N0, + ST_TET, COLOR0, P0, P4, P1, N0, + ST_PYR, COLOR0, P1, P4, EE, EJ, N0, + ST_TET, COLOR0, P4, EH, EE, N0, + ST_WDG, COLOR1, EL, EG, EF, P2, P7, P5, + ST_TET, COLOR1, P7, P2, P5, N0, + ST_PYR, COLOR1, P2, EB, EJ, P5, N0, + ST_TET, COLOR1, P3, P2, P7, N0, + ST_PYR, COLOR1, EH, ED, P3, P7, N0, + ST_PYR, COLOR1, ED, EB, P2, P3, N0, + ST_PYR, COLOR1, P5, EE, EH, P7, N0, + ST_TET, COLOR1, EJ, EE, P5, N0, + // Case #173: (cloned #91) + ST_TET, COLOR0, EG, EF, EL, P6, + ST_TET, COLOR0, EH, P4, EI, EE, + ST_TET, COLOR0, EB, EA, P1, EJ, + ST_WDG, COLOR1, EJ, EA, EB, P5, P0, P2, + ST_WDG, COLOR1, P7, P5, P0, EH, EE, EI, + ST_WDG, COLOR1, EG, EF, EL, P7, P5, P2, + ST_TET, COLOR1, P5, P2, P0, P7, + ST_TET, COLOR1, P7, P3, P2, P0, + // Case #174: (cloned #61) + ST_PNT, 0, COLOR1, 6, P2, P5, P1, P3, EH, ED, + ST_WDG, COLOR0, EH, P4, EE, ED, P0, EA, + ST_TET, COLOR0, EL, EF, P6, EG, + ST_WDG, COLOR1, EL, EG, EF, P2, P7, P5, + ST_PYR, COLOR1, ED, P3, P7, EH, N0, + ST_TET, COLOR1, P3, P2, P7, N0, + ST_TET, COLOR1, P3, P1, P2, N0, + ST_PYR, COLOR1, EA, P1, P3, ED, N0, + ST_PYR, COLOR1, EE, P5, P1, EA, N0, + ST_TET, COLOR1, P1, P5, P2, N0, + ST_PYR, COLOR1, EH, EE, EA, ED, N0, + ST_TET, COLOR1, P5, P7, P2, N0, + ST_PYR, COLOR1, EH, P7, P5, EE, N0, + // Case #175: (cloned #95) + ST_TET, COLOR0, EE, EH, EI, P4, + ST_TET, COLOR0, EF, EL, EG, P6, + ST_WDG, COLOR1, EG, EF, EL, P7, P5, P2, + ST_WDG, COLOR1, P7, P5, P0, EH, EE, EI, + ST_PYR, COLOR1, P3, P0, P1, P2, P7, + ST_TET, COLOR1, P5, P1, P0, P7, + ST_TET, COLOR1, P5, P2, P1, P7, + // Case #176: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EJ, EI, EF, EG, EK, + ST_PYR, COLOR0, P1, P2, P3, P0, N0, + ST_TET, COLOR0, P3, P6, N0, P2, + ST_PYR, COLOR0, P0, P3, EK, EI, N0, + ST_PYR, COLOR0, EJ, P1, P0, EI, N0, + ST_TET, COLOR0, P6, P2, P1, N0, + ST_PYR, COLOR0, P6, P1, EJ, EF, N0, + ST_TET, COLOR0, P6, EF, EG, N0, + ST_PYR, COLOR0, EK, P3, P6, EG, N0, + ST_PYR, COLOR1, EI, EK, P7, P4, N0, + ST_PYR, COLOR1, EJ, EI, P4, P5, N0, + ST_TET, COLOR1, EF, EJ, P5, N0, + ST_TET, COLOR1, P5, P4, P7, N0, + ST_PYR, COLOR1, EF, P5, P7, EG, N0, + ST_TET, COLOR1, P7, EK, EG, N0, + // Case #177: (cloned #27) + ST_TET, COLOR0, P6, P3, P2, P1, + ST_PYR, COLOR0, EA, P1, P3, ED, EK, + ST_PYR, COLOR0, EJ, EF, P6, P1, EG, + ST_PYR, COLOR0, P3, P6, EG, EK, P1, + ST_PYR, COLOR0, EK, EG, EJ, EA, P1, + ST_TET, COLOR1, P4, P5, P0, P7, + ST_PYR, COLOR1, EK, P7, P0, ED, EA, + ST_PYR, COLOR1, EG, EF, P5, P7, EJ, + ST_PYR, COLOR1, P0, P5, EJ, EA, P7, + ST_PYR, COLOR1, EK, EA, EJ, EG, P7, + // Case #178: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EI, EA, EB, EF, EG, EK, + ST_TET, COLOR0, P2, P6, P3, N0, + ST_PYR, COLOR0, P2, EB, EF, P6, N0, + ST_TET, COLOR0, P0, P2, P3, N0, + ST_PYR, COLOR0, P0, EA, EB, P2, N0, + ST_TET, COLOR0, EI, EA, P0, N0, + ST_PYR, COLOR0, P0, P3, EK, EI, N0, + ST_PYR, COLOR0, EK, P3, P6, EG, N0, + ST_TET, COLOR0, P6, EF, EG, N0, + ST_TET, COLOR1, P4, P5, P1, N0, + ST_PYR, COLOR1, EI, P4, P1, EA, N0, + ST_PYR, COLOR1, EI, EK, P7, P4, N0, + ST_TET, COLOR1, P7, P5, P4, N0, + ST_TET, COLOR1, P7, EK, EG, N0, + ST_PYR, COLOR1, P7, EG, EF, P5, N0, + ST_PYR, COLOR1, EF, EB, P1, P5, N0, + ST_TET, COLOR1, EB, EA, P1, N0, + // Case #179: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EF, EB, ED, EG, EK, + ST_PYR, COLOR0, P2, P3, ED, EB, N0, + ST_TET, COLOR0, P3, EK, ED, N0, + ST_PYR, COLOR0, P6, P2, EB, EF, N0, + ST_TET, COLOR0, EG, P6, EF, N0, + ST_PYR, COLOR0, EK, P3, P6, EG, N0, + ST_TET, COLOR0, P3, P2, P6, N0, + ST_PYR, COLOR1, P4, P5, P1, P0, N0, + ST_TET, COLOR1, P0, P7, P4, N0, + ST_TET, COLOR1, P7, P5, P4, N0, + ST_PYR, COLOR1, P7, EG, EF, P5, N0, + ST_PYR, COLOR1, EF, EB, P1, P5, N0, + ST_PYR, COLOR1, EB, ED, P0, P1, N0, + ST_PYR, COLOR1, ED, EK, P7, P0, N0, + ST_TET, COLOR1, EG, P7, EK, N0, + // Case #180: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EI, EK, EJ, EB, EC, + ST_TET, COLOR0, P6, EF, EG, EL, + ST_PYR, COLOR0, P3, EK, EI, P0, N0, + ST_PYR, COLOR0, P0, EI, EJ, P1, N0, + ST_TET, COLOR0, P1, EJ, EB, N0, + ST_TET, COLOR0, P0, P1, P3, N0, + ST_PYR, COLOR0, P1, EB, EC, P3, N0, + ST_TET, COLOR0, P3, EC, EK, N0, + ST_WDG, COLOR1, P5, P7, P2, EF, EG, EL, + ST_TET, COLOR1, P7, P2, P5, N0, + ST_PYR, COLOR1, P5, P2, EB, EJ, N0, + ST_TET, COLOR1, P4, P7, P5, N0, + ST_PYR, COLOR1, EK, P7, P4, EI, N0, + ST_PYR, COLOR1, EI, P4, P5, EJ, N0, + ST_PYR, COLOR1, P2, P7, EK, EC, N0, + ST_TET, COLOR1, EB, P2, EC, N0, + // Case #181: (cloned #91) + ST_TET, COLOR0, EG, EF, EL, P6, + ST_TET, COLOR0, EK, ED, P3, EC, + ST_TET, COLOR0, EJ, P1, EA, EB, + ST_WDG, COLOR1, P2, P0, P5, EB, EA, EJ, + ST_WDG, COLOR1, EK, EC, ED, P7, P2, P0, + ST_WDG, COLOR1, P7, P2, P5, EG, EL, EF, + ST_TET, COLOR1, P2, P0, P5, P7, + ST_TET, COLOR1, P7, P5, P4, P0, + // Case #182: (cloned #61) + ST_PNT, 0, COLOR1, 6, P5, P2, P1, P4, EK, EI, + ST_WDG, COLOR0, EI, P0, EA, EK, P3, EC, + ST_TET, COLOR0, EF, P6, EL, EG, + ST_WDG, COLOR1, P5, P7, P2, EF, EG, EL, + ST_PYR, COLOR1, EI, EK, P7, P4, N0, + ST_TET, COLOR1, P4, P7, P5, N0, + ST_TET, COLOR1, P4, P5, P1, N0, + ST_PYR, COLOR1, EA, EI, P4, P1, N0, + ST_PYR, COLOR1, EC, EA, P1, P2, N0, + ST_TET, COLOR1, P1, P5, P2, N0, + ST_PYR, COLOR1, EK, EI, EA, EC, N0, + ST_TET, COLOR1, P2, P5, P7, N0, + ST_PYR, COLOR1, EK, EC, P2, P7, N0, + // Case #183: (cloned #95) + ST_TET, COLOR0, EC, ED, EK, P3, + ST_TET, COLOR0, EL, EG, EF, P6, + ST_WDG, COLOR1, P7, P2, P5, EG, EL, EF, + ST_WDG, COLOR1, EK, EC, ED, P7, P2, P0, + ST_PYR, COLOR1, P4, P5, P1, P0, P7, + ST_TET, COLOR1, P2, P0, P1, P7, + ST_TET, COLOR1, P2, P1, P5, P7, + // Case #184: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EI, ED, EC, EG, EF, EJ, + ST_TET, COLOR0, P2, P1, P6, N0, + ST_PYR, COLOR0, P2, P6, EG, EC, N0, + ST_TET, COLOR0, P0, P1, P2, N0, + ST_PYR, COLOR0, P0, P2, EC, ED, N0, + ST_TET, COLOR0, EI, P0, ED, N0, + ST_PYR, COLOR0, P0, EI, EJ, P1, N0, + ST_PYR, COLOR0, EJ, EF, P6, P1, N0, + ST_TET, COLOR0, P6, EF, EG, N0, + ST_TET, COLOR1, P4, P3, P7, N0, + ST_PYR, COLOR1, EI, ED, P3, P4, N0, + ST_PYR, COLOR1, EI, P4, P5, EJ, N0, + ST_TET, COLOR1, P5, P4, P7, N0, + ST_TET, COLOR1, P5, EF, EJ, N0, + ST_PYR, COLOR1, P5, P7, EG, EF, N0, + ST_PYR, COLOR1, EG, P7, P3, EC, N0, + ST_TET, COLOR1, EC, P3, ED, N0, + // Case #185: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EG, EC, EA, EF, EJ, + ST_PYR, COLOR0, P2, EC, EA, P1, N0, + ST_TET, COLOR0, P1, EA, EJ, N0, + ST_PYR, COLOR0, P6, EG, EC, P2, N0, + ST_TET, COLOR0, EF, EG, P6, N0, + ST_PYR, COLOR0, EJ, EF, P6, P1, N0, + ST_TET, COLOR0, P1, P6, P2, N0, + ST_PYR, COLOR1, P4, P0, P3, P7, N0, + ST_TET, COLOR1, P0, P4, P5, N0, + ST_TET, COLOR1, P5, P4, P7, N0, + ST_PYR, COLOR1, P5, P7, EG, EF, N0, + ST_PYR, COLOR1, EG, P7, P3, EC, N0, + ST_PYR, COLOR1, EC, P3, P0, EA, N0, + ST_PYR, COLOR1, EA, P0, P5, EJ, N0, + ST_TET, COLOR1, EF, EJ, P5, N0, + // Case #186: (cloned #61) + ST_PNT, 0, COLOR1, 6, P4, P3, P7, P5, EB, EF, + ST_WDG, COLOR0, EF, P6, EG, EB, P2, EC, + ST_TET, COLOR0, EI, P0, ED, EA, + ST_WDG, COLOR1, P4, P1, P3, EI, EA, ED, + ST_PYR, COLOR1, EF, EB, P1, P5, N0, + ST_TET, COLOR1, P5, P1, P4, N0, + ST_TET, COLOR1, P5, P4, P7, N0, + ST_PYR, COLOR1, EG, EF, P5, P7, N0, + ST_PYR, COLOR1, EC, EG, P7, P3, N0, + ST_TET, COLOR1, P7, P4, P3, N0, + ST_PYR, COLOR1, EB, EF, EG, EC, N0, + ST_TET, COLOR1, P3, P4, P1, N0, + ST_PYR, COLOR1, EB, EC, P3, P1, N0, + // Case #187: (cloned #63) + ST_WDG, COLOR0, P6, EG, EF, P2, EC, EB, + ST_HEX, COLOR1, EC, EB, EF, EG, P3, P1, P5, P7, + ST_WDG, COLOR1, P0, P3, P1, P4, P7, P5, + // Case #188: (cloned #61) + ST_PNT, 0, COLOR1, 6, P7, P5, P4, P3, EB, ED, + ST_WDG, COLOR0, ED, P0, EI, EB, P1, EJ, + ST_TET, COLOR0, EG, P6, EF, EL, + ST_WDG, COLOR1, P7, P2, P5, EG, EL, EF, + ST_PYR, COLOR1, ED, EB, P2, P3, N0, + ST_TET, COLOR1, P3, P2, P7, N0, + ST_TET, COLOR1, P3, P7, P4, N0, + ST_PYR, COLOR1, EI, ED, P3, P4, N0, + ST_PYR, COLOR1, EJ, EI, P4, P5, N0, + ST_TET, COLOR1, P4, P7, P5, N0, + ST_PYR, COLOR1, EB, ED, EI, EJ, N0, + ST_TET, COLOR1, P5, P7, P2, N0, + ST_PYR, COLOR1, EB, EJ, P5, P2, N0, + // Case #189: (cloned #95) + ST_TET, COLOR0, EJ, EA, EB, P1, + ST_TET, COLOR0, EF, EL, EG, P6, + ST_WDG, COLOR1, P2, P5, P7, EL, EF, EG, + ST_WDG, COLOR1, EB, EJ, EA, P2, P5, P0, + ST_PYR, COLOR1, P3, P7, P4, P0, P2, + ST_TET, COLOR1, P5, P0, P4, P2, + ST_TET, COLOR1, P5, P4, P7, P2, + // Case #190: (cloned #125) + ST_TET, COLOR0, EA, EI, ED, P0, + ST_TET, COLOR0, EL, EG, EF, P6, + ST_WDG, COLOR1, ED, EA, EI, P3, P1, P4, + ST_WDG, COLOR1, EL, EG, EF, P2, P7, P5, + ST_TET, COLOR1, P5, P4, P7, P2, + ST_TET, COLOR1, P2, P3, P1, P4, + ST_TET, COLOR1, P3, P2, P7, P4, + ST_TET, COLOR1, P2, P1, P5, P4, + // Case #191: (cloned #127) + ST_PNT, 0, COLOR1, 7, P1, P0, P3, P2, P5, P4, P7, + ST_TET, COLOR0, EF, EL, EG, P6, + ST_WDG, COLOR1, P5, P7, P2, EF, EG, EL, + ST_TET, COLOR1, P5, P7, P2, N0, + ST_PYR, COLOR1, P4, P0, P3, P7, N0, + ST_TET, COLOR1, P7, P3, P2, N0, + ST_PYR, COLOR1, P1, P2, P3, P0, N0, + ST_TET, COLOR1, P1, P5, P2, N0, + ST_PYR, COLOR1, P1, P0, P4, P5, N0, + ST_TET, COLOR1, P5, P4, P7, N0, + // Case #192: (cloned #3) + ST_HEX, COLOR0, EF, P5, P4, EH, EL, P2, P3, EK, + ST_WDG, COLOR0, P5, P1, P2, P4, P0, P3, + ST_WDG, COLOR1, P6, EF, EL, P7, EH, EK, + // Case #193: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EL, EF, EA, EA, + ST_PYR, COLOR0, P1, EA, EI, P4, N0, + ST_TET, COLOR0, EI, EH, P4, N0, + ST_PYR, COLOR0, P5, P4, EH, EF, N0, + ST_TET, COLOR0, P1, P4, P5, N0, + ST_TET, COLOR0, P3, EK, ED, N0, + ST_TET, COLOR0, P2, P3, P1, N0, + ST_PYR, COLOR0, P3, ED, EA, P1, N0, + ST_TET, COLOR0, P2, P1, P5, N0, + ST_PYR, COLOR0, P2, P5, EF, EL, N0, + ST_PYR, COLOR0, P2, EL, EK, P3, N0, + ST_PYR, COLOR1, P0, P7, EH, EI, N0, + ST_PYR, COLOR1, EK, P7, P0, ED, N0, + ST_TET, COLOR1, P0, EI, EA, N0, + ST_TET, COLOR1, ED, P0, EA, N0, + ST_PYR, COLOR1, EH, P7, P6, EF, N0, + ST_PYR, COLOR1, P7, EK, EL, P6, N0, + ST_TET, COLOR1, EL, EF, P6, N0, + // Case #194: (cloned #21) + ST_PNT, 0, NOCOLOR, 4, EK, EH, EA, EA, + ST_PYR, COLOR0, P0, P5, EJ, EA, N0, + ST_TET, COLOR0, EJ, P5, EF, N0, + ST_PYR, COLOR0, P4, EH, EF, P5, N0, + ST_TET, COLOR0, P0, P4, P5, N0, + ST_TET, COLOR0, P2, EB, EL, N0, + ST_TET, COLOR0, P3, P0, P2, N0, + ST_PYR, COLOR0, P2, P0, EA, EB, N0, + ST_TET, COLOR0, P3, P4, P0, N0, + ST_PYR, COLOR0, P3, EK, EH, P4, N0, + ST_PYR, COLOR0, P3, P2, EL, EK, N0, + ST_PYR, COLOR1, P1, EJ, EF, P6, N0, + ST_PYR, COLOR1, EL, EB, P1, P6, N0, + ST_TET, COLOR1, P1, EA, EJ, N0, + ST_TET, COLOR1, EB, EA, P1, N0, + ST_PYR, COLOR1, EF, EH, P7, P6, N0, + ST_PYR, COLOR1, P6, P7, EK, EL, N0, + ST_TET, COLOR1, EK, P7, EH, N0, + // Case #195: (cloned #60) + ST_WDG, COLOR0, P3, ED, EK, P2, EB, EL, + ST_WDG, COLOR0, P4, EH, EI, P5, EF, EJ, + ST_HEX, COLOR1, EI, EH, EF, EJ, P0, P7, P6, P1, + ST_HEX, COLOR1, P0, P7, P6, P1, ED, EK, EL, EB, + // Case #196: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EH, EF, EK, EC, EB, + ST_PYR, COLOR0, P4, P5, P1, P0, N0, + ST_TET, COLOR0, P1, N0, P3, P0, + ST_PYR, COLOR0, P5, EF, EB, P1, N0, + ST_PYR, COLOR0, EH, EF, P5, P4, N0, + ST_TET, COLOR0, P3, P4, P0, N0, + ST_PYR, COLOR0, P3, EK, EH, P4, N0, + ST_TET, COLOR0, P3, EC, EK, N0, + ST_PYR, COLOR0, EB, EC, P3, P1, N0, + ST_PYR, COLOR1, EF, P6, P2, EB, N0, + ST_PYR, COLOR1, EH, P7, P6, EF, N0, + ST_TET, COLOR1, EK, P7, EH, N0, + ST_TET, COLOR1, P7, P2, P6, N0, + ST_PYR, COLOR1, EK, EC, P2, P7, N0, + ST_TET, COLOR1, P2, EC, EB, N0, + // Case #197: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EF, EB, EH, EI, EA, + ST_TET, COLOR0, P3, EC, EK, ED, + ST_PYR, COLOR0, P1, P5, EF, EB, N0, + ST_PYR, COLOR0, P5, P4, EH, EF, N0, + ST_TET, COLOR0, P4, EI, EH, N0, + ST_TET, COLOR0, P5, P1, P4, N0, + ST_PYR, COLOR0, P4, P1, EA, EI, N0, + ST_TET, COLOR0, P1, EB, EA, N0, + ST_WDG, COLOR1, EK, EC, ED, P7, P2, P0, + ST_TET, COLOR1, P2, P7, P0, N0, + ST_PYR, COLOR1, P7, EH, EI, P0, N0, + ST_TET, COLOR1, P6, P7, P2, N0, + ST_PYR, COLOR1, EB, EF, P6, P2, N0, + ST_PYR, COLOR1, EF, EH, P7, P6, N0, + ST_PYR, COLOR1, P0, EA, EB, P2, N0, + ST_TET, COLOR1, EI, EA, P0, N0, + // Case #198: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EC, EK, EH, EF, EJ, EA, + ST_TET, COLOR0, P4, P5, P0, N0, + ST_PYR, COLOR0, P4, EH, EF, P5, N0, + ST_TET, COLOR0, P3, P4, P0, N0, + ST_PYR, COLOR0, P3, EK, EH, P4, N0, + ST_TET, COLOR0, EC, EK, P3, N0, + ST_PYR, COLOR0, P3, P0, EA, EC, N0, + ST_PYR, COLOR0, EA, P0, P5, EJ, N0, + ST_TET, COLOR0, P5, EF, EJ, N0, + ST_TET, COLOR1, P2, P6, P7, N0, + ST_PYR, COLOR1, EC, P2, P7, EK, N0, + ST_PYR, COLOR1, EC, EA, P1, P2, N0, + ST_TET, COLOR1, P1, P6, P2, N0, + ST_TET, COLOR1, P1, EA, EJ, N0, + ST_PYR, COLOR1, P1, EJ, EF, P6, N0, + ST_PYR, COLOR1, EF, EH, P7, P6, N0, + ST_TET, COLOR1, EH, EK, P7, N0, + // Case #199: (cloned #61) + ST_PNT, 0, COLOR1, 6, P2, P0, P1, P6, EH, EF, + ST_WDG, COLOR0, EF, P5, EJ, EH, P4, EI, + ST_TET, COLOR0, EC, P3, ED, EK, + ST_WDG, COLOR1, P2, P7, P0, EC, EK, ED, + ST_PYR, COLOR1, EF, EH, P7, P6, N0, + ST_TET, COLOR1, P6, P7, P2, N0, + ST_TET, COLOR1, P6, P2, P1, N0, + ST_PYR, COLOR1, EJ, EF, P6, P1, N0, + ST_PYR, COLOR1, EI, EJ, P1, P0, N0, + ST_TET, COLOR1, P1, P2, P0, N0, + ST_PYR, COLOR1, EH, EF, EJ, EI, N0, + ST_TET, COLOR1, P0, P2, P7, N0, + ST_PYR, COLOR1, EH, EI, P0, P7, N0, + // Case #200: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, ED, EH, EC, EL, EF, + ST_PYR, COLOR0, P0, P4, P5, P1, N0, + ST_TET, COLOR0, P5, N0, P2, P1, + ST_PYR, COLOR0, P4, EH, EF, P5, N0, + ST_PYR, COLOR0, ED, EH, P4, P0, N0, + ST_TET, COLOR0, P2, P0, P1, N0, + ST_PYR, COLOR0, P2, EC, ED, P0, N0, + ST_TET, COLOR0, P2, EL, EC, N0, + ST_PYR, COLOR0, EF, EL, P2, P5, N0, + ST_PYR, COLOR1, EH, P7, P6, EF, N0, + ST_PYR, COLOR1, ED, P3, P7, EH, N0, + ST_TET, COLOR1, EC, P3, ED, N0, + ST_TET, COLOR1, P3, P6, P7, N0, + ST_PYR, COLOR1, EC, EL, P6, P3, N0, + ST_TET, COLOR1, P6, EL, EF, N0, + // Case #201: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EC, EL, EF, EH, EI, EA, + ST_TET, COLOR0, P5, P1, P4, N0, + ST_PYR, COLOR0, P5, P4, EH, EF, N0, + ST_TET, COLOR0, P2, P1, P5, N0, + ST_PYR, COLOR0, P2, P5, EF, EL, N0, + ST_TET, COLOR0, EC, P2, EL, N0, + ST_PYR, COLOR0, P2, EC, EA, P1, N0, + ST_PYR, COLOR0, EA, EI, P4, P1, N0, + ST_TET, COLOR0, P4, EI, EH, N0, + ST_TET, COLOR1, P3, P6, P7, N0, + ST_PYR, COLOR1, EC, EL, P6, P3, N0, + ST_PYR, COLOR1, EC, P3, P0, EA, N0, + ST_TET, COLOR1, P0, P3, P7, N0, + ST_TET, COLOR1, P0, EI, EA, N0, + ST_PYR, COLOR1, P0, P7, EH, EI, N0, + ST_PYR, COLOR1, EH, P7, P6, EF, N0, + ST_TET, COLOR1, EF, P6, EL, N0, + // Case #202: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EH, EF, ED, EA, EJ, + ST_TET, COLOR0, P2, EL, EC, EB, + ST_PYR, COLOR0, P5, P4, EH, EF, N0, + ST_PYR, COLOR0, P4, P0, ED, EH, N0, + ST_TET, COLOR0, P0, EA, ED, N0, + ST_TET, COLOR0, P4, P5, P0, N0, + ST_PYR, COLOR0, P0, P5, EJ, EA, N0, + ST_TET, COLOR0, P5, EF, EJ, N0, + ST_WDG, COLOR1, EC, EL, EB, P3, P6, P1, + ST_TET, COLOR1, P6, P3, P1, N0, + ST_PYR, COLOR1, P3, ED, EA, P1, N0, + ST_TET, COLOR1, P7, P3, P6, N0, + ST_PYR, COLOR1, EF, EH, P7, P6, N0, + ST_PYR, COLOR1, EH, ED, P3, P7, N0, + ST_PYR, COLOR1, P1, EJ, EF, P6, N0, + ST_TET, COLOR1, EA, EJ, P1, N0, + // Case #203: (cloned #61) + ST_PNT, 0, COLOR1, 6, P3, P1, P0, P7, EF, EH, + ST_WDG, COLOR0, EF, P5, EJ, EH, P4, EI, + ST_TET, COLOR0, EC, EB, P2, EL, + ST_WDG, COLOR1, EC, EL, EB, P3, P6, P1, + ST_PYR, COLOR1, EH, P7, P6, EF, N0, + ST_TET, COLOR1, P7, P3, P6, N0, + ST_TET, COLOR1, P7, P0, P3, N0, + ST_PYR, COLOR1, EI, P0, P7, EH, N0, + ST_PYR, COLOR1, EJ, P1, P0, EI, N0, + ST_TET, COLOR1, P0, P1, P3, N0, + ST_PYR, COLOR1, EF, EJ, EI, EH, N0, + ST_TET, COLOR1, P1, P6, P3, N0, + ST_PYR, COLOR1, EF, P6, P1, EJ, N0, + // Case #204: (cloned #15) + ST_HEX, COLOR0, ED, EB, EF, EH, P0, P1, P5, P4, + ST_HEX, COLOR1, P3, P2, P6, P7, ED, EB, EF, EH, + // Case #205: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EB, EF, EH, EA, EI, + ST_PYR, COLOR0, P5, P4, EH, EF, N0, + ST_TET, COLOR0, P4, EI, EH, N0, + ST_PYR, COLOR0, P1, P5, EF, EB, N0, + ST_TET, COLOR0, EA, P1, EB, N0, + ST_PYR, COLOR0, EI, P4, P1, EA, N0, + ST_TET, COLOR0, P4, P5, P1, N0, + ST_PYR, COLOR1, P3, P2, P6, P7, N0, + ST_TET, COLOR1, P7, P0, P3, N0, + ST_TET, COLOR1, P0, P2, P3, N0, + ST_PYR, COLOR1, P0, EA, EB, P2, N0, + ST_PYR, COLOR1, EB, EF, P6, P2, N0, + ST_PYR, COLOR1, EF, EH, P7, P6, N0, + ST_PYR, COLOR1, EH, EI, P0, P7, N0, + ST_TET, COLOR1, EA, P0, EI, N0, + // Case #206: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EF, EH, ED, EJ, EA, + ST_PYR, COLOR0, P4, P0, ED, EH, N0, + ST_TET, COLOR0, P0, EA, ED, N0, + ST_PYR, COLOR0, P5, P4, EH, EF, N0, + ST_TET, COLOR0, EJ, P5, EF, N0, + ST_PYR, COLOR0, EA, P0, P5, EJ, N0, + ST_TET, COLOR0, P0, P4, P5, N0, + ST_PYR, COLOR1, P2, P6, P7, P3, N0, + ST_TET, COLOR1, P3, P1, P2, N0, + ST_TET, COLOR1, P1, P6, P2, N0, + ST_PYR, COLOR1, P1, EJ, EF, P6, N0, + ST_PYR, COLOR1, EF, EH, P7, P6, N0, + ST_PYR, COLOR1, EH, ED, P3, P7, N0, + ST_PYR, COLOR1, ED, EA, P1, P3, N0, + ST_TET, COLOR1, EJ, P1, EA, N0, + // Case #207: (cloned #63) + ST_WDG, COLOR0, P5, EJ, EF, P4, EI, EH, + ST_HEX, COLOR1, EI, EH, EF, EJ, P0, P7, P6, P1, + ST_WDG, COLOR1, P3, P0, P7, P2, P1, P6, + // Case #208: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EI, EK, EE, EF, EL, + ST_PYR, COLOR0, P0, P1, P2, P3, N0, + ST_TET, COLOR0, P2, P5, N0, P1, + ST_PYR, COLOR0, P3, P2, EL, EK, N0, + ST_PYR, COLOR0, EI, P0, P3, EK, N0, + ST_TET, COLOR0, P5, P1, P0, N0, + ST_PYR, COLOR0, P5, P0, EI, EE, N0, + ST_TET, COLOR0, P5, EE, EF, N0, + ST_PYR, COLOR0, EL, P2, P5, EF, N0, + ST_PYR, COLOR1, EK, EL, P6, P7, N0, + ST_PYR, COLOR1, EI, EK, P7, P4, N0, + ST_TET, COLOR1, EE, EI, P4, N0, + ST_TET, COLOR1, P4, P7, P6, N0, + ST_PYR, COLOR1, EE, P4, P6, EF, N0, + ST_TET, COLOR1, P6, EL, EF, N0, + // Case #209: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EE, EF, EL, EK, ED, EA, + ST_TET, COLOR0, P2, P3, P1, N0, + ST_PYR, COLOR0, P2, EL, EK, P3, N0, + ST_TET, COLOR0, P5, P2, P1, N0, + ST_PYR, COLOR0, P5, EF, EL, P2, N0, + ST_TET, COLOR0, EE, EF, P5, N0, + ST_PYR, COLOR0, P5, P1, EA, EE, N0, + ST_PYR, COLOR0, EA, P1, P3, ED, N0, + ST_TET, COLOR0, P3, EK, ED, N0, + ST_TET, COLOR1, P4, P7, P6, N0, + ST_PYR, COLOR1, EE, P4, P6, EF, N0, + ST_PYR, COLOR1, EE, EA, P0, P4, N0, + ST_TET, COLOR1, P0, P7, P4, N0, + ST_TET, COLOR1, P0, EA, ED, N0, + ST_PYR, COLOR1, P0, ED, EK, P7, N0, + ST_PYR, COLOR1, EK, EL, P6, P7, N0, + ST_TET, COLOR1, EL, EF, P6, N0, + // Case #210: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EK, EL, EI, EA, EB, + ST_TET, COLOR0, P5, EE, EF, EJ, + ST_PYR, COLOR0, P2, EL, EK, P3, N0, + ST_PYR, COLOR0, P3, EK, EI, P0, N0, + ST_TET, COLOR0, P0, EI, EA, N0, + ST_TET, COLOR0, P3, P0, P2, N0, + ST_PYR, COLOR0, P0, EA, EB, P2, N0, + ST_TET, COLOR0, P2, EB, EL, N0, + ST_WDG, COLOR1, P4, P6, P1, EE, EF, EJ, + ST_TET, COLOR1, P6, P1, P4, N0, + ST_PYR, COLOR1, P4, P1, EA, EI, N0, + ST_TET, COLOR1, P7, P6, P4, N0, + ST_PYR, COLOR1, EL, P6, P7, EK, N0, + ST_PYR, COLOR1, EK, P7, P4, EI, N0, + ST_PYR, COLOR1, P1, P6, EL, EB, N0, + ST_TET, COLOR1, EA, P1, EB, N0, + // Case #211: (cloned #61) + ST_PNT, 0, COLOR1, 6, P4, P1, P0, P7, EL, EK, + ST_WDG, COLOR0, EK, P3, ED, EL, P2, EB, + ST_TET, COLOR0, EE, P5, EJ, EF, + ST_WDG, COLOR1, P4, P6, P1, EE, EF, EJ, + ST_PYR, COLOR1, EK, EL, P6, P7, N0, + ST_TET, COLOR1, P7, P6, P4, N0, + ST_TET, COLOR1, P7, P4, P0, N0, + ST_PYR, COLOR1, ED, EK, P7, P0, N0, + ST_PYR, COLOR1, EB, ED, P0, P1, N0, + ST_TET, COLOR1, P0, P4, P1, N0, + ST_PYR, COLOR1, EL, EK, ED, EB, N0, + ST_TET, COLOR1, P1, P4, P6, N0, + ST_PYR, COLOR1, EL, EB, P1, P6, N0, + // Case #212: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EK, EC, EB, EF, EE, EI, + ST_TET, COLOR0, P1, P0, P5, N0, + ST_PYR, COLOR0, P1, P5, EF, EB, N0, + ST_TET, COLOR0, P3, P0, P1, N0, + ST_PYR, COLOR0, P3, P1, EB, EC, N0, + ST_TET, COLOR0, EK, P3, EC, N0, + ST_PYR, COLOR0, P3, EK, EI, P0, N0, + ST_PYR, COLOR0, EI, EE, P5, P0, N0, + ST_TET, COLOR0, P5, EE, EF, N0, + ST_TET, COLOR1, P7, P2, P6, N0, + ST_PYR, COLOR1, EK, EC, P2, P7, N0, + ST_PYR, COLOR1, EK, P7, P4, EI, N0, + ST_TET, COLOR1, P4, P7, P6, N0, + ST_TET, COLOR1, P4, EE, EI, N0, + ST_PYR, COLOR1, P4, P6, EF, EE, N0, + ST_PYR, COLOR1, EF, P6, P2, EB, N0, + ST_TET, COLOR1, EB, P2, EC, N0, + // Case #213: (cloned #61) + ST_PNT, 0, COLOR1, 6, P7, P0, P4, P6, EB, EF, + ST_WDG, COLOR0, EB, P1, EA, EF, P5, EE, + ST_TET, COLOR0, EK, ED, P3, EC, + ST_WDG, COLOR1, EK, EC, ED, P7, P2, P0, + ST_PYR, COLOR1, EF, P6, P2, EB, N0, + ST_TET, COLOR1, P6, P7, P2, N0, + ST_TET, COLOR1, P6, P4, P7, N0, + ST_PYR, COLOR1, EE, P4, P6, EF, N0, + ST_PYR, COLOR1, EA, P0, P4, EE, N0, + ST_TET, COLOR1, P4, P0, P7, N0, + ST_PYR, COLOR1, EB, EA, EE, EF, N0, + ST_TET, COLOR1, P0, P2, P7, N0, + ST_PYR, COLOR1, EB, P2, P0, EA, N0, + // Case #214: (cloned #61) + ST_PNT, 0, COLOR1, 6, P6, P1, P2, P7, EI, EK, + ST_WDG, COLOR0, EI, P0, EA, EK, P3, EC, + ST_TET, COLOR0, EF, EJ, P5, EE, + ST_WDG, COLOR1, EF, EE, EJ, P6, P4, P1, + ST_PYR, COLOR1, EK, P7, P4, EI, N0, + ST_TET, COLOR1, P7, P6, P4, N0, + ST_TET, COLOR1, P7, P2, P6, N0, + ST_PYR, COLOR1, EC, P2, P7, EK, N0, + ST_PYR, COLOR1, EA, P1, P2, EC, N0, + ST_TET, COLOR1, P2, P1, P6, N0, + ST_PYR, COLOR1, EI, EA, EC, EK, N0, + ST_TET, COLOR1, P1, P4, P6, N0, + ST_PYR, COLOR1, EI, P4, P1, EA, N0, + // Case #215: (cloned #125) + ST_TET, COLOR0, EF, EE, EJ, P5, + ST_TET, COLOR0, EC, ED, EK, P3, + ST_WDG, COLOR1, EJ, EF, EE, P1, P6, P4, + ST_WDG, COLOR1, EC, ED, EK, P2, P0, P7, + ST_TET, COLOR1, P7, P4, P0, P2, + ST_TET, COLOR1, P2, P1, P6, P4, + ST_TET, COLOR1, P1, P2, P0, P4, + ST_TET, COLOR1, P2, P6, P7, P4, + // Case #216: (cloned #27) + ST_TET, COLOR0, P2, P0, P1, P5, + ST_PYR, COLOR0, EE, P5, P0, EI, ED, + ST_PYR, COLOR0, EF, EL, P2, P5, EC, + ST_PYR, COLOR0, P0, P2, EC, ED, P5, + ST_PYR, COLOR0, ED, EC, EF, EE, P5, + ST_TET, COLOR1, P7, P6, P4, P3, + ST_PYR, COLOR1, ED, P3, P4, EI, EE, + ST_PYR, COLOR1, EC, EL, P6, P3, EF, + ST_PYR, COLOR1, P4, P6, EF, EE, P3, + ST_PYR, COLOR1, ED, EE, EF, EC, P3, + // Case #217: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EC, EA, EE, EL, EF, + ST_PYR, COLOR0, P1, EA, EE, P5, N0, + ST_TET, COLOR0, P5, EE, EF, N0, + ST_PYR, COLOR0, P2, EC, EA, P1, N0, + ST_TET, COLOR0, EL, EC, P2, N0, + ST_PYR, COLOR0, EF, EL, P2, P5, N0, + ST_TET, COLOR0, P5, P2, P1, N0, + ST_PYR, COLOR1, P7, P4, P0, P3, N0, + ST_TET, COLOR1, P4, P7, P6, N0, + ST_TET, COLOR1, P6, P7, P3, N0, + ST_PYR, COLOR1, P6, P3, EC, EL, N0, + ST_PYR, COLOR1, EC, P3, P0, EA, N0, + ST_PYR, COLOR1, EA, P0, P4, EE, N0, + ST_PYR, COLOR1, EE, P4, P6, EF, N0, + ST_TET, COLOR1, EL, EF, P6, N0, + // Case #218: (cloned #91) + ST_TET, COLOR0, EC, EL, EB, P2, + ST_TET, COLOR0, ED, EI, P0, EA, + ST_TET, COLOR0, EF, P5, EE, EJ, + ST_WDG, COLOR1, P1, P4, P6, EJ, EE, EF, + ST_WDG, COLOR1, ED, EA, EI, P3, P1, P4, + ST_WDG, COLOR1, P3, P1, P6, EC, EB, EL, + ST_TET, COLOR1, P1, P4, P6, P3, + ST_TET, COLOR1, P3, P6, P7, P4, + // Case #219: (cloned #95) + ST_TET, COLOR0, EL, EB, EC, P2, + ST_TET, COLOR0, EF, EE, EJ, P5, + ST_WDG, COLOR1, EJ, EF, EE, P1, P6, P4, + ST_WDG, COLOR1, P1, P6, P3, EB, EL, EC, + ST_PYR, COLOR1, P0, P3, P7, P4, P1, + ST_TET, COLOR1, P6, P7, P3, P1, + ST_TET, COLOR1, P6, P4, P7, P1, + // Case #220: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EF, EB, ED, EE, EI, + ST_PYR, COLOR0, P1, EB, ED, P0, N0, + ST_TET, COLOR0, P0, ED, EI, N0, + ST_PYR, COLOR0, P5, EF, EB, P1, N0, + ST_TET, COLOR0, EE, EF, P5, N0, + ST_PYR, COLOR0, EI, EE, P5, P0, N0, + ST_TET, COLOR0, P0, P5, P1, N0, + ST_PYR, COLOR1, P7, P3, P2, P6, N0, + ST_TET, COLOR1, P3, P7, P4, N0, + ST_TET, COLOR1, P4, P7, P6, N0, + ST_PYR, COLOR1, P4, P6, EF, EE, N0, + ST_PYR, COLOR1, EF, P6, P2, EB, N0, + ST_PYR, COLOR1, EB, P2, P3, ED, N0, + ST_PYR, COLOR1, ED, P3, P4, EI, N0, + ST_TET, COLOR1, EE, EI, P4, N0, + // Case #221: (cloned #63) + ST_WDG, COLOR0, P1, EA, EB, P5, EE, EF, + ST_HEX, COLOR1, P0, P2, P6, P4, EA, EB, EF, EE, + ST_WDG, COLOR1, P7, P4, P6, P3, P0, P2, + // Case #222: (cloned #95) + ST_TET, COLOR0, EA, EI, ED, P0, + ST_TET, COLOR0, EJ, EF, EE, P5, + ST_WDG, COLOR1, EE, EJ, EF, P4, P1, P6, + ST_WDG, COLOR1, P4, P1, P3, EI, EA, ED, + ST_PYR, COLOR1, P7, P3, P2, P6, P4, + ST_TET, COLOR1, P1, P2, P3, P4, + ST_TET, COLOR1, P1, P6, P2, P4, + // Case #223: (cloned #127) + ST_PNT, 0, COLOR1, 7, P0, P3, P7, P4, P1, P2, P6, + ST_TET, COLOR0, EJ, EF, EE, P5, + ST_WDG, COLOR1, EJ, EF, EE, P1, P6, P4, + ST_TET, COLOR1, P1, P4, P6, N0, + ST_PYR, COLOR1, P2, P6, P7, P3, N0, + ST_TET, COLOR1, P6, P4, P7, N0, + ST_PYR, COLOR1, P0, P3, P7, P4, N0, + ST_TET, COLOR1, P0, P4, P1, N0, + ST_PYR, COLOR1, P0, P1, P2, P3, N0, + ST_TET, COLOR1, P1, P6, P2, N0, + // Case #224: (cloned #7) + ST_PNT, 0, NOCOLOR, 5, EK, EL, EH, EE, EJ, + ST_PYR, COLOR0, P3, P0, P1, P2, N0, + ST_TET, COLOR0, P1, P4, N0, P0, + ST_PYR, COLOR0, P2, P1, EJ, EL, N0, + ST_PYR, COLOR0, EK, P3, P2, EL, N0, + ST_TET, COLOR0, P4, P0, P3, N0, + ST_PYR, COLOR0, P4, P3, EK, EH, N0, + ST_TET, COLOR0, P4, EH, EE, N0, + ST_PYR, COLOR0, EJ, P1, P4, EE, N0, + ST_PYR, COLOR1, EL, EJ, P5, P6, N0, + ST_PYR, COLOR1, EK, EL, P6, P7, N0, + ST_TET, COLOR1, EH, EK, P7, N0, + ST_TET, COLOR1, P7, P6, P5, N0, + ST_PYR, COLOR1, EH, P7, P5, EE, N0, + ST_TET, COLOR1, P5, EJ, EE, N0, + // Case #225: (cloned #30) + ST_PNT, 0, NOCOLOR, 5, EL, EJ, EK, ED, EA, + ST_TET, COLOR0, P4, EH, EE, EI, + ST_PYR, COLOR0, P1, EJ, EL, P2, N0, + ST_PYR, COLOR0, P2, EL, EK, P3, N0, + ST_TET, COLOR0, P3, EK, ED, N0, + ST_TET, COLOR0, P2, P3, P1, N0, + ST_PYR, COLOR0, P3, ED, EA, P1, N0, + ST_TET, COLOR0, P1, EA, EJ, N0, + ST_WDG, COLOR1, P7, P5, P0, EH, EE, EI, + ST_TET, COLOR1, P5, P0, P7, N0, + ST_PYR, COLOR1, P7, P0, ED, EK, N0, + ST_TET, COLOR1, P6, P5, P7, N0, + ST_PYR, COLOR1, EJ, P5, P6, EL, N0, + ST_PYR, COLOR1, EL, P6, P7, EK, N0, + ST_PYR, COLOR1, P0, P5, EJ, EA, N0, + ST_TET, COLOR1, ED, P0, EA, N0, + // Case #226: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EE, EH, EK, EL, EB, EA, + ST_TET, COLOR0, P3, P0, P2, N0, + ST_PYR, COLOR0, P3, P2, EL, EK, N0, + ST_TET, COLOR0, P4, P0, P3, N0, + ST_PYR, COLOR0, P4, P3, EK, EH, N0, + ST_TET, COLOR0, EE, P4, EH, N0, + ST_PYR, COLOR0, P4, EE, EA, P0, N0, + ST_PYR, COLOR0, EA, EB, P2, P0, N0, + ST_TET, COLOR0, P2, EB, EL, N0, + ST_TET, COLOR1, P5, P7, P6, N0, + ST_PYR, COLOR1, EE, EH, P7, P5, N0, + ST_PYR, COLOR1, EE, P5, P1, EA, N0, + ST_TET, COLOR1, P1, P5, P6, N0, + ST_TET, COLOR1, P1, EB, EA, N0, + ST_PYR, COLOR1, P1, P6, EL, EB, N0, + ST_PYR, COLOR1, EL, P6, P7, EK, N0, + ST_TET, COLOR1, EK, P7, EH, N0, + // Case #227: (cloned #61) + ST_PNT, 0, COLOR1, 6, P5, P0, P1, P6, EK, EL, + ST_WDG, COLOR0, EK, P3, ED, EL, P2, EB, + ST_TET, COLOR0, EE, EI, P4, EH, + ST_WDG, COLOR1, EE, EH, EI, P5, P7, P0, + ST_PYR, COLOR1, EL, P6, P7, EK, N0, + ST_TET, COLOR1, P6, P5, P7, N0, + ST_TET, COLOR1, P6, P1, P5, N0, + ST_PYR, COLOR1, EB, P1, P6, EL, N0, + ST_PYR, COLOR1, ED, P0, P1, EB, N0, + ST_TET, COLOR1, P1, P0, P5, N0, + ST_PYR, COLOR1, EK, ED, EB, EL, N0, + ST_TET, COLOR1, P0, P7, P5, N0, + ST_PYR, COLOR1, EK, P7, P0, ED, N0, + // Case #228: (cloned #27) + ST_TET, COLOR0, P3, P0, P1, P4, + ST_PYR, COLOR0, EE, EJ, P1, P4, EB, + ST_PYR, COLOR0, EH, P4, P3, EK, EC, + ST_PYR, COLOR0, P1, EB, EC, P3, P4, + ST_PYR, COLOR0, EB, EE, EH, EC, P4, + ST_TET, COLOR1, P6, P5, P7, P2, + ST_PYR, COLOR1, EB, EJ, P5, P2, EE, + ST_PYR, COLOR1, EC, P2, P7, EK, EH, + ST_PYR, COLOR1, P5, EE, EH, P7, P2, + ST_PYR, COLOR1, EB, EC, EH, EE, P2, + // Case #229: (cloned #91) + ST_TET, COLOR0, EC, ED, EK, P3, + ST_TET, COLOR0, EB, P1, EJ, EA, + ST_TET, COLOR0, EH, EE, P4, EI, + ST_WDG, COLOR1, EI, EE, EH, P0, P5, P7, + ST_WDG, COLOR1, P2, P0, P5, EB, EA, EJ, + ST_WDG, COLOR1, EC, ED, EK, P2, P0, P7, + ST_TET, COLOR1, P0, P7, P5, P2, + ST_TET, COLOR1, P2, P6, P7, P5, + // Case #230: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EC, EA, EE, EK, EH, + ST_PYR, COLOR0, P0, P4, EE, EA, N0, + ST_TET, COLOR0, P4, EH, EE, N0, + ST_PYR, COLOR0, P3, P0, EA, EC, N0, + ST_TET, COLOR0, EK, P3, EC, N0, + ST_PYR, COLOR0, EH, P4, P3, EK, N0, + ST_TET, COLOR0, P4, P0, P3, N0, + ST_PYR, COLOR1, P6, P2, P1, P5, N0, + ST_TET, COLOR1, P5, P7, P6, N0, + ST_TET, COLOR1, P7, P2, P6, N0, + ST_PYR, COLOR1, P7, EK, EC, P2, N0, + ST_PYR, COLOR1, EC, EA, P1, P2, N0, + ST_PYR, COLOR1, EA, EE, P5, P1, N0, + ST_PYR, COLOR1, EE, EH, P7, P5, N0, + ST_TET, COLOR1, EK, P7, EH, N0, + // Case #231: (cloned #95) + ST_TET, COLOR0, EK, EC, ED, P3, + ST_TET, COLOR0, EH, EI, EE, P4, + ST_WDG, COLOR1, P0, P7, P5, EI, EH, EE, + ST_WDG, COLOR1, ED, EK, EC, P0, P7, P2, + ST_PYR, COLOR1, P1, P5, P6, P2, P0, + ST_TET, COLOR1, P7, P2, P6, P0, + ST_TET, COLOR1, P7, P6, P5, P0, + // Case #232: (cloned #23) + ST_PNT, 0, NOCOLOR, 6, EH, EE, EJ, EL, EC, ED, + ST_TET, COLOR0, P1, P2, P0, N0, + ST_PYR, COLOR0, P1, EJ, EL, P2, N0, + ST_TET, COLOR0, P4, P1, P0, N0, + ST_PYR, COLOR0, P4, EE, EJ, P1, N0, + ST_TET, COLOR0, EH, EE, P4, N0, + ST_PYR, COLOR0, P4, P0, ED, EH, N0, + ST_PYR, COLOR0, ED, P0, P2, EC, N0, + ST_TET, COLOR0, P2, EL, EC, N0, + ST_TET, COLOR1, P7, P6, P5, N0, + ST_PYR, COLOR1, EH, P7, P5, EE, N0, + ST_PYR, COLOR1, EH, ED, P3, P7, N0, + ST_TET, COLOR1, P3, P6, P7, N0, + ST_TET, COLOR1, P3, ED, EC, N0, + ST_PYR, COLOR1, P3, EC, EL, P6, N0, + ST_PYR, COLOR1, EL, EJ, P5, P6, N0, + ST_TET, COLOR1, EJ, EE, P5, N0, + // Case #233: (cloned #61) + ST_PNT, 0, COLOR1, 6, P7, P0, P3, P6, EJ, EL, + ST_WDG, COLOR0, EL, P2, EC, EJ, P1, EA, + ST_TET, COLOR0, EH, P4, EI, EE, + ST_WDG, COLOR1, P7, P5, P0, EH, EE, EI, + ST_PYR, COLOR1, EL, EJ, P5, P6, N0, + ST_TET, COLOR1, P6, P5, P7, N0, + ST_TET, COLOR1, P6, P7, P3, N0, + ST_PYR, COLOR1, EC, EL, P6, P3, N0, + ST_PYR, COLOR1, EA, EC, P3, P0, N0, + ST_TET, COLOR1, P3, P7, P0, N0, + ST_PYR, COLOR1, EJ, EL, EC, EA, N0, + ST_TET, COLOR1, P0, P7, P5, N0, + ST_PYR, COLOR1, EJ, EA, P0, P5, N0, + // Case #234: (cloned #61) + ST_PNT, 0, COLOR1, 6, P6, P1, P5, P7, ED, EH, + ST_WDG, COLOR0, EH, P4, EE, ED, P0, EA, + ST_TET, COLOR0, EL, P2, EB, EC, + ST_WDG, COLOR1, P6, P3, P1, EL, EC, EB, + ST_PYR, COLOR1, EH, ED, P3, P7, N0, + ST_TET, COLOR1, P7, P3, P6, N0, + ST_TET, COLOR1, P7, P6, P5, N0, + ST_PYR, COLOR1, EE, EH, P7, P5, N0, + ST_PYR, COLOR1, EA, EE, P5, P1, N0, + ST_TET, COLOR1, P5, P6, P1, N0, + ST_PYR, COLOR1, ED, EH, EE, EA, N0, + ST_TET, COLOR1, P1, P6, P3, N0, + ST_PYR, COLOR1, ED, EA, P1, P3, N0, + // Case #235: (cloned #125) + ST_TET, COLOR0, EL, EB, EC, P2, + ST_TET, COLOR0, EE, EH, EI, P4, + ST_WDG, COLOR1, P1, P6, P3, EB, EL, EC, + ST_WDG, COLOR1, P5, P0, P7, EE, EI, EH, + ST_TET, COLOR1, P7, P0, P3, P5, + ST_TET, COLOR1, P5, P6, P1, P3, + ST_TET, COLOR1, P1, P0, P5, P3, + ST_TET, COLOR1, P5, P7, P6, P3, + // Case #236: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EH, ED, EB, EE, EJ, + ST_PYR, COLOR0, P0, P1, EB, ED, N0, + ST_TET, COLOR0, P1, EJ, EB, N0, + ST_PYR, COLOR0, P4, P0, ED, EH, N0, + ST_TET, COLOR0, EE, P4, EH, N0, + ST_PYR, COLOR0, EJ, P1, P4, EE, N0, + ST_TET, COLOR0, P1, P0, P4, N0, + ST_PYR, COLOR1, P6, P7, P3, P2, N0, + ST_TET, COLOR1, P2, P5, P6, N0, + ST_TET, COLOR1, P5, P7, P6, N0, + ST_PYR, COLOR1, P5, EE, EH, P7, N0, + ST_PYR, COLOR1, EH, ED, P3, P7, N0, + ST_PYR, COLOR1, ED, EB, P2, P3, N0, + ST_PYR, COLOR1, EB, EJ, P5, P2, N0, + ST_TET, COLOR1, EE, P5, EJ, N0, + // Case #237: (cloned #95) + ST_TET, COLOR0, EE, EH, EI, P4, + ST_TET, COLOR0, EJ, EA, EB, P1, + ST_WDG, COLOR1, P0, P5, P2, EA, EJ, EB, + ST_WDG, COLOR1, EI, EE, EH, P0, P5, P7, + ST_PYR, COLOR1, P3, P2, P6, P7, P0, + ST_TET, COLOR1, P5, P7, P6, P0, + ST_TET, COLOR1, P5, P6, P2, P0, + // Case #238: (cloned #63) + ST_WDG, COLOR0, P4, EE, EH, P0, EA, ED, + ST_HEX, COLOR1, EA, ED, EH, EE, P1, P3, P7, P5, + ST_WDG, COLOR1, P2, P1, P3, P6, P5, P7, + // Case #239: (cloned #127) + ST_PNT, 0, COLOR1, 7, P3, P2, P1, P0, P7, P6, P5, + ST_TET, COLOR0, EH, EI, EE, P4, + ST_WDG, COLOR1, P7, P5, P0, EH, EE, EI, + ST_TET, COLOR1, P7, P5, P0, N0, + ST_PYR, COLOR1, P6, P2, P1, P5, N0, + ST_TET, COLOR1, P5, P1, P0, N0, + ST_PYR, COLOR1, P3, P0, P1, P2, N0, + ST_TET, COLOR1, P3, P7, P0, N0, + ST_PYR, COLOR1, P3, P2, P6, P7, N0, + ST_TET, COLOR1, P7, P6, P5, N0, + // Case #240: (cloned #15) + ST_HEX, COLOR0, P0, P1, P2, P3, EI, EJ, EL, EK, + ST_HEX, COLOR1, EI, EJ, EL, EK, P4, P5, P6, P7, + // Case #241: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EJ, EL, EK, EA, ED, + ST_PYR, COLOR0, P2, EL, EK, P3, N0, + ST_TET, COLOR0, P3, EK, ED, N0, + ST_PYR, COLOR0, P1, EJ, EL, P2, N0, + ST_TET, COLOR0, EA, EJ, P1, N0, + ST_PYR, COLOR0, ED, EA, P1, P3, N0, + ST_TET, COLOR0, P3, P1, P2, N0, + ST_PYR, COLOR1, P4, P7, P6, P5, N0, + ST_TET, COLOR1, P7, P4, P0, N0, + ST_TET, COLOR1, P0, P4, P5, N0, + ST_PYR, COLOR1, P0, P5, EJ, EA, N0, + ST_PYR, COLOR1, EJ, P5, P6, EL, N0, + ST_PYR, COLOR1, EL, P6, P7, EK, N0, + ST_PYR, COLOR1, EK, P7, P0, ED, N0, + ST_TET, COLOR1, EA, ED, P0, N0, + // Case #242: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EL, EK, EI, EB, EA, + ST_PYR, COLOR0, P3, EK, EI, P0, N0, + ST_TET, COLOR0, P0, EI, EA, N0, + ST_PYR, COLOR0, P2, EL, EK, P3, N0, + ST_TET, COLOR0, EB, EL, P2, N0, + ST_PYR, COLOR0, EA, EB, P2, P0, N0, + ST_TET, COLOR0, P0, P2, P3, N0, + ST_PYR, COLOR1, P5, P4, P7, P6, N0, + ST_TET, COLOR1, P4, P5, P1, N0, + ST_TET, COLOR1, P1, P5, P6, N0, + ST_PYR, COLOR1, P1, P6, EL, EB, N0, + ST_PYR, COLOR1, EL, P6, P7, EK, N0, + ST_PYR, COLOR1, EK, P7, P4, EI, N0, + ST_PYR, COLOR1, EI, P4, P1, EA, N0, + ST_TET, COLOR1, EB, EA, P1, N0, + // Case #243: (cloned #63) + ST_WDG, COLOR0, P3, ED, EK, P2, EB, EL, + ST_HEX, COLOR1, P0, P7, P6, P1, ED, EK, EL, EB, + ST_WDG, COLOR1, P5, P1, P6, P4, P0, P7, + // Case #244: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EK, EI, EJ, EC, EB, + ST_PYR, COLOR0, P0, EI, EJ, P1, N0, + ST_TET, COLOR0, P1, EJ, EB, N0, + ST_PYR, COLOR0, P3, EK, EI, P0, N0, + ST_TET, COLOR0, EC, EK, P3, N0, + ST_PYR, COLOR0, EB, EC, P3, P1, N0, + ST_TET, COLOR0, P1, P3, P0, N0, + ST_PYR, COLOR1, P6, P5, P4, P7, N0, + ST_TET, COLOR1, P5, P6, P2, N0, + ST_TET, COLOR1, P2, P6, P7, N0, + ST_PYR, COLOR1, P2, P7, EK, EC, N0, + ST_PYR, COLOR1, EK, P7, P4, EI, N0, + ST_PYR, COLOR1, EI, P4, P5, EJ, N0, + ST_PYR, COLOR1, EJ, P5, P2, EB, N0, + ST_TET, COLOR1, EC, EB, P2, N0, + // Case #245: (cloned #95) + ST_TET, COLOR0, EC, ED, EK, P3, + ST_TET, COLOR0, EB, EJ, EA, P1, + ST_WDG, COLOR1, EA, EB, EJ, P0, P2, P5, + ST_WDG, COLOR1, P0, P2, P7, ED, EC, EK, + ST_PYR, COLOR1, P4, P7, P6, P5, P0, + ST_TET, COLOR1, P2, P6, P7, P0, + ST_TET, COLOR1, P2, P5, P6, P0, + // Case #246: (cloned #63) + ST_WDG, COLOR0, P0, EA, EI, P3, EC, EK, + ST_HEX, COLOR1, P1, P4, P7, P2, EA, EI, EK, EC, + ST_WDG, COLOR1, P6, P2, P7, P5, P1, P4, + // Case #247: (cloned #127) + ST_PNT, 0, COLOR1, 7, P4, P5, P1, P0, P7, P6, P2, + ST_TET, COLOR0, EK, EC, ED, P3, + ST_WDG, COLOR1, EK, EC, ED, P7, P2, P0, + ST_TET, COLOR1, P7, P0, P2, N0, + ST_PYR, COLOR1, P6, P2, P1, P5, N0, + ST_TET, COLOR1, P2, P0, P1, N0, + ST_PYR, COLOR1, P4, P5, P1, P0, N0, + ST_TET, COLOR1, P4, P0, P7, N0, + ST_PYR, COLOR1, P4, P7, P6, P5, N0, + ST_TET, COLOR1, P7, P2, P6, N0, + // Case #248: (cloned #31) + ST_PNT, 0, NOCOLOR, 5, EL, EJ, EI, EC, ED, + ST_PYR, COLOR0, P1, P0, EI, EJ, N0, + ST_TET, COLOR0, P0, ED, EI, N0, + ST_PYR, COLOR0, P2, P1, EJ, EL, N0, + ST_TET, COLOR0, EC, P2, EL, N0, + ST_PYR, COLOR0, ED, P0, P2, EC, N0, + ST_TET, COLOR0, P0, P1, P2, N0, + ST_PYR, COLOR1, P7, P6, P5, P4, N0, + ST_TET, COLOR1, P4, P3, P7, N0, + ST_TET, COLOR1, P3, P6, P7, N0, + ST_PYR, COLOR1, P3, EC, EL, P6, N0, + ST_PYR, COLOR1, EL, EJ, P5, P6, N0, + ST_PYR, COLOR1, EJ, EI, P4, P5, N0, + ST_PYR, COLOR1, EI, ED, P3, P4, N0, + ST_TET, COLOR1, EC, P3, ED, N0, + // Case #249: (cloned #63) + ST_WDG, COLOR0, P2, EC, EL, P1, EA, EJ, + ST_HEX, COLOR1, EA, EJ, EL, EC, P0, P5, P6, P3, + ST_WDG, COLOR1, P4, P0, P5, P7, P3, P6, + // Case #250: (cloned #95) + ST_TET, COLOR0, EA, EI, ED, P0, + ST_TET, COLOR0, EB, EC, EL, P2, + ST_WDG, COLOR1, P3, P1, P6, EC, EB, EL, + ST_WDG, COLOR1, ED, EA, EI, P3, P1, P4, + ST_PYR, COLOR1, P7, P6, P5, P4, P3, + ST_TET, COLOR1, P1, P4, P5, P3, + ST_TET, COLOR1, P1, P5, P6, P3, + // Case #251: (cloned #127) + ST_PNT, 0, COLOR1, 7, P0, P4, P7, P3, P1, P5, P6, + ST_TET, COLOR0, EB, EC, EL, P2, + ST_WDG, COLOR1, P1, P6, P3, EB, EL, EC, + ST_TET, COLOR1, P1, P6, P3, N0, + ST_PYR, COLOR1, P5, P4, P7, P6, N0, + ST_TET, COLOR1, P6, P7, P3, N0, + ST_PYR, COLOR1, P0, P3, P7, P4, N0, + ST_TET, COLOR1, P0, P1, P3, N0, + ST_PYR, COLOR1, P0, P4, P5, P1, N0, + ST_TET, COLOR1, P1, P5, P6, N0, + // Case #252: (cloned #63) + ST_WDG, COLOR0, P0, EI, ED, P1, EJ, EB, + ST_HEX, COLOR1, P4, P3, P2, P5, EI, ED, EB, EJ, + ST_WDG, COLOR1, P6, P5, P2, P7, P4, P3, + // Case #253: (cloned #127) + ST_PNT, 0, COLOR1, 7, P3, P7, P4, P0, P2, P6, P5, + ST_TET, COLOR0, EB, EJ, EA, P1, + ST_WDG, COLOR1, EB, EJ, EA, P2, P5, P0, + ST_TET, COLOR1, P2, P0, P5, N0, + ST_PYR, COLOR1, P6, P5, P4, P7, N0, + ST_TET, COLOR1, P5, P0, P4, N0, + ST_PYR, COLOR1, P3, P7, P4, P0, N0, + ST_TET, COLOR1, P3, P0, P2, N0, + ST_PYR, COLOR1, P3, P2, P6, P7, N0, + ST_TET, COLOR1, P2, P5, P6, N0, + // Case #254: (cloned #127) + ST_PNT, 0, COLOR1, 7, P7, P6, P5, P4, P3, P2, P1, + ST_TET, COLOR0, ED, EA, EI, P0, + ST_WDG, COLOR1, ED, EA, EI, P3, P1, P4, + ST_TET, COLOR1, P3, P4, P1, N0, + ST_PYR, COLOR1, P2, P1, P5, P6, N0, + ST_TET, COLOR1, P1, P4, P5, N0, + ST_PYR, COLOR1, P7, P6, P5, P4, N0, + ST_TET, COLOR1, P7, P4, P3, N0, + ST_PYR, COLOR1, P7, P3, P2, P6, N0, + ST_TET, COLOR1, P3, P1, P2, N0, + // Case #255: Unique case #22 + ST_HEX, COLOR1, P0, P1, P2, P3, P4, P5, P6, P7, + // Dummy + 0 +}; + +//--------------------------------------------------------------------------- +// Axom modifications +const size_t clipShapesHexSize = sizeof(clipShapesHex) / sizeof(unsigned char); +} // namespace visit +} // namespace clipping +} // namespace mir +} // namespace axom +// clang-format on +//--------------------------------------------------------------------------- diff --git a/src/axom/mir/clipping/ClipCasesPyr.cpp b/src/axom/mir/clipping/ClipCasesPyr.cpp new file mode 100644 index 0000000000..158990badc --- /dev/null +++ b/src/axom/mir/clipping/ClipCasesPyr.cpp @@ -0,0 +1,229 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt +// Project developers. See the top-level LICENSE file for dates and other +// details. No copyright assignment is required to contribute to VisIt. + +#include "ClipCases.h" +//--------------------------------------------------------------------------- +// Axom modifications +// clang-format off +namespace axom { +namespace mir { +namespace clipping { +namespace visit { +//--------------------------------------------------------------------------- + +// Programmer: Jeremy Meredith +// Date : August 11, 2003 +// +// Modifications: +// Jeremy Meredith, Mon Sep 15 17:30:21 PDT 2003 +// Added ability for Centroid-Points to have an associated color. + +// This file is meant to be read and created by a program other than a +// compiler. If you must modify it by hand, at least be nice to the +// parser and don't add anything else to this file or rearrange it. + +int numClipCasesPyr = 32; + +int numClipShapesPyr[32] = { + 1, 3, 3, 8, 3, 4, 8, 4, // cases 0 - 7 + 3, 8, 4, 4, 8, 4, 4, 2, // cases 8 - 15 + 2, 4, 4, 8, 4, 4, 8, 3, // cases 16 - 23 + 4, 8, 4, 3, 8, 3, 3, 1 // cases 24 - 31 +}; + +int startClipShapesPyr[32] = { + 0, 7, 27, 47, 106, 126, 158, 217, // cases 0 - 7 + 247, 267, 326, 358, 388, 447, 477, 507, // cases 8 - 15 + 524, 541, 571, 601, 660, 690, 718, 777, // cases 16 - 23 + 797, 827, 886, 914, 934, 993, 1013, 1033 // cases 24 - 31 +}; + +unsigned char clipShapesPyr[] = { + // Case #0: Unique case #1 + ST_PYR, COLOR0, P0, P1, P2, P3, P4, + // Case #1: Unique case #2 + ST_WDG, COLOR0, EA, EE, ED, P1, P4, P3, + ST_TET, COLOR0, P1, P2, P3, P4, + ST_TET, COLOR1, P0, EA, ED, EE, + // Case #2: (cloned #1) + ST_WDG, COLOR0, EB, EF, EA, P2, P4, P0, + ST_TET, COLOR0, P2, P3, P0, P4, + ST_TET, COLOR1, P1, EB, EA, EF, + // Case #3: Unique case #3 + ST_PNT, 0, COLOR0, 7, P4, EF, EE, EB, ED, P2, P3, + ST_TET, COLOR0, EE, P4, EF, N0, + ST_PYR, COLOR0, EB, ED, EE, EF, N0, + ST_PYR, COLOR0, EB, EF, P4, P2, N0, + ST_TET, COLOR0, P2, P4, P3, N0, + ST_PYR, COLOR0, P3, P4, EE, ED, N0, + ST_PYR, COLOR0, P2, P3, ED, EB, N0, + ST_WDG, COLOR1, EB, EF, P1, ED, EE, P0, + // Case #4: (cloned #1) + ST_WDG, COLOR0, EC, EG, EB, P3, P4, P1, + ST_TET, COLOR0, P3, P0, P1, P4, + ST_TET, COLOR1, P2, EC, EB, EG, + // Case #5: Unique case #4 + ST_WDG, COLOR0, EE, P4, EG, EA, P1, EB, + ST_WDG, COLOR0, P4, EE, EG, P3, ED, EC, + ST_WDG, COLOR1, P0, EA, EE, P2, EB, EG, + ST_WDG, COLOR1, ED, P0, EE, EC, P2, EG, + // Case #6: (cloned #3) + ST_PNT, 0, COLOR0, 7, P4, EG, EF, EC, EA, P3, P0, + ST_TET, COLOR0, EF, P4, EG, N0, + ST_PYR, COLOR0, EC, EA, EF, EG, N0, + ST_PYR, COLOR0, EC, EG, P4, P3, N0, + ST_TET, COLOR0, P3, P4, P0, N0, + ST_PYR, COLOR0, P0, P4, EF, EA, N0, + ST_PYR, COLOR0, P3, P0, EA, EC, N0, + ST_WDG, COLOR1, EC, EG, P2, EA, EF, P1, + // Case #7: Unique case #5 + ST_TET, COLOR0, EE, EF, EG, P4, + ST_WDG, COLOR0, EC, ED, P3, EG, EE, P4, + ST_WDG, COLOR1, EE, EF, EG, P0, P1, P2, + ST_WDG, COLOR1, P2, EC, EG, P0, ED, EE, + // Case #8: (cloned #1) + ST_WDG, COLOR0, ED, EH, EC, P0, P4, P2, + ST_TET, COLOR0, P0, P1, P2, P4, + ST_TET, COLOR1, P3, ED, EC, EH, + // Case #9: (cloned #3) + ST_PNT, 0, COLOR0, 7, P4, EE, EH, EA, EC, P1, P2, + ST_TET, COLOR0, EH, P4, EE, N0, + ST_PYR, COLOR0, EA, EC, EH, EE, N0, + ST_PYR, COLOR0, EA, EE, P4, P1, N0, + ST_TET, COLOR0, P1, P4, P2, N0, + ST_PYR, COLOR0, P2, P4, EH, EC, N0, + ST_PYR, COLOR0, P1, P2, EC, EA, N0, + ST_WDG, COLOR1, EA, EE, P0, EC, EH, P3, + // Case #10: (cloned #5) + ST_WDG, COLOR0, EH, P4, EF, ED, P0, EA, + ST_WDG, COLOR0, P4, EH, EF, P2, EC, EB, + ST_WDG, COLOR1, P3, ED, EH, P1, EA, EF, + ST_WDG, COLOR1, EC, P3, EH, EB, P1, EF, + // Case #11: (cloned #7) + ST_TET, COLOR0, EH, EE, EF, P4, + ST_WDG, COLOR0, EB, EC, P2, EF, EH, P4, + ST_WDG, COLOR1, EH, EE, EF, P3, P0, P1, + ST_WDG, COLOR1, P1, EB, EF, P3, EC, EH, + // Case #12: (cloned #3) + ST_PNT, 0, COLOR0, 7, P4, EH, EG, ED, EB, P0, P1, + ST_TET, COLOR0, EG, P4, EH, N0, + ST_PYR, COLOR0, ED, EB, EG, EH, N0, + ST_PYR, COLOR0, ED, EH, P4, P0, N0, + ST_TET, COLOR0, P0, P4, P1, N0, + ST_PYR, COLOR0, P1, P4, EG, EB, N0, + ST_PYR, COLOR0, P0, P1, EB, ED, N0, + ST_WDG, COLOR1, ED, EH, P3, EB, EG, P2, + // Case #13: (cloned #7) + ST_TET, COLOR0, EG, EH, EE, P4, + ST_WDG, COLOR0, EA, EB, P1, EE, EG, P4, + ST_WDG, COLOR1, EG, EH, EE, P2, P3, P0, + ST_WDG, COLOR1, P0, EA, EE, P2, EB, EG, + // Case #14: (cloned #7) + ST_TET, COLOR0, EF, EG, EH, P4, + ST_WDG, COLOR0, ED, EA, P0, EH, EF, P4, + ST_WDG, COLOR1, EF, EG, EH, P1, P2, P3, + ST_WDG, COLOR1, P3, ED, EH, P1, EA, EF, + // Case #15: Unique case #6 + ST_PYR, COLOR0, EE, EF, EG, EH, P4, + ST_HEX, COLOR1, P0, P1, P2, P3, EE, EF, EG, EH, + // Case #16: Unique case #7 + ST_HEX, COLOR0, P0, P1, P2, P3, EE, EF, EG, EH, + ST_PYR, COLOR1, EE, EF, EG, EH, P4, + // Case #17: Unique case #8 + ST_WDG, COLOR0, ED, EH, P3, EA, EF, P1, + ST_WDG, COLOR0, EF, EG, EH, P1, P2, P3, + ST_WDG, COLOR1, P4, EF, EH, P0, EA, ED, + ST_TET, COLOR1, EF, EG, EH, P4, + // Case #18: (cloned #17) + ST_WDG, COLOR0, EA, EE, P0, EB, EG, P2, + ST_WDG, COLOR0, EG, EH, EE, P2, P3, P0, + ST_WDG, COLOR1, P4, EG, EE, P1, EB, EA, + ST_TET, COLOR1, EG, EH, EE, P4, + // Case #19: Unique case #9 + ST_PNT, 0, COLOR1, 7, EH, EG, ED, EB, P0, P1, P4, + ST_WDG, COLOR0, ED, EH, P3, EB, EG, P2, + ST_PYR, COLOR1, EG, EH, ED, EB, N0, + ST_PYR, COLOR1, ED, P0, P1, EB, N0, + ST_TET, COLOR1, P0, P4, P1, N0, + ST_TET, COLOR1, EH, EG, P4, N0, + ST_PYR, COLOR1, EH, P4, P0, ED, N0, + ST_PYR, COLOR1, P4, EG, EB, P1, N0, + // Case #20: (cloned #17) + ST_WDG, COLOR0, EB, EF, P1, EC, EH, P3, + ST_WDG, COLOR0, EH, EE, EF, P3, P0, P1, + ST_WDG, COLOR1, P4, EH, EF, P2, EC, EB, + ST_TET, COLOR1, EH, EE, EF, P4, + // Case #21: Unique case #10 + ST_TET, COLOR0, EA, P1, EB, EF, + ST_TET, COLOR0, P3, ED, EC, EH, + ST_WDG, COLOR1, EA, EB, EF, P0, P2, P4, + ST_WDG, COLOR1, EC, ED, EH, P2, P0, P4, + // Case #22: (cloned #19) + ST_PNT, 0, COLOR1, 7, EE, EH, EA, EC, P1, P2, P4, + ST_WDG, COLOR0, EA, EE, P0, EC, EH, P3, + ST_PYR, COLOR1, EH, EE, EA, EC, N0, + ST_PYR, COLOR1, EA, P1, P2, EC, N0, + ST_TET, COLOR1, P1, P4, P2, N0, + ST_TET, COLOR1, EE, EH, P4, N0, + ST_PYR, COLOR1, EE, P4, P1, EA, N0, + ST_PYR, COLOR1, P4, EH, EC, P2, N0, + // Case #23: Unique case #11 + ST_TET, COLOR0, P3, ED, EC, EH, + ST_WDG, COLOR1, P0, P2, P4, ED, EC, EH, + ST_TET, COLOR1, P0, P1, P2, P4, + // Case #24: (cloned #17) + ST_WDG, COLOR0, EC, EG, P2, ED, EE, P0, + ST_WDG, COLOR0, EE, EF, EG, P0, P1, P2, + ST_WDG, COLOR1, P4, EE, EG, P3, ED, EC, + ST_TET, COLOR1, EE, EF, EG, P4, + // Case #25: (cloned #19) + ST_PNT, 0, COLOR1, 7, EG, EF, EC, EA, P3, P0, P4, + ST_WDG, COLOR0, EC, EG, P2, EA, EF, P1, + ST_PYR, COLOR1, EF, EG, EC, EA, N0, + ST_PYR, COLOR1, EC, P3, P0, EA, N0, + ST_TET, COLOR1, P3, P4, P0, N0, + ST_TET, COLOR1, EG, EF, P4, N0, + ST_PYR, COLOR1, EG, P4, P3, EC, N0, + ST_PYR, COLOR1, P4, EF, EA, P0, N0, + // Case #26: (cloned #21) + ST_TET, COLOR0, ED, P0, EA, EE, + ST_TET, COLOR0, P2, EC, EB, EG, + ST_WDG, COLOR1, ED, EA, EE, P3, P1, P4, + ST_WDG, COLOR1, EB, EC, EG, P1, P3, P4, + // Case #27: (cloned #23) + ST_TET, COLOR0, P2, EC, EB, EG, + ST_WDG, COLOR1, P3, P1, P4, EC, EB, EG, + ST_TET, COLOR1, P3, P0, P1, P4, + // Case #28: (cloned #19) + ST_PNT, 0, COLOR1, 7, EF, EE, EB, ED, P2, P3, P4, + ST_WDG, COLOR0, EB, EF, P1, ED, EE, P0, + ST_PYR, COLOR1, EE, EF, EB, ED, N0, + ST_PYR, COLOR1, EB, P2, P3, ED, N0, + ST_TET, COLOR1, P2, P4, P3, N0, + ST_TET, COLOR1, EF, EE, P4, N0, + ST_PYR, COLOR1, EF, P4, P2, EB, N0, + ST_PYR, COLOR1, P4, EE, ED, P3, N0, + // Case #29: (cloned #23) + ST_TET, COLOR0, P1, EB, EA, EF, + ST_WDG, COLOR1, P2, P0, P4, EB, EA, EF, + ST_TET, COLOR1, P2, P3, P0, P4, + // Case #30: (cloned #23) + ST_TET, COLOR0, P0, EA, ED, EE, + ST_WDG, COLOR1, P1, P3, P4, EA, ED, EE, + ST_TET, COLOR1, P1, P2, P3, P4, + // Case #31: Unique case #12 + ST_PYR, COLOR1, P0, P1, P2, P3, P4, + // Dummy + 0 +}; + +//--------------------------------------------------------------------------- +// Axom modifications +const size_t clipShapesPyrSize = sizeof(clipShapesPyr) / sizeof(unsigned char); +} // namespace visit +} // namespace clipping +} // namespace mir +} // namespace axom +// clang-format on +//--------------------------------------------------------------------------- diff --git a/src/axom/mir/clipping/ClipCasesQua.cpp b/src/axom/mir/clipping/ClipCasesQua.cpp new file mode 100644 index 0000000000..e870378465 --- /dev/null +++ b/src/axom/mir/clipping/ClipCasesQua.cpp @@ -0,0 +1,105 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt +// Project developers. See the top-level LICENSE file for dates and other +// details. No copyright assignment is required to contribute to VisIt. + +#include "ClipCases.h" +//--------------------------------------------------------------------------- +// Axom modifications +// clang-format off +namespace axom { +namespace mir { +namespace clipping { +namespace visit { +//--------------------------------------------------------------------------- + +// Programmer: Jeremy Meredith +// Date : September 18, 2003 + +// This file is meant to be read and created by a program other than a +// compiler. If you must modify it by hand, at least be nice to the +// parser and don't add anything else to this file or rearrange it. + +int numClipCasesQua = 16; + +int numClipShapesQua[16] = { + 1, 3, 3, 2, 3, 4, 2, 3, // cases 0 - 7 + 3, 2, 4, 3, 2, 3, 3, 1 // cases 8 - 15 +}; + +int startClipShapesQua[16] = { + 0, 6, 22, 38, 50, 66, 88, 100, // cases 0 - 7 + 116, 132, 144, 166, 182, 194, 210, 226 // cases 8 - 15 +}; + +unsigned char clipShapesQua[] = { + // Case #0: Unique case #1 + ST_QUA, COLOR0, P0, P1, P2, P3, + // Case #1: Unique case #2 + ST_QUA, COLOR0, ED, EA, P1, P3, + ST_TRI, COLOR0, P3, P1, P2, + ST_TRI, COLOR1, P0, EA, ED, + // Case #2: (cloned #1) + ST_QUA, COLOR0, EA, EB, P2, P0, + ST_TRI, COLOR0, P0, P2, P3, + ST_TRI, COLOR1, P1, EB, EA, + // Case #3: Unique case #3 + ST_QUA, COLOR0, ED, EB, P2, P3, + ST_QUA, COLOR1, P0, P1, EB, ED, + // Case #4: (cloned #1) + ST_QUA, COLOR0, EB, EC, P3, P1, + ST_TRI, COLOR0, P1, P3, P0, + ST_TRI, COLOR1, P2, EC, EB, + // Case #5: Unique case #4 + ST_TRI, COLOR0, ED, EC, P3, + ST_TRI, COLOR0, EB, EA, P1, + ST_QUA, COLOR1, P2, P0, EA, EB, + ST_QUA, COLOR1, P0, P2, EC, ED, + // Case #6: (cloned #3) + ST_QUA, COLOR0, EA, EC, P3, P0, + ST_QUA, COLOR1, P1, P2, EC, EA, + // Case #7: Unique case #5 + ST_TRI, COLOR0, ED, EC, P3, + ST_QUA, COLOR1, P0, P2, EC, ED, + ST_TRI, COLOR1, P1, P2, P0, + // Case #8: (cloned #1) + ST_QUA, COLOR0, EC, ED, P0, P2, + ST_TRI, COLOR0, P2, P0, P1, + ST_TRI, COLOR1, P3, ED, EC, + // Case #9: (cloned #3) + ST_QUA, COLOR0, EC, EA, P1, P2, + ST_QUA, COLOR1, P3, P0, EA, EC, + // Case #10: (cloned #5) + ST_TRI, COLOR0, EA, ED, P0, + ST_TRI, COLOR0, EC, EB, P2, + ST_QUA, COLOR1, P3, P1, EB, EC, + ST_QUA, COLOR1, P1, P3, ED, EA, + // Case #11: (cloned #7) + ST_TRI, COLOR0, EC, EB, P2, + ST_QUA, COLOR1, P3, P1, EB, EC, + ST_TRI, COLOR1, P0, P1, P3, + // Case #12: (cloned #3) + ST_QUA, COLOR0, EB, ED, P0, P1, + ST_QUA, COLOR1, P2, P3, ED, EB, + // Case #13: (cloned #7) + ST_TRI, COLOR0, EB, EA, P1, + ST_QUA, COLOR1, P2, P0, EA, EB, + ST_TRI, COLOR1, P3, P0, P2, + // Case #14: (cloned #7) + ST_TRI, COLOR0, EA, ED, P0, + ST_QUA, COLOR1, P1, P3, ED, EA, + ST_TRI, COLOR1, P2, P3, P1, + // Case #15: Unique case #6 + ST_QUA, COLOR1, P0, P1, P2, P3, + // Dummy + 0 +}; + +//--------------------------------------------------------------------------- +// Axom modifications +const size_t clipShapesQuaSize = sizeof(clipShapesQua) / sizeof(unsigned char); +} // namespace visit +} // namespace clipping +} // namespace mir +} // namespace axom +// clang-format on +//--------------------------------------------------------------------------- diff --git a/src/axom/mir/clipping/ClipCasesTet.cpp b/src/axom/mir/clipping/ClipCasesTet.cpp new file mode 100644 index 0000000000..fc04e6ca86 --- /dev/null +++ b/src/axom/mir/clipping/ClipCasesTet.cpp @@ -0,0 +1,97 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt +// Project developers. See the top-level LICENSE file for dates and other +// details. No copyright assignment is required to contribute to VisIt. + +#include "ClipCases.h" +//--------------------------------------------------------------------------- +// Axom modifications +// clang-format off +namespace axom { +namespace mir { +namespace clipping { +namespace visit { +//--------------------------------------------------------------------------- + +// Programmer: Jeremy Meredith +// Date : August 11, 2003 +// +// Modifications: +// Jeremy Meredith, Mon Sep 15 17:30:21 PDT 2003 +// Added ability for Centroid-Points to have an associated color. + +// This file is meant to be read and created by a program other than a +// compiler. If you must modify it by hand, at least be nice to the +// parser and don't add anything else to this file or rearrange it. + +int numClipCasesTet = 16; + +int numClipShapesTet[16] = { + 1, 2, 2, 2, 2, 2, 2, 2, // cases 0 - 7 + 2, 2, 2, 2, 2, 2, 2, 1 // cases 8 - 15 +}; + +int startClipShapesTet[16] = { + 0, 6, 20, 34, 50, 64, 80, 96, // cases 0 - 7 + 110, 124, 140, 156, 170, 186, 200, 214 // cases 8 - 15 +}; + +unsigned char clipShapesTet[] = { + // Case #0: Unique case #1 + ST_TET, COLOR0, P0, P1, P2, P3, + // Case #1: Unique case #2 + ST_WDG, COLOR0, EA, ED, EC, P1, P3, P2, + ST_TET, COLOR1, P0, EA, EC, ED, + // Case #2: (cloned #1) + ST_WDG, COLOR0, P0, P3, P2, EA, EE, EB, + ST_TET, COLOR1, P1, EB, EA, EE, + // Case #3: Unique case #3 + ST_WDG, COLOR0, P3, ED, EE, P2, EC, EB, + ST_WDG, COLOR1, P0, ED, EC, P1, EE, EB, + // Case #4: (cloned #1) + ST_WDG, COLOR0, EC, EF, EB, P0, P3, P1, + ST_TET, COLOR1, P2, EC, EB, EF, + // Case #5: (cloned #3) + ST_WDG, COLOR0, P1, EA, EB, P3, ED, EF, + ST_WDG, COLOR1, P2, EF, EB, P0, ED, EA, + // Case #6: (cloned #3) + ST_WDG, COLOR0, P3, EE, EF, P0, EA, EC, + ST_WDG, COLOR1, P1, EE, EA, P2, EF, EC, + // Case #7: Unique case #4 + ST_TET, COLOR0, ED, EE, EF, P3, + ST_WDG, COLOR1, ED, EE, EF, P0, P1, P2, + // Case #8: (cloned #1) + ST_WDG, COLOR0, P0, P2, P1, ED, EF, EE, + ST_TET, COLOR1, P3, EE, ED, EF, + // Case #9: (cloned #3) + ST_WDG, COLOR0, P2, EC, EF, P1, EA, EE, + ST_WDG, COLOR1, P0, EC, EA, P3, EF, EE, + // Case #10: (cloned #3) + ST_WDG, COLOR0, P0, EA, ED, P2, EB, EF, + ST_WDG, COLOR1, P3, EF, ED, P1, EB, EA, + // Case #11: (cloned #7) + ST_TET, COLOR0, EC, EF, EB, P2, + ST_WDG, COLOR1, P0, P1, P3, EC, EB, EF, + // Case #12: (cloned #3) + ST_WDG, COLOR0, P1, EB, EE, P0, EC, ED, + ST_WDG, COLOR1, P2, EB, EC, P3, EE, ED, + // Case #13: (cloned #7) + ST_TET, COLOR0, EA, EB, EE, P1, + ST_WDG, COLOR1, EA, EB, EE, P0, P2, P3, + // Case #14: (cloned #7) + ST_TET, COLOR0, EA, ED, EC, P0, + ST_WDG, COLOR1, P1, P2, P3, EA, EC, ED, + // Case #15: Unique case #5 + ST_TET, COLOR1, P0, P1, P2, P3, + // Dummy + 0 +}; + +//--------------------------------------------------------------------------- +// Axom modifications +const size_t clipShapesTetSize = sizeof(clipShapesTet) / sizeof(unsigned char); +} // namespace visit +} // namespace clipping +} // namespace mir +} // namespace axom +// clang-format on +//--------------------------------------------------------------------------- diff --git a/src/axom/mir/clipping/ClipCasesTri.cpp b/src/axom/mir/clipping/ClipCasesTri.cpp new file mode 100644 index 0000000000..68c27a94d7 --- /dev/null +++ b/src/axom/mir/clipping/ClipCasesTri.cpp @@ -0,0 +1,67 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt +// Project developers. See the top-level LICENSE file for dates and other +// details. No copyright assignment is required to contribute to VisIt. + +#include "ClipCases.h" +//--------------------------------------------------------------------------- +// Axom modifications +// clang-format off +namespace axom { +namespace mir { +namespace clipping { +namespace visit { +//--------------------------------------------------------------------------- + +// Programmer: Jeremy Meredith +// Date : September 18, 2003 + +// This file is meant to be read and created by a program other than a +// compiler. If you must modify it by hand, at least be nice to the +// parser and don't add anything else to this file or rearrange it. + +int numClipCasesTri = 8; + +int numClipShapesTri[8] = { + 1, 2, 2, 2, 2, 2, 2, 1 // cases 0 - 7 +}; + +int startClipShapesTri[8] = { + 0, 5, 16, 27, 38, 49, 60, 71 // cases 0 - 7 +}; + +unsigned char clipShapesTri[] = { + // Case #0: Unique case #1 + ST_TRI, COLOR0, P0, P1, P2, + // Case #1: Unique case #2 + ST_QUA, COLOR0, P1, P2, EC, EA, + ST_TRI, COLOR1, P0, EA, EC, + // Case #2: (cloned #1) + ST_QUA, COLOR0, P2, P0, EA, EB, + ST_TRI, COLOR1, P1, EB, EA, + // Case #3: Unique case #3 + ST_TRI, COLOR0, EC, EB, P2, + ST_QUA, COLOR1, P0, P1, EB, EC, + // Case #4: (cloned #1) + ST_QUA, COLOR0, P0, P1, EB, EC, + ST_TRI, COLOR1, P2, EC, EB, + // Case #5: (cloned #3) + ST_TRI, COLOR0, EB, EA, P1, + ST_QUA, COLOR1, P2, P0, EA, EB, + // Case #6: (cloned #3) + ST_TRI, COLOR0, EA, EC, P0, + ST_QUA, COLOR1, P1, P2, EC, EA, + // Case #7: Unique case #4 + ST_TRI, COLOR1, P0, P1, P2, + // Dummy + 0 +}; + +//--------------------------------------------------------------------------- +// Axom modifications +const size_t clipShapesTriSize = sizeof(clipShapesTri) / sizeof(unsigned char); +} // namespace visit +} // namespace clipping +} // namespace mir +} // namespace axom +// clang-format on +//--------------------------------------------------------------------------- diff --git a/src/axom/mir/clipping/ClipCasesWdg.cpp b/src/axom/mir/clipping/ClipCasesWdg.cpp new file mode 100644 index 0000000000..03d3dba8a5 --- /dev/null +++ b/src/axom/mir/clipping/ClipCasesWdg.cpp @@ -0,0 +1,625 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt +// Project developers. See the top-level LICENSE file for dates and other +// details. No copyright assignment is required to contribute to VisIt. + +#include "ClipCases.h" +//--------------------------------------------------------------------------- +// Axom modifications +// clang-format off +namespace axom { +namespace mir { +namespace clipping { +namespace visit { +//--------------------------------------------------------------------------- + +// Programmer: Jeremy Meredith +// Date : August 11, 2003 +// +// Modifications: +// Jeremy Meredith, Mon Sep 15 17:30:21 PDT 2003 +// Added ability for Centroid-Points to have an associated color. + +// This file is meant to be read and created by a program other than a +// compiler. If you must modify it by hand, at least be nice to the +// parser and don't add anything else to this file or rearrange it. + +int numClipCasesWdg = 64; + +int numClipShapesWdg[64] = { + 1, 3, 3, 9, 3, 9, 9, 2, // cases 0 - 7 + 3, 2, 15, 13, 15, 13, 10, 9, // cases 8 - 15 + 3, 15, 2, 13, 15, 10, 13, 9, // cases 16 - 23 + 9, 13, 13, 2, 10, 5, 5, 3, // cases 24 - 31 + 3, 15, 15, 10, 2, 13, 13, 9, // cases 32 - 39 + 9, 13, 10, 5, 13, 2, 5, 3, // cases 40 - 47 + 9, 10, 13, 5, 13, 5, 2, 3, // cases 48 - 55 + 2, 9, 9, 3, 9, 3, 3, 1 // cases 56 - 63 +}; + +int startClipShapesWdg[64] = { + 0, 8, 29, 50, 115, 136, 201, 266, // cases 0 - 7 + 282, 303, 321, 421, 508, 608, 695, 768, // cases 8 - 15 + 833, 854, 954, 972, 1059, 1159, 1232, 1319, // cases 16 - 23 + 1384, 1449, 1536, 1623, 1641, 1714, 1748, 1782, // cases 24 - 31 + 1803, 1824, 1924, 2024, 2097, 2115, 2202, 2289, // cases 32 - 39 + 2354, 2419, 2506, 2579, 2613, 2700, 2718, 2752, // cases 40 - 47 + 2773, 2838, 2911, 2998, 3032, 3119, 3153, 3171, // cases 48 - 55 + 3192, 3208, 3273, 3338, 3359, 3424, 3445, 3466 // cases 56 - 63 +}; + +unsigned char clipShapesWdg[] = { + // Case #0: Unique case #1 + ST_WDG, COLOR0, P0, P1, P2, P3, P4, P5, + // Case #1: Unique case #2 + ST_WDG, COLOR0, EA, EC, EG, P1, P2, P3, + ST_PYR, COLOR0, P1, P2, P5, P4, P3, + ST_TET, COLOR1, EG, EA, EC, P0, + // Case #2: (cloned #1) + ST_WDG, COLOR0, EB, EA, EH, P2, P0, P4, + ST_PYR, COLOR0, P2, P0, P3, P5, P4, + ST_TET, COLOR1, EH, EB, EA, P1, + // Case #3: Unique case #3 + ST_PNT, 0, COLOR0, 7, P2, P3, P4, EB, EC, EG, EH, + ST_TET, COLOR0, P4, P5, P3, P2, + ST_TET, COLOR0, P2, P3, P4, N0, + ST_PYR, COLOR0, EG, EH, P4, P3, N0, + ST_PYR, COLOR0, EB, EH, EG, EC, N0, + ST_TET, COLOR0, P2, EB, EC, N0, + ST_PYR, COLOR0, P2, EC, EG, P3, N0, + ST_PYR, COLOR0, EH, EB, P2, P4, N0, + ST_WDG, COLOR1, EC, EG, P0, EB, EH, P1, + // Case #4: (cloned #1) + ST_WDG, COLOR0, EC, EB, EI, P0, P1, P5, + ST_PYR, COLOR0, P0, P1, P4, P3, P5, + ST_TET, COLOR1, EI, EC, EB, P2, + // Case #5: (cloned #3) + ST_PNT, 0, COLOR0, 7, P1, P5, P3, EA, EB, EI, EG, + ST_TET, COLOR0, P3, P4, P5, P1, + ST_TET, COLOR0, P1, P5, P3, N0, + ST_PYR, COLOR0, EI, EG, P3, P5, N0, + ST_PYR, COLOR0, EA, EG, EI, EB, N0, + ST_TET, COLOR0, P1, EA, EB, N0, + ST_PYR, COLOR0, P1, EB, EI, P5, N0, + ST_PYR, COLOR0, EG, EA, P1, P3, N0, + ST_WDG, COLOR1, EB, EI, P2, EA, EG, P0, + // Case #6: (cloned #3) + ST_PNT, 0, COLOR0, 7, P0, P4, P5, EC, EA, EH, EI, + ST_TET, COLOR0, P5, P3, P4, P0, + ST_TET, COLOR0, P0, P4, P5, N0, + ST_PYR, COLOR0, EH, EI, P5, P4, N0, + ST_PYR, COLOR0, EC, EI, EH, EA, N0, + ST_TET, COLOR0, P0, EC, EA, N0, + ST_PYR, COLOR0, P0, EA, EH, P4, N0, + ST_PYR, COLOR0, EI, EC, P0, P5, N0, + ST_WDG, COLOR1, EA, EH, P1, EC, EI, P2, + // Case #7: Unique case #4 + ST_WDG, COLOR0, EG, EH, EI, P3, P4, P5, + ST_WDG, COLOR1, P0, P1, P2, EG, EH, EI, + // Case #8: (cloned #1) + ST_WDG, COLOR0, P4, P5, P0, ED, EF, EG, + ST_PYR, COLOR0, P4, P1, P2, P5, P0, + ST_TET, COLOR1, EG, EF, ED, P3, + // Case #9: Unique case #5 + ST_HEX, COLOR0, P1, P2, P5, P4, EA, EC, EF, ED, + ST_WDG, COLOR1, P0, EA, EC, P3, ED, EF, + // Case #10: Unique case #6 + ST_PNT, 0, NOCOLOR, 6, EA, EB, EH, ED, EF, EG, + ST_PYR, COLOR0, P5, P0, EG, EF, N0, + ST_TET, COLOR0, P0, EA, EG, N0, + ST_PYR, COLOR0, P0, P2, EB, EA, N0, + ST_TET, COLOR0, P5, P2, P0, N0, + ST_PYR, COLOR0, P4, EH, EB, P2, N0, + ST_TET, COLOR0, P5, P4, P2, N0, + ST_PYR, COLOR0, EF, ED, P4, P5, N0, + ST_TET, COLOR0, ED, EH, P4, N0, + ST_PYR, COLOR1, EG, EA, P1, P3, N0, + ST_PYR, COLOR1, P3, P1, EH, ED, N0, + ST_TET, COLOR1, P3, ED, EF, N0, + ST_TET, COLOR1, EF, EG, P3, N0, + ST_TET, COLOR1, P1, EB, EH, N0, + ST_TET, COLOR1, P1, EA, EB, N0, + // Case #11: Unique case #7 + ST_PNT, 0, NOCOLOR, 5, EB, EC, EF, ED, EH, + ST_PYR, COLOR0, P4, P5, EF, ED, N0, + ST_TET, COLOR0, ED, EH, P4, N0, + ST_PYR, COLOR0, EC, EF, P5, P2, N0, + ST_PYR, COLOR0, EB, P2, P4, EH, N0, + ST_TET, COLOR0, P4, P2, P5, N0, + ST_TET, COLOR0, P2, EB, EC, N0, + ST_TET, COLOR1, P0, P1, P3, N0, + ST_PYR, COLOR1, EC, EB, P1, P0, N0, + ST_PYR, COLOR1, EC, P0, P3, EF, N0, + ST_TET, COLOR1, EF, P3, ED, N0, + ST_PYR, COLOR1, P3, P1, EH, ED, N0, + ST_TET, COLOR1, P1, EB, EH, N0, + // Case #12: (cloned #10) + ST_PNT, 0, NOCOLOR, 6, EF, ED, EG, EC, EB, EI, + ST_PYR, COLOR0, P1, EB, EI, P5, N0, + ST_TET, COLOR0, P5, EI, EF, N0, + ST_PYR, COLOR0, P5, EF, ED, P4, N0, + ST_TET, COLOR0, P1, P5, P4, N0, + ST_PYR, COLOR0, P0, P4, ED, EG, N0, + ST_TET, COLOR0, P1, P4, P0, N0, + ST_PYR, COLOR0, EB, P1, P0, EC, N0, + ST_TET, COLOR0, EC, P0, EG, N0, + ST_PYR, COLOR1, EI, P2, P3, EF, N0, + ST_PYR, COLOR1, P2, EC, EG, P3, N0, + ST_TET, COLOR1, P2, EB, EC, N0, + ST_TET, COLOR1, EB, P2, EI, N0, + ST_TET, COLOR1, P3, EG, ED, N0, + ST_TET, COLOR1, P3, ED, EF, N0, + // Case #13: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, EB, EA, ED, EF, EI, + ST_PYR, COLOR0, P5, EF, ED, P4, N0, + ST_TET, COLOR0, EF, P5, EI, N0, + ST_PYR, COLOR0, EA, P1, P4, ED, N0, + ST_PYR, COLOR0, EB, EI, P5, P1, N0, + ST_TET, COLOR0, P5, P4, P1, N0, + ST_TET, COLOR0, P1, EA, EB, N0, + ST_TET, COLOR1, P0, P3, P2, N0, + ST_PYR, COLOR1, EA, P0, P2, EB, N0, + ST_PYR, COLOR1, EA, ED, P3, P0, N0, + ST_TET, COLOR1, ED, EF, P3, N0, + ST_PYR, COLOR1, P3, EF, EI, P2, N0, + ST_TET, COLOR1, P2, EI, EB, N0, + // Case #14: Unique case #8 + ST_PNT, 0, COLOR1, 7, ED, EF, EI, EH, P3, P2, P1, + ST_TET, COLOR0, P0, EC, EA, EG, + ST_WDG, COLOR0, EF, EI, P5, ED, EH, P4, + ST_WDG, COLOR1, P2, P1, P3, EC, EA, EG, + ST_PYR, COLOR1, EF, ED, EH, EI, N0, + ST_PYR, COLOR1, EH, P1, P2, EI, N0, + ST_TET, COLOR1, P3, P2, P1, N0, + ST_TET, COLOR1, P3, ED, EF, N0, + ST_PYR, COLOR1, ED, P3, P1, EH, N0, + ST_PYR, COLOR1, EI, P2, P3, EF, N0, + // Case #15: Unique case #9 + ST_PNT, 0, COLOR1, 7, P1, P2, P3, EF, ED, EH, EI, + ST_WDG, COLOR0, ED, P4, EH, EF, P5, EI, + ST_TET, COLOR1, P0, P2, P1, P3, + ST_PYR, COLOR1, EF, ED, EH, EI, N0, + ST_PYR, COLOR1, EI, EH, P1, P2, N0, + ST_TET, COLOR1, P3, P2, P1, N0, + ST_TET, COLOR1, P3, ED, EF, N0, + ST_PYR, COLOR1, P3, P1, EH, ED, N0, + ST_PYR, COLOR1, P2, P3, EF, EI, N0, + // Case #16: (cloned #1) + ST_WDG, COLOR0, P5, P3, P1, EE, ED, EH, + ST_PYR, COLOR0, P5, P2, P0, P3, P1, + ST_TET, COLOR1, EH, ED, EE, P4, + // Case #17: (cloned #10) + ST_PNT, 0, NOCOLOR, 6, ED, EE, EH, EA, EC, EG, + ST_PYR, COLOR0, P2, EC, EG, P3, N0, + ST_TET, COLOR0, P3, EG, ED, N0, + ST_PYR, COLOR0, P3, ED, EE, P5, N0, + ST_TET, COLOR0, P2, P3, P5, N0, + ST_PYR, COLOR0, P1, P5, EE, EH, N0, + ST_TET, COLOR0, P2, P5, P1, N0, + ST_PYR, COLOR0, EC, P2, P1, EA, N0, + ST_TET, COLOR0, EA, P1, EH, N0, + ST_PYR, COLOR1, EG, P0, P4, ED, N0, + ST_PYR, COLOR1, P0, EA, EH, P4, N0, + ST_TET, COLOR1, P0, EC, EA, N0, + ST_TET, COLOR1, EC, P0, EG, N0, + ST_TET, COLOR1, P4, EH, EE, N0, + ST_TET, COLOR1, P4, EE, ED, N0, + // Case #18: (cloned #9) + ST_HEX, COLOR0, P2, P0, P3, P5, EB, EA, ED, EE, + ST_WDG, COLOR1, P1, EB, EA, P4, EE, ED, + // Case #19: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, EC, EB, EE, ED, EG, + ST_PYR, COLOR0, P3, ED, EE, P5, N0, + ST_TET, COLOR0, ED, P3, EG, N0, + ST_PYR, COLOR0, EB, P2, P5, EE, N0, + ST_PYR, COLOR0, EC, EG, P3, P2, N0, + ST_TET, COLOR0, P3, P5, P2, N0, + ST_TET, COLOR0, P2, EB, EC, N0, + ST_TET, COLOR1, P1, P4, P0, N0, + ST_PYR, COLOR1, EB, P1, P0, EC, N0, + ST_PYR, COLOR1, EB, EE, P4, P1, N0, + ST_TET, COLOR1, EE, ED, P4, N0, + ST_PYR, COLOR1, P4, ED, EG, P0, N0, + ST_TET, COLOR1, P0, EG, EC, N0, + // Case #20: (cloned #10) + ST_PNT, 0, NOCOLOR, 6, EB, EC, EI, EE, ED, EH, + ST_PYR, COLOR0, P3, P1, EH, ED, N0, + ST_TET, COLOR0, P1, EB, EH, N0, + ST_PYR, COLOR0, P1, P0, EC, EB, N0, + ST_TET, COLOR0, P3, P0, P1, N0, + ST_PYR, COLOR0, P5, EI, EC, P0, N0, + ST_TET, COLOR0, P3, P5, P0, N0, + ST_PYR, COLOR0, ED, EE, P5, P3, N0, + ST_TET, COLOR0, EE, EI, P5, N0, + ST_PYR, COLOR1, EH, EB, P2, P4, N0, + ST_PYR, COLOR1, P4, P2, EI, EE, N0, + ST_TET, COLOR1, P4, EE, ED, N0, + ST_TET, COLOR1, ED, EH, P4, N0, + ST_TET, COLOR1, P2, EC, EI, N0, + ST_TET, COLOR1, P2, EB, EC, N0, + // Case #21: (cloned #14) + ST_PNT, 0, COLOR1, 7, EE, ED, EG, EI, P4, P0, P2, + ST_TET, COLOR0, P1, EA, EB, EH, + ST_WDG, COLOR0, ED, EG, P3, EE, EI, P5, + ST_WDG, COLOR1, P0, P2, P4, EA, EB, EH, + ST_PYR, COLOR1, ED, EE, EI, EG, N0, + ST_PYR, COLOR1, EI, P2, P0, EG, N0, + ST_TET, COLOR1, P4, P0, P2, N0, + ST_TET, COLOR1, P4, EE, ED, N0, + ST_PYR, COLOR1, EE, P4, P2, EI, N0, + ST_PYR, COLOR1, EG, P0, P4, ED, N0, + // Case #22: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, EC, EA, ED, EE, EI, + ST_PYR, COLOR0, P5, P3, ED, EE, N0, + ST_TET, COLOR0, EE, EI, P5, N0, + ST_PYR, COLOR0, EA, ED, P3, P0, N0, + ST_PYR, COLOR0, EC, P0, P5, EI, N0, + ST_TET, COLOR0, P5, P0, P3, N0, + ST_TET, COLOR0, P0, EC, EA, N0, + ST_TET, COLOR1, P1, P2, P4, N0, + ST_PYR, COLOR1, EA, EC, P2, P1, N0, + ST_PYR, COLOR1, EA, P1, P4, ED, N0, + ST_TET, COLOR1, ED, P4, EE, N0, + ST_PYR, COLOR1, P4, P2, EI, EE, N0, + ST_TET, COLOR1, P2, EC, EI, N0, + // Case #23: (cloned #15) + ST_PNT, 0, COLOR1, 7, P2, P0, P4, ED, EE, EI, EG, + ST_WDG, COLOR0, EE, P5, EI, ED, P3, EG, + ST_TET, COLOR1, P1, P0, P2, P4, + ST_PYR, COLOR1, ED, EE, EI, EG, N0, + ST_PYR, COLOR1, EG, EI, P2, P0, N0, + ST_TET, COLOR1, P4, P0, P2, N0, + ST_TET, COLOR1, P4, EE, ED, N0, + ST_PYR, COLOR1, P4, P2, EI, EE, N0, + ST_PYR, COLOR1, P0, P4, ED, EG, N0, + // Case #24: (cloned #3) + ST_PNT, 0, COLOR0, 7, P5, P0, P1, EE, EF, EG, EH, + ST_TET, COLOR0, P1, P0, P2, P5, + ST_TET, COLOR0, P5, P1, P0, N0, + ST_PYR, COLOR0, EG, P0, P1, EH, N0, + ST_PYR, COLOR0, EE, EF, EG, EH, N0, + ST_TET, COLOR0, P5, EF, EE, N0, + ST_PYR, COLOR0, P5, P0, EG, EF, N0, + ST_PYR, COLOR0, EH, P1, P5, EE, N0, + ST_WDG, COLOR1, EE, EH, P4, EF, EG, P3, + // Case #25: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, EE, EF, EC, EA, EH, + ST_PYR, COLOR0, P1, EA, EC, P2, N0, + ST_TET, COLOR0, EA, P1, EH, N0, + ST_PYR, COLOR0, EF, P5, P2, EC, N0, + ST_PYR, COLOR0, EE, EH, P1, P5, N0, + ST_TET, COLOR0, P1, P2, P5, N0, + ST_TET, COLOR0, P5, EF, EE, N0, + ST_TET, COLOR1, P3, P0, P4, N0, + ST_PYR, COLOR1, EF, P3, P4, EE, N0, + ST_PYR, COLOR1, EF, EC, P0, P3, N0, + ST_TET, COLOR1, EC, EA, P0, N0, + ST_PYR, COLOR1, P0, EA, EH, P4, N0, + ST_TET, COLOR1, P4, EH, EE, N0, + // Case #26: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, EF, EE, EB, EA, EG, + ST_PYR, COLOR0, P0, P2, EB, EA, N0, + ST_TET, COLOR0, EA, EG, P0, N0, + ST_PYR, COLOR0, EE, EB, P2, P5, N0, + ST_PYR, COLOR0, EF, P5, P0, EG, N0, + ST_TET, COLOR0, P0, P5, P2, N0, + ST_TET, COLOR0, P5, EF, EE, N0, + ST_TET, COLOR1, P4, P3, P1, N0, + ST_PYR, COLOR1, EE, EF, P3, P4, N0, + ST_PYR, COLOR1, EE, P4, P1, EB, N0, + ST_TET, COLOR1, EB, P1, EA, N0, + ST_PYR, COLOR1, P1, P3, EG, EA, N0, + ST_TET, COLOR1, P3, EF, EG, N0, + // Case #27: Unique case #10 + ST_WDG, COLOR0, EF, P5, EE, EC, P2, EB, + ST_HEX, COLOR1, P3, P4, EE, EF, P0, P1, EB, EC, + // Case #28: (cloned #14) + ST_PNT, 0, COLOR1, 7, EC, EB, EH, EG, P2, P4, P3, + ST_TET, COLOR0, P5, EF, EE, EI, + ST_WDG, COLOR0, EC, EG, P0, EB, EH, P1, + ST_WDG, COLOR1, EE, EF, EI, P4, P3, P2, + ST_PYR, COLOR1, EB, EH, EG, EC, N0, + ST_PYR, COLOR1, EG, EH, P4, P3, N0, + ST_TET, COLOR1, P2, P3, P4, N0, + ST_TET, COLOR1, P2, EB, EC, N0, + ST_PYR, COLOR1, EC, EG, P3, P2, N0, + ST_PYR, COLOR1, EH, EB, P2, P4, N0, + // Case #29: Unique case #11 + ST_TET, COLOR0, P1, EA, EB, EH, + ST_TET, COLOR0, EF, EE, P5, EI, + ST_WDG, COLOR1, P2, P3, P4, EI, EF, EE, + ST_TET, COLOR1, P2, P3, P4, P0, + ST_WDG, COLOR1, P2, P4, P0, EB, EH, EA, + // Case #30: (cloned #29) + ST_TET, COLOR0, P5, EF, EE, EI, + ST_TET, COLOR0, EA, P0, EC, EG, + ST_WDG, COLOR1, EG, EA, EC, P3, P1, P2, + ST_TET, COLOR1, P3, P2, P1, P4, + ST_WDG, COLOR1, EF, EI, EE, P3, P2, P4, + // Case #31: Unique case #12 + ST_TET, COLOR0, EF, EI, EE, P5, + ST_WDG, COLOR1, EI, EE, EF, P2, P4, P3, + ST_PYR, COLOR1, P0, P1, P4, P3, P2, + // Case #32: (cloned #1) + ST_WDG, COLOR0, P3, P4, P2, EF, EE, EI, + ST_PYR, COLOR0, P3, P0, P1, P4, P2, + ST_TET, COLOR1, EI, EE, EF, P5, + // Case #33: (cloned #10) + ST_PNT, 0, NOCOLOR, 6, EC, EA, EG, EF, EE, EI, + ST_PYR, COLOR0, P4, P2, EI, EE, N0, + ST_TET, COLOR0, P2, EC, EI, N0, + ST_PYR, COLOR0, P2, P1, EA, EC, N0, + ST_TET, COLOR0, P4, P1, P2, N0, + ST_PYR, COLOR0, P3, EG, EA, P1, N0, + ST_TET, COLOR0, P4, P3, P1, N0, + ST_PYR, COLOR0, EE, EF, P3, P4, N0, + ST_TET, COLOR0, EF, EG, P3, N0, + ST_PYR, COLOR1, EI, EC, P0, P5, N0, + ST_PYR, COLOR1, P5, P0, EG, EF, N0, + ST_TET, COLOR1, P5, EF, EE, N0, + ST_TET, COLOR1, EE, EI, P5, N0, + ST_TET, COLOR1, P0, EA, EG, N0, + ST_TET, COLOR1, P0, EC, EA, N0, + // Case #34: (cloned #10) + ST_PNT, 0, NOCOLOR, 6, EE, EF, EI, EB, EA, EH, + ST_PYR, COLOR0, P0, EA, EH, P4, N0, + ST_TET, COLOR0, P4, EH, EE, N0, + ST_PYR, COLOR0, P4, EE, EF, P3, N0, + ST_TET, COLOR0, P0, P4, P3, N0, + ST_PYR, COLOR0, P2, P3, EF, EI, N0, + ST_TET, COLOR0, P0, P3, P2, N0, + ST_PYR, COLOR0, EA, P0, P2, EB, N0, + ST_TET, COLOR0, EB, P2, EI, N0, + ST_PYR, COLOR1, EH, P1, P5, EE, N0, + ST_PYR, COLOR1, P1, EB, EI, P5, N0, + ST_TET, COLOR1, P1, EA, EB, N0, + ST_TET, COLOR1, EA, P1, EH, N0, + ST_TET, COLOR1, P5, EI, EF, N0, + ST_TET, COLOR1, P5, EF, EE, N0, + // Case #35: (cloned #14) + ST_PNT, 0, COLOR1, 7, EF, EE, EH, EG, P5, P1, P0, + ST_TET, COLOR0, P2, EB, EC, EI, + ST_WDG, COLOR0, EE, EH, P4, EF, EG, P3, + ST_WDG, COLOR1, P1, P0, P5, EB, EC, EI, + ST_PYR, COLOR1, EE, EF, EG, EH, N0, + ST_PYR, COLOR1, EG, P0, P1, EH, N0, + ST_TET, COLOR1, P5, P1, P0, N0, + ST_TET, COLOR1, P5, EF, EE, N0, + ST_PYR, COLOR1, EF, P5, P0, EG, N0, + ST_PYR, COLOR1, EH, P1, P5, EE, N0, + // Case #36: (cloned #9) + ST_HEX, COLOR0, P0, P1, P4, P3, EC, EB, EE, EF, + ST_WDG, COLOR1, P2, EC, EB, P5, EF, EE, + // Case #37: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, EA, EB, EE, EF, EG, + ST_PYR, COLOR0, P3, P4, EE, EF, N0, + ST_TET, COLOR0, EF, EG, P3, N0, + ST_PYR, COLOR0, EB, EE, P4, P1, N0, + ST_PYR, COLOR0, EA, P1, P3, EG, N0, + ST_TET, COLOR0, P3, P1, P4, N0, + ST_TET, COLOR0, P1, EA, EB, N0, + ST_TET, COLOR1, P2, P0, P5, N0, + ST_PYR, COLOR1, EB, EA, P0, P2, N0, + ST_PYR, COLOR1, EB, P2, P5, EE, N0, + ST_TET, COLOR1, EE, P5, EF, N0, + ST_PYR, COLOR1, P5, P0, EG, EF, N0, + ST_TET, COLOR1, P0, EA, EG, N0, + // Case #38: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, EA, EC, EF, EE, EH, + ST_PYR, COLOR0, P4, EE, EF, P3, N0, + ST_TET, COLOR0, EE, P4, EH, N0, + ST_PYR, COLOR0, EC, P0, P3, EF, N0, + ST_PYR, COLOR0, EA, EH, P4, P0, N0, + ST_TET, COLOR0, P4, P3, P0, N0, + ST_TET, COLOR0, P0, EC, EA, N0, + ST_TET, COLOR1, P2, P5, P1, N0, + ST_PYR, COLOR1, EC, P2, P1, EA, N0, + ST_PYR, COLOR1, EC, EF, P5, P2, N0, + ST_TET, COLOR1, EF, EE, P5, N0, + ST_PYR, COLOR1, P5, EE, EH, P1, N0, + ST_TET, COLOR1, P1, EH, EA, N0, + // Case #39: (cloned #15) + ST_PNT, 0, COLOR1, 7, P0, P1, P5, EE, EF, EG, EH, + ST_WDG, COLOR0, EF, P3, EG, EE, P4, EH, + ST_TET, COLOR1, P2, P1, P0, P5, + ST_PYR, COLOR1, EE, EF, EG, EH, N0, + ST_PYR, COLOR1, EH, EG, P0, P1, N0, + ST_TET, COLOR1, P5, P1, P0, N0, + ST_TET, COLOR1, P5, EF, EE, N0, + ST_PYR, COLOR1, P5, P0, EG, EF, N0, + ST_PYR, COLOR1, P1, P5, EE, EH, N0, + // Case #40: (cloned #3) + ST_PNT, 0, COLOR0, 7, P4, P2, P0, ED, EE, EI, EG, + ST_TET, COLOR0, P0, P2, P1, P4, + ST_TET, COLOR0, P4, P0, P2, N0, + ST_PYR, COLOR0, EI, P2, P0, EG, N0, + ST_PYR, COLOR0, ED, EE, EI, EG, N0, + ST_TET, COLOR0, P4, EE, ED, N0, + ST_PYR, COLOR0, P4, P2, EI, EE, N0, + ST_PYR, COLOR0, EG, P0, P4, ED, N0, + ST_WDG, COLOR1, ED, EG, P3, EE, EI, P5, + // Case #41: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, EE, ED, EA, EC, EI, + ST_PYR, COLOR0, P2, P1, EA, EC, N0, + ST_TET, COLOR0, EC, EI, P2, N0, + ST_PYR, COLOR0, ED, EA, P1, P4, N0, + ST_PYR, COLOR0, EE, P4, P2, EI, N0, + ST_TET, COLOR0, P2, P4, P1, N0, + ST_TET, COLOR0, P4, EE, ED, N0, + ST_TET, COLOR1, P3, P5, P0, N0, + ST_PYR, COLOR1, ED, EE, P5, P3, N0, + ST_PYR, COLOR1, ED, P3, P0, EA, N0, + ST_TET, COLOR1, EA, P0, EC, N0, + ST_PYR, COLOR1, P0, P5, EI, EC, N0, + ST_TET, COLOR1, P5, EE, EI, N0, + // Case #42: (cloned #14) + ST_PNT, 0, COLOR1, 7, EB, EA, EG, EI, P1, P3, P5, + ST_TET, COLOR0, P4, EE, ED, EH, + ST_WDG, COLOR0, EB, EI, P2, EA, EG, P0, + ST_WDG, COLOR1, ED, EE, EH, P3, P5, P1, + ST_PYR, COLOR1, EA, EG, EI, EB, N0, + ST_PYR, COLOR1, EI, EG, P3, P5, N0, + ST_TET, COLOR1, P1, P5, P3, N0, + ST_TET, COLOR1, P1, EA, EB, N0, + ST_PYR, COLOR1, EB, EI, P5, P1, N0, + ST_PYR, COLOR1, EG, EA, P1, P3, N0, + // Case #43: (cloned #29) + ST_TET, COLOR0, P4, EE, ED, EH, + ST_TET, COLOR0, EC, P2, EB, EI, + ST_WDG, COLOR1, EI, EC, EB, P5, P0, P1, + ST_TET, COLOR1, P5, P1, P0, P3, + ST_WDG, COLOR1, EE, EH, ED, P5, P1, P3, + // Case #44: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, ED, EE, EB, EC, EG, + ST_PYR, COLOR0, P0, EC, EB, P1, N0, + ST_TET, COLOR0, EC, P0, EG, N0, + ST_PYR, COLOR0, EE, P4, P1, EB, N0, + ST_PYR, COLOR0, ED, EG, P0, P4, N0, + ST_TET, COLOR0, P0, P1, P4, N0, + ST_TET, COLOR0, P4, EE, ED, N0, + ST_TET, COLOR1, P5, P2, P3, N0, + ST_PYR, COLOR1, EE, P5, P3, ED, N0, + ST_PYR, COLOR1, EE, EB, P2, P5, N0, + ST_TET, COLOR1, EB, EC, P2, N0, + ST_PYR, COLOR1, P2, EC, EG, P3, N0, + ST_TET, COLOR1, P3, EG, ED, N0, + // Case #45: (cloned #27) + ST_WDG, COLOR0, EE, P4, ED, EB, P1, EA, + ST_HEX, COLOR1, P5, P3, ED, EE, P2, P0, EA, EB, + // Case #46: (cloned #29) + ST_TET, COLOR0, P0, EC, EA, EG, + ST_TET, COLOR0, EE, ED, P4, EH, + ST_WDG, COLOR1, P1, P5, P3, EH, EE, ED, + ST_TET, COLOR1, P1, P5, P3, P2, + ST_WDG, COLOR1, P1, P3, P2, EA, EG, EC, + // Case #47: (cloned #31) + ST_TET, COLOR0, EE, EH, ED, P4, + ST_WDG, COLOR1, EH, ED, EE, P1, P3, P5, + ST_PYR, COLOR1, P2, P0, P3, P5, P1, + // Case #48: (cloned #3) + ST_PNT, 0, COLOR0, 7, P3, P1, P2, EF, ED, EH, EI, + ST_TET, COLOR0, P2, P1, P0, P3, + ST_TET, COLOR0, P3, P2, P1, N0, + ST_PYR, COLOR0, EH, P1, P2, EI, N0, + ST_PYR, COLOR0, EF, ED, EH, EI, N0, + ST_TET, COLOR0, P3, ED, EF, N0, + ST_PYR, COLOR0, P3, P1, EH, ED, N0, + ST_PYR, COLOR0, EI, P2, P3, EF, N0, + ST_WDG, COLOR1, EF, EI, P5, ED, EH, P4, + // Case #49: (cloned #14) + ST_PNT, 0, COLOR1, 7, EA, EC, EI, EH, P0, P5, P4, + ST_TET, COLOR0, P3, ED, EF, EG, + ST_WDG, COLOR0, EA, EH, P1, EC, EI, P2, + ST_WDG, COLOR1, EF, ED, EG, P5, P4, P0, + ST_PYR, COLOR1, EC, EI, EH, EA, N0, + ST_PYR, COLOR1, EH, EI, P5, P4, N0, + ST_TET, COLOR1, P0, P4, P5, N0, + ST_TET, COLOR1, P0, EC, EA, N0, + ST_PYR, COLOR1, EA, EH, P4, P0, N0, + ST_PYR, COLOR1, EI, EC, P0, P5, N0, + // Case #50: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, EF, ED, EA, EB, EI, + ST_PYR, COLOR0, P2, EB, EA, P0, N0, + ST_TET, COLOR0, EB, P2, EI, N0, + ST_PYR, COLOR0, ED, P3, P0, EA, N0, + ST_PYR, COLOR0, EF, EI, P2, P3, N0, + ST_TET, COLOR0, P2, P0, P3, N0, + ST_TET, COLOR0, P3, ED, EF, N0, + ST_TET, COLOR1, P4, P1, P5, N0, + ST_PYR, COLOR1, ED, P4, P5, EF, N0, + ST_PYR, COLOR1, ED, EA, P1, P4, N0, + ST_TET, COLOR1, EA, EB, P1, N0, + ST_PYR, COLOR1, P1, EB, EI, P5, N0, + ST_TET, COLOR1, P5, EI, EF, N0, + // Case #51: (cloned #29) + ST_TET, COLOR0, P2, EB, EC, EI, + ST_TET, COLOR0, ED, EF, P3, EG, + ST_WDG, COLOR1, P0, P4, P5, EG, ED, EF, + ST_TET, COLOR1, P0, P4, P5, P1, + ST_WDG, COLOR1, P0, P5, P1, EC, EI, EB, + // Case #52: (cloned #11) + ST_PNT, 0, NOCOLOR, 5, ED, EF, EC, EB, EH, + ST_PYR, COLOR0, P1, P0, EC, EB, N0, + ST_TET, COLOR0, EB, EH, P1, N0, + ST_PYR, COLOR0, EF, EC, P0, P3, N0, + ST_PYR, COLOR0, ED, P3, P1, EH, N0, + ST_TET, COLOR0, P1, P3, P0, N0, + ST_TET, COLOR0, P3, ED, EF, N0, + ST_TET, COLOR1, P5, P4, P2, N0, + ST_PYR, COLOR1, EF, ED, P4, P5, N0, + ST_PYR, COLOR1, EF, P5, P2, EC, N0, + ST_TET, COLOR1, EC, P2, EB, N0, + ST_PYR, COLOR1, P2, P4, EH, EB, N0, + ST_TET, COLOR1, P4, ED, EH, N0, + // Case #53: (cloned #29) + ST_TET, COLOR0, P3, ED, EF, EG, + ST_TET, COLOR0, EB, P1, EA, EH, + ST_WDG, COLOR1, EH, EB, EA, P4, P2, P0, + ST_TET, COLOR1, P4, P0, P2, P5, + ST_WDG, COLOR1, ED, EG, EF, P4, P0, P5, + // Case #54: (cloned #27) + ST_WDG, COLOR0, ED, P3, EF, EA, P0, EC, + ST_HEX, COLOR1, P4, P5, EF, ED, P1, P2, EC, EA, + // Case #55: (cloned #31) + ST_TET, COLOR0, ED, EG, EF, P3, + ST_WDG, COLOR1, EG, EF, ED, P0, P5, P4, + ST_PYR, COLOR1, P1, P2, P5, P4, P0, + // Case #56: (cloned #7) + ST_WDG, COLOR0, P0, P1, P2, EG, EH, EI, + ST_WDG, COLOR1, EG, EH, EI, P3, P4, P5, + // Case #57: (cloned #15) + ST_PNT, 0, COLOR1, 7, P4, P5, P0, EC, EA, EH, EI, + ST_WDG, COLOR0, EC, P2, EI, EA, P1, EH, + ST_TET, COLOR1, P3, P4, P5, P0, + ST_PYR, COLOR1, EC, EI, EH, EA, N0, + ST_PYR, COLOR1, EI, P5, P4, EH, N0, + ST_TET, COLOR1, P0, P4, P5, N0, + ST_TET, COLOR1, P0, EC, EA, N0, + ST_PYR, COLOR1, P0, EA, EH, P4, N0, + ST_PYR, COLOR1, P5, EI, EC, P0, N0, + // Case #58: (cloned #15) + ST_PNT, 0, COLOR1, 7, P5, P3, P1, EA, EB, EI, EG, + ST_WDG, COLOR0, EA, P0, EG, EB, P2, EI, + ST_TET, COLOR1, P4, P5, P3, P1, + ST_PYR, COLOR1, EA, EG, EI, EB, N0, + ST_PYR, COLOR1, EG, P3, P5, EI, N0, + ST_TET, COLOR1, P1, P5, P3, N0, + ST_TET, COLOR1, P1, EA, EB, N0, + ST_PYR, COLOR1, P1, EB, EI, P5, N0, + ST_PYR, COLOR1, P3, EG, EA, P1, N0, + // Case #59: (cloned #31) + ST_TET, COLOR0, EC, EB, EI, P2, + ST_WDG, COLOR1, P5, P1, P0, EI, EB, EC, + ST_PYR, COLOR1, P3, P0, P1, P4, P5, + // Case #60: (cloned #15) + ST_PNT, 0, COLOR1, 7, P3, P4, P2, EB, EC, EG, EH, + ST_WDG, COLOR0, EB, P1, EH, EC, P0, EG, + ST_TET, COLOR1, P5, P3, P4, P2, + ST_PYR, COLOR1, EB, EH, EG, EC, N0, + ST_PYR, COLOR1, EH, P4, P3, EG, N0, + ST_TET, COLOR1, P2, P3, P4, N0, + ST_TET, COLOR1, P2, EB, EC, N0, + ST_PYR, COLOR1, P2, EC, EG, P3, N0, + ST_PYR, COLOR1, P4, EH, EB, P2, N0, + // Case #61: (cloned #31) + ST_TET, COLOR0, EB, EA, EH, P1, + ST_WDG, COLOR1, P4, P0, P2, EH, EA, EB, + ST_PYR, COLOR1, P5, P2, P0, P3, P4, + // Case #62: (cloned #31) + ST_TET, COLOR0, EA, EC, EG, P0, + ST_WDG, COLOR1, P3, P2, P1, EG, EC, EA, + ST_PYR, COLOR1, P4, P1, P2, P5, P3, + // Case #63: Unique case #13 + ST_WDG, COLOR1, P0, P1, P2, P3, P4, P5, + // Dummy + 0 +}; + +//--------------------------------------------------------------------------- +// Axom modifications +const size_t clipShapesWdgSize = sizeof(clipShapesWdg) / sizeof(unsigned char); +} // namespace visit +} // namespace clipping +} // namespace mir +} // namespace axom +// clang-format on +//--------------------------------------------------------------------------- diff --git a/src/axom/mir/clipping/ClipField.hpp b/src/axom/mir/clipping/ClipField.hpp new file mode 100644 index 0000000000..c1bc1999f2 --- /dev/null +++ b/src/axom/mir/clipping/ClipField.hpp @@ -0,0 +1,2411 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for internal. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#ifndef AXOM_MIR_CLIP_FIELD_HPP_ +#define AXOM_MIR_CLIP_FIELD_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/clipping/BlendGroupBuilder.hpp" +#include "axom/mir/clipping/ClipCases.h" +#include "axom/mir/clipping/ClipOptions.hpp" +#include "axom/mir/clipping/ClipTableManager.hpp" +#include "axom/mir/utilities/blueprint_utilities.hpp" +#include "axom/mir/utilities/CoordsetBlender.hpp" +#include "axom/mir/utilities/FieldBlender.hpp" +#include "axom/mir/utilities/FieldSlicer.hpp" +#include "axom/mir/utilities/SelectedZones.hpp" +#include "axom/mir/utilities/utilities.hpp" +#include "axom/mir/views/Shapes.hpp" +#include "axom/mir/views/view_traits.hpp" +#include "axom/slic.hpp" + +#include +#include +#include +#include + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +#include +#include + +// Enable code to save some debugging output files. +// #define AXOM_DEBUG_CLIP_FIELD + +// Filter out degenerate zones in 2D. +#define AXOM_CLIP_FILTER_DEGENERATES + +// Enable code to reduce the number of blend groups by NOT emitting 1-node +// blend groups and instead using a node lookup field for those. +#define AXOM_REDUCE_BLEND_GROUPS + +namespace axom +{ +namespace mir +{ +namespace clipping +{ +namespace internal +{ +/*! + * \brief Given an "ST_index" (e.g. ST_TET from clipping definitions), return an appropriate ShapeID value. + * + * \param st_index The value we want to translate into a ShapeID value. + * + * \return The ShapeID value that matches the st_index, or 0 if there is no match. + */ +template +inline AXOM_HOST_DEVICE IntegerType ST_Index_to_ShapeID(IntegerType st_index) +{ + IntegerType shapeID = 0; + switch(st_index) + { + case ST_LIN: + shapeID = views::Line_ShapeID; + break; + case ST_TRI: + shapeID = views::Tri_ShapeID; + break; + case ST_QUA: + shapeID = views::Quad_ShapeID; + break; + case ST_TET: + shapeID = views::Tet_ShapeID; + break; + case ST_PYR: + shapeID = views::Pyramid_ShapeID; + break; + case ST_WDG: + shapeID = views::Wedge_ShapeID; + break; + case ST_HEX: + shapeID = views::Hex_ShapeID; + break; + } + return shapeID; +} + +/*! + * \brief Returns a clip table index for the input shapeId. + * \param shapeId A shapeID (e.g. Tet_ShapeID) + * \return The clip table index for the shape. + */ +AXOM_HOST_DEVICE +inline int getClipTableIndex(int shapeId) +{ + int index = 0; + switch(shapeId) + { + case views::Tri_ShapeID: + index = 0; + break; + case views::Quad_ShapeID: + index = 1; + break; + case views::Tet_ShapeID: + index = 2; + break; + case views::Pyramid_ShapeID: + index = 3; + break; + case views::Wedge_ShapeID: + index = 4; + break; + case views::Hex_ShapeID: + index = 5; + break; + } + return index; +} + +AXOM_HOST_DEVICE +inline bool color0Selected(int selection) +{ + return axom::utilities::bitIsSet(selection, 0); +} + +AXOM_HOST_DEVICE +inline bool color1Selected(int selection) +{ + return axom::utilities::bitIsSet(selection, 1); +} + +AXOM_HOST_DEVICE +inline bool generatedPointIsSelected(unsigned char color, int selection) +{ + return color == NOCOLOR || (color0Selected(selection) && color == COLOR0) || + (color1Selected(selection) && color == COLOR1); +} + +AXOM_HOST_DEVICE +inline bool shapeIsSelected(unsigned char color, int selection) +{ + return (color0Selected(selection) && color == COLOR0) || + (color1Selected(selection) && color == COLOR1); +} + +template +inline AXOM_HOST_DEVICE int unique_count(const IdType *values, int n) +{ + IdType v[MAXSIZE]; + // Start off with one unique element + int nv = 1; + v[0] = values[0]; + // Scan the rest + for(int j = 1; j < n; j++) + { + int fi = -1; + for(int i = 0; i < nv; i++) + { + if(values[j] == v[i]) + { + fi = i; + break; + } + } + if(fi == -1) + { + v[nv++] = values[j]; + } + } + return nv; +} + +//------------------------------------------------------------------------------ +// NOTE - These types were pulled out of ClipField so they could be used in +// some code that was moved out to handle degeneracies using partial +// specialization rather than "if constexpr". Put it all back when +// "if constexpr" is allowed. One nice side-effect is shorter symbol +// names in the debugger. + +using BitSet = std::uint32_t; + +/*! + * \brief Contains data that describes the number and size of zone fragments in the output. + */ +struct FragmentData +{ + IndexType m_finalNumZones {0}; + IndexType m_finalConnSize {0}; + axom::ArrayView m_fragmentsView {}; + axom::ArrayView m_fragmentsSizeView {}; + axom::ArrayView m_fragmentOffsetsView {}; + axom::ArrayView m_fragmentSizeOffsetsView {}; +}; +//------------------------------------------------------------------------------ + +#if defined(AXOM_CLIP_FILTER_DEGENERATES) +/*! + * \brief Replace data in the input Conduit node with a denser version using the mask. + * + * \tparam ExecSpace The execution space. + * \tparam DataView The type of data view that is operated on. + * + * \param n_src The Conduit node that contains the data. + * \param srcView A view that wraps the input Conduit data. + * \param newSize The new array size. + * \param maskView The mask for valid data elements. + * \param maskOffsetsView The offsets view to indicate where to write the new data. + */ +template +DataView filter(conduit::Node &n_src, + DataView srcView, + axom::IndexType newSize, + axom::ArrayView maskView, + axom::ArrayView maskOffsetsView) +{ + using value_type = typename DataView::value_type; + namespace bputils = axom::mir::utilities::blueprint; + + // Get the ID of a Conduit allocator that will allocate through Axom with device allocator allocatorID. + utilities::blueprint::ConduitAllocateThroughAxom c2a; + const int conduitAllocatorID = c2a.getConduitAllocatorID(); + + conduit::Node n_values; + n_values.set_allocator(conduitAllocatorID); + n_values.set(conduit::DataType(bputils::cpp2conduit::id, newSize)); + auto valuesView = bputils::make_array_view(n_values); + const auto nValues = maskView.size(); + axom::for_all( + nValues, + AXOM_LAMBDA(axom::IndexType index) { + if(maskView[index] > 0) + { + const auto destIndex = maskOffsetsView[index]; + valuesView[destIndex] = srcView[index]; + } + }); + + n_src.swap(n_values); + return bputils::make_array_view(n_src); +} + +/// NOTE - Use partial specialization (instead of the cleaner "if constexpr") +/// for now to implement some 2D-specific behavior. + +/*! + * \brief Base template for degenerate removal. + */ +template +struct DegenerateHandler +{ + /*! + * \brief Set the size for the current fragment. + * + * \param fragmentsView The number of fragments for the szIndex zone. + * \param connView The new connectivity. + * \param sizesView The new mesh sizes. + * \param szIndex The zone index currently being processed. + * \param nidsThisFragment The number of node ids in the current fragment. + * \param sizeIndex The write index for the sizes to output. + * \param outputIndex The write index for the offsets to output. + * + * \return True if the fragment is degenerate, false otherwise. + */ + AXOM_HOST_DEVICE + static bool setSize(axom::ArrayView AXOM_UNUSED_PARAM(fragmentsView), + axom::ArrayView AXOM_UNUSED_PARAM(connView), + axom::ArrayView sizesView, + axom::IndexType AXOM_UNUSED_PARAM(szIndex), + int nIdsThisFragment, + int sizeIndex, + int &AXOM_UNUSED_PARAM(outputIndex)) + { + sizesView[sizeIndex] = nIdsThisFragment; + return false; + } + + /*! + * \brief In a previous stage, degenerate shapes were marked as having zero size. + * This method filters them out from the auxiliary arrays. + * + * \param fragmentData The fragments. + * \param[inout] n_sizes The node that contains the sizes. + * \param[inout] n_offsets The node that contains the offsets. + * \param[inout] n_shapes The node that contains the shapes. + * \param[inout] n_color The node that contains the color. + * \param[inout] sizesView The view that wraps sizes (can change on output). + * \param[inout] offsetsView The view that wraps offsets (can change on output). + * \param[inout] shapesView The view that wraps shapes (can change on output). + * \param[inout] colorView The view that wraps colors (can change on output). + */ + static void filterZeroSizes( + FragmentData &AXOM_UNUSED_PARAM(fragmentData), + conduit::Node &AXOM_UNUSED_PARAM(n_sizes), + conduit::Node &AXOM_UNUSED_PARAM(n_offsets), + conduit::Node &AXOM_UNUSED_PARAM(n_shapes), + conduit::Node &AXOM_UNUSED_PARAM(n_color), + axom::ArrayView &AXOM_UNUSED_PARAM(sizesView), + axom::ArrayView &AXOM_UNUSED_PARAM(offsetsView), + axom::ArrayView &AXOM_UNUSED_PARAM(shapesView), + axom::ArrayView &AXOM_UNUSED_PARAM(colorView)) + { } + + /*! + * \brief Turns degenerate quads into triangles in-place. + * + * \param shapesUsed A BitSet that indicates which shapes are present in the mesh. + * \param connView A view that contains the connectivity. + * \param sizesView A view that contains the sizes. + * \param offsetsView A view that contains the offsets. + * \param shapesView A view that contains the shapes. + */ + static BitSet quadtri( + BitSet shapesUsed, + axom::ArrayView AXOM_UNUSED_PARAM(connView), + axom::ArrayView AXOM_UNUSED_PARAM(sizesView), + axom::ArrayView AXOM_UNUSED_PARAM(offsetsView), + axom::ArrayView AXOM_UNUSED_PARAM(shapesView)) + { + return shapesUsed; + } +}; + +/*! + * \brief Partial specialization that implements some degeneracy handling for 2D meshes. + */ +template +struct DegenerateHandler<2, ExecSpace, ConnectivityType> +{ + using reduce_policy = typename axom::execution_space::reduce_policy; + + /*! + * \brief Set the size for the current fragment. + * + * \param fragmentsView The number of fragments for the szIndex zone. + * \param connView The new connectivity. + * \param sizesView The new mesh sizes. + * \param szIndex The zone index currently being processed. + * \param nidsThisFragment The number of node ids in the current fragment. + * \param sizeIndex The write index for the sizes to output. + * \param outputIndex The write index for the offsets to output. + * + * \return True if the fragment is degenerate, false otherwise. + */ + AXOM_HOST_DEVICE + static bool setSize(axom::ArrayView fragmentsView, + axom::ArrayView connView, + axom::ArrayView sizesView, + axom::IndexType szIndex, + int nIdsThisFragment, + int sizeIndex, + int &outputIndex) + { + const int connStart = outputIndex - nIdsThisFragment; + + // Check for degenerate + const int nUniqueIds = + internal::unique_count(connView.data() + connStart, + nIdsThisFragment); + const bool thisFragmentDegenerate = nUniqueIds < (nIdsThisFragment - 1); + + // Rewind the outputIndex so we don't emit it in the connectivity. + if(thisFragmentDegenerate) + { + outputIndex = connStart; + + // There is one less fragment than we're expecting in the output. + fragmentsView[szIndex] -= 1; + } + + // Mark empty size. + sizesView[sizeIndex] = thisFragmentDegenerate ? 0 : nIdsThisFragment; + + return thisFragmentDegenerate; + } + + /*! + * \brief In a previous stage, degenerate shapes were marked as having zero size. + * This method filters them out from the auxiliary arrays. + * + * \param fragmentData The fragments. + * \param[inout] n_sizes The node that contains the sizes. + * \param[inout] n_offsets The node that contains the offsets. + * \param[inout] n_shapes The node that contains the shapes. + * \param[inout] n_color The node that contains the color. + * \param[inout] sizesView The view that wraps sizes (can change on output). + * \param[inout] offsetsView The view that wraps offsets (can change on output). + * \param[inout] shapesView The view that wraps shapes (can change on output). + * \param[inout] colorView The view that wraps colors (can change on output). + */ + static void filterZeroSizes(FragmentData &fragmentData, + conduit::Node &n_sizes, + conduit::Node &n_offsets, + conduit::Node &n_shapes, + conduit::Node &n_color, + axom::ArrayView &sizesView, + axom::ArrayView &offsetsView, + axom::ArrayView &shapesView, + axom::ArrayView &colorView) + { + AXOM_ANNOTATE_SCOPE("filterZeroSizes"); + + // There were degenerates so the expected number of fragments per zone (m_fragmentsView) + // was adjusted down. That means redoing the offsets. These need to be up + // to date to handle zonal fields later. + axom::exclusive_scan(fragmentData.m_fragmentsView, + fragmentData.m_fragmentOffsetsView); + + // Use sizesView to make a mask that has 1's where size > 0. + axom::IndexType nz = fragmentData.m_finalNumZones; + axom::Array mask(nz, nz, axom::execution_space::allocatorID()); + axom::Array maskOffsets(nz, + nz, + axom::execution_space::allocatorID()); + auto maskView = mask.view(); + auto maskOffsetsView = maskOffsets.view(); + RAJA::ReduceSum mask_reduce(0); + const axom::ArrayView deviceSizesView = sizesView; + axom::for_all( + nz, + AXOM_LAMBDA(axom::IndexType index) { + const int ival = (deviceSizesView[index] > 0) ? 1 : 0; + maskView[index] = ival; + mask_reduce += ival; + }); + const axom::IndexType filteredZoneCount = mask_reduce.get(); + + // Make offsets + axom::exclusive_scan(maskView, maskOffsetsView); + + // Filter sizes, shapes, color using the mask + sizesView = + filter>(n_sizes, + sizesView, + filteredZoneCount, + maskView, + maskOffsetsView); + offsetsView = + filter>(n_offsets, + offsetsView, + filteredZoneCount, + maskView, + maskOffsetsView); + shapesView = + filter>(n_shapes, + shapesView, + filteredZoneCount, + maskView, + maskOffsetsView); + colorView = filter>(n_color, + colorView, + filteredZoneCount, + maskView, + maskOffsetsView); + + // Record the filtered size. + fragmentData.m_finalNumZones = filteredZoneCount; + } + + /*! + * \brief Turns degenerate quads into triangles in-place. + * + * \param shapesUsed A BitSet that indicates which shapes are present in the mesh. + * \param connView A view that contains the connectivity. + * \param sizesView A view that contains the sizes. + * \param offsetsView A view that contains the offsets. + * \param shapesView A view that contains the shapes. + */ + static BitSet quadtri(BitSet shapesUsed, + axom::ArrayView connView, + axom::ArrayView sizesView, + axom::ArrayView offsetsView, + axom::ArrayView shapesView) + { + if(axom::utilities::bitIsSet(shapesUsed, views::Quad_ShapeID)) + { + AXOM_ANNOTATE_SCOPE("quadtri"); + const axom::IndexType numOutputZones = shapesView.size(); + RAJA::ReduceBitOr shapesUsed_reduce(0); + axom::for_all( + numOutputZones, + AXOM_LAMBDA(axom::IndexType index) { + if(shapesView[index] == views::Quad_ShapeID) + { + const auto offset = offsetsView[index]; + ConnectivityType pts[4]; + int npts = 0; + for(int current = 0; current < 4; current++) + { + int next = (current + 1) % 4; + ConnectivityType curNode = connView[offset + current]; + ConnectivityType nextNode = connView[offset + next]; + if(curNode != nextNode) + { + pts[npts++] = curNode; + } + } + + if(npts == 3) + { + shapesView[index] = views::Tri_ShapeID; + sizesView[index] = 3; + connView[offset] = pts[0]; + connView[offset + 1] = pts[1]; + connView[offset + 2] = pts[2]; + // Repeat the last point (it won't be used though). + connView[offset + 3] = pts[2]; + } + } + + BitSet shapeBit {}; + axom::utilities::setBitOn(shapeBit, shapesView[index]); + shapesUsed_reduce |= shapeBit; + }); + // We redid shapesUsed reduction in case triangles appeared. + shapesUsed = shapesUsed_reduce.get(); + } + return shapesUsed; + } +}; +#endif + +/*! + * \brief Base template for handling fields on a strided-structured mesh. The + * default is that the mesh is not strided-structured so do nothing. + * + * \tparam enabled Whether the mesh is strided-structured. + * \tparam ExecSpace The execution space. + * \tparam TopologyView The topology view type. + * + * \note This was extracted from ClipField to remove some "if constexpr". Put + * it back someday. + */ +template +struct StridedStructuredFields +{ + /*! + * \brief Slice an element field. + * + * \param topologyView The topology view. + * \param slice Slice data. + * \param n_field The field being sliced. + * \param n_newField The node that will contain the new field. + */ + static bool sliceElementField( + const TopologyView &AXOM_UNUSED_PARAM(topologyView), + const axom::mir::utilities::blueprint::SliceData &AXOM_UNUSED_PARAM(slice), + const conduit::Node &AXOM_UNUSED_PARAM(n_field), + conduit::Node &AXOM_UNUSED_PARAM(n_newField)) + { + return false; + } + + /*! + * \brief Blend a vertex field. + * + * \param topologyView The topology view. + * \param blend Blend data. + * \param n_field The field being sliced. + * \param n_newField The node that will contain the new field. + */ + static bool blendVertexField( + const TopologyView &AXOM_UNUSED_PARAM(topologyView), + const axom::mir::utilities::blueprint::BlendData &AXOM_UNUSED_PARAM(blend), + const conduit::Node &AXOM_UNUSED_PARAM(n_field), + conduit::Node &AXOM_UNUSED_PARAM(n_newField)) + { + return false; + } +}; + +/*! + * \brief Partial specialization to handle fields on strided-structured mesh. + * This is the strided-structured case. + * + * \tparam ExecSpace The execution space. + * \tparam TopologyView The topology view type. + * + * \note This was extracted from ClipField to remove some "if constexpr". Put + * it back someday. + */ +template +struct StridedStructuredFields +{ + /*! + * \brief Slice an element field if the field is strided-structured. + * + * \param topologyView The topology view. + * \param slice Slice data. + * \param n_field The field being sliced. + * \param n_newField The node that will contain the new field. + */ + static bool sliceElementField( + const TopologyView &topologyView, + const axom::mir::utilities::blueprint::SliceData &slice, + const conduit::Node &n_field, + conduit::Node &n_newField) + { + bool handled = false; + if(n_field.has_path("offsets") && n_field.has_path("strides")) + { + using Indexing = typename TopologyView::IndexingPolicy; + using IndexingPolicy = + axom::mir::utilities::blueprint::SSElementFieldIndexing; + IndexingPolicy indexing; + indexing.m_indexing = topologyView.indexing(); + indexing.update(n_field); + + axom::mir::utilities::blueprint::FieldSlicer s( + indexing); + s.execute(slice, n_field, n_newField); + handled = true; + } + return handled; + } + + /*! + * \brief Blend a vertex field if the field is strided-structured. + * + * \param topologyView The topology view. + * \param blend Blend data. + * \param n_field The field being sliced. + * \param n_newField The node that will contain the new field. + */ + static bool blendVertexField(const TopologyView &topologyView, + const axom::mir::utilities::blueprint::BlendData &blend, + const conduit::Node &n_field, + conduit::Node &n_newField) + { + bool handled = false; + if(n_field.has_path("offsets") && n_field.has_path("strides")) + { + // Make node indexing that the field blender can use. + using Indexing = typename TopologyView::IndexingPolicy; + using IndexingPolicy = + axom::mir::utilities::blueprint::SSVertexFieldIndexing; + IndexingPolicy indexing; + indexing.m_topoIndexing = topologyView.indexing().expand(); + indexing.m_fieldIndexing = topologyView.indexing().expand(); + indexing.update(n_field); + + // If the topo and field offsets/strides are different then we need to go through + // SSVertexFieldIndexing. Otherwise, we can let the normal case further below + // handle the field. + if(indexing.m_topoIndexing.m_offsets != indexing.m_fieldIndexing.m_offsets || + indexing.m_topoIndexing.m_strides != indexing.m_fieldIndexing.m_strides) + { + // Blend the field. + axom::mir::utilities::blueprint::FieldBlender< + ExecSpace, + axom::mir::utilities::blueprint::SelectSubsetPolicy, + IndexingPolicy> + b(indexing); + b.execute(blend, n_field, n_newField); + handled = true; + } + } + return handled; + } +}; + +#if defined(AXOM_DEBUG_CLIP_FIELD) +/*! + * \brief Print device views to std::out after moving data to the host. + * + * \param name The name of the view. + * \param view The view to print. + */ +template +void printHost(const std::string &name, const ViewType &deviceView) +{ + using value_type = typename ViewType::value_type; + int nn = deviceView.size(); + // Move data to host into temp array. + value_type *host = new value_type[nn]; + axom::copy(host, deviceView.data(), sizeof(value_type) * nn); + // Print + std::cout << name << "[" << nn << "] = {"; + for(int ii = 0; ii < nn; ii++) + { + if(ii > 0) + { + std::cout << ", "; + } + std::cout << host[ii]; + } + std::cout << "}" << std::endl; + // Cleanup. + delete[] host; +} +#endif + +} // end namespace internal + +//------------------------------------------------------------------------------ +/*! + * \brief This class helps ClipField determine intersection cases and weights + * using a field designated by the options. + */ +template +class FieldIntersector +{ +public: + using ClipFieldType = float; + using ConnectivityView = axom::ArrayView; + + /*! + * \brief This is a view class for FieldIntersector that can be used in device code. + */ + struct View + { + /*! + * \brief Given a zone index and the node ids that comprise the zone, return + * the appropriate clip case, taking into account the clip field and + * clip value. + * + * \param zoneIndex The zone index. + * \param nodeIds A view containing node ids for the zone. + */ + AXOM_HOST_DEVICE + axom::IndexType determineClipCase(axom::IndexType AXOM_UNUSED_PARAM(zoneIndex), + const ConnectivityView &nodeIds) const + { + axom::IndexType clipcase = 0; + for(IndexType i = 0; i < nodeIds.size(); i++) + { + const auto id = nodeIds[i]; + const auto value = m_clipFieldView[id] - m_clipValue; + clipcase |= (value > 0) ? (1 << i) : 0; + } + return clipcase; + } + + /*! + * \brief Compute the weight of a clip value along an edge (id0, id1) using the clip field and value. + * + * \param id0 The mesh node at the start of the edge. + * \param id1 The mesh node at the end of the edge. + * + * \return A parametric position t [0,1] where we locate \a clipValues in [d0,d1]. + */ + AXOM_HOST_DEVICE + ClipFieldType computeWeight(axom::IndexType AXOM_UNUSED_PARAM(zoneIndex), + ConnectivityType id0, + ConnectivityType id1) const + { + const ClipFieldType d0 = m_clipFieldView[id0]; + const ClipFieldType d1 = m_clipFieldView[id1]; + constexpr ClipFieldType tiny = 1.e-09; + return axom::utilities::clampVal(axom::utilities::abs(m_clipValue - d0) / + (axom::utilities::abs(d1 - d0) + tiny), + ClipFieldType(0), + ClipFieldType(1)); + } + + axom::ArrayView m_clipFieldView {}; + ClipFieldType m_clipValue {}; + }; + + /*! + * \brief Initialize the object from options. + * \param n_options The node that contains the options. + * \param n_fields The node that contains fields. + */ + void initialize(const conduit::Node &n_options, const conduit::Node &n_fields) + { + namespace bputils = axom::mir::utilities::blueprint; + const int allocatorID = axom::execution_space::allocatorID(); + + // Get the clip field and value. + ClipOptions opts(n_options); + std::string clipFieldName = opts.clipField(); + m_view.m_clipValue = opts.clipValue(); + + // Make sure the clipField is the right data type and store access to it in the view. + const conduit::Node &n_clip_field = n_fields.fetch_existing(opts.clipField()); + const conduit::Node &n_clip_field_values = n_clip_field["values"]; + SLIC_ASSERT(n_clip_field["association"].as_string() == "vertex"); + if(n_clip_field_values.dtype().id() == bputils::cpp2conduit::id) + { + // Make a view. + m_view.m_clipFieldView = + bputils::make_array_view(n_clip_field_values); + } + else + { + // Convert to ClipFieldType. + const IndexType n = + static_cast(n_clip_field_values.dtype().number_of_elements()); + m_clipFieldData = axom::Array(n, n, allocatorID); + m_view.m_clipFieldView = m_clipFieldData.view(); + views::Node_to_ArrayView(n_clip_field_values, [&](auto clipFieldViewSrc) { + copyValues(clipFieldViewSrc); + }); + } + } + + /*! + * \brief Determine the name of the topology on which to operate. + * \param n_input The input mesh node. + * \param n_options The clipping options. + * \return The name of the toplogy on which to operate. + */ + std::string getTopologyName(const conduit::Node &n_input, + const conduit::Node &n_options) const + { + // Get the clipField's topo name. + ClipOptions opts(n_options); + const conduit::Node &n_fields = n_input.fetch_existing("fields"); + const conduit::Node &n_clipField = n_fields.fetch_existing(opts.clipField()); + return n_clipField["topology"].as_string(); + } + + /*! + * \brief Return a new instance of the view. + * \return A new instance of the view. + */ + View view() const { return m_view; } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + /*! + * \brief Copy values from srcView into m_clipFieldData. + * + * \param srcView The source data view. + */ + template + void copyValues(DataView srcView) + { + auto clipFieldView = m_clipFieldData.view(); + axom::for_all( + srcView.size(), + AXOM_LAMBDA(axom::IndexType index) { + clipFieldView[index] = static_cast(srcView[index]); + }); + } + + axom::Array m_clipFieldData {}; + View m_view {}; +}; + +//------------------------------------------------------------------------------ +/*! + * \accelerated + * \brief This class clips a topology using a field and puts the new topology into a new Conduit node. + * + * \tparam ExecSpace The execution space where the compute-heavy kernels run. + * \tparam TopologyView The topology view that can operate on the Blueprint topology. + * \tparam CoordsetView The coordset view that can operate on the Blueprint coordset. + * \tparam IntersectPolicy The intersector policy that can helps with cases and weights. + * \tparam NamingPolicy The policy for making names from arrays of ids. + */ +template , + typename NamingPolicy = axom::mir::utilities::HashNaming> +class ClipField +{ +public: + using BlendData = axom::mir::utilities::blueprint::BlendData; + using SliceData = axom::mir::utilities::blueprint::SliceData; + using ClipTableViews = axom::StackArray; + using Intersector = IntersectPolicy; + + using BitSet = internal::BitSet; + using KeyType = typename NamingPolicy::KeyType; + using loop_policy = typename axom::execution_space::loop_policy; + using reduce_policy = typename axom::execution_space::reduce_policy; + using ConnectivityType = typename TopologyView::ConnectivityType; + using BlendGroupBuilderType = + BlendGroupBuilder; + using ClipFieldType = float; + using ZoneType = typename TopologyView::ShapeType; + + /*! + * \brief Constructor + * + * \param topoView A topology view suitable for the supplied topology. + * \param coordsetView A coordset view suitable for the supplied coordset. + * + */ + ClipField(const TopologyView &topoView, + const CoordsetView &coordsetView, + const Intersector &intersector = Intersector()) + : m_topologyView(topoView) + , m_coordsetView(coordsetView) + , m_intersector(intersector) + , m_clipTables() + , m_naming() + { } + + /*! + * \brief Allow the user to pass in a NamingPolicy to use when making blend group names. + * + * \param naming A new naming policy object. + */ + void setNamingPolicy(NamingPolicy &naming) { m_naming = naming; } + + /*! + * \brief Execute the clipping operation using the data stored in the specified \a clipField. + * + * \param[in] n_input The Conduit node that contains the topology, coordsets, and fields. + * \param[in] n_options A Conduit node that contains clipping options. + * \param[out] n_output A Conduit node that will hold the clipped output mesh. This should be a different node from \a n_input. + * + * \note The clipField field must currently be vertex-associated. + */ + void execute(const conduit::Node &n_input, + const conduit::Node &n_options, + conduit::Node &n_output) + { + // Get the topo/coordset names in the input. + ClipOptions opts(n_options); + const std::string topoName = + m_intersector.getTopologyName(n_input, n_options); + const conduit::Node &n_topo = + n_input.fetch_existing("topologies/" + topoName); + const std::string coordsetName = n_topo["coordset"].as_string(); + const conduit::Node &n_coordset = + n_input.fetch_existing("coordsets/" + coordsetName); + const conduit::Node &n_fields = n_input.fetch_existing("fields"); + + conduit::Node &n_newTopo = + n_output["topologies/" + opts.topologyName(topoName)]; + conduit::Node &n_newCoordset = + n_output["coordsets/" + opts.coordsetName(coordsetName)]; + conduit::Node &n_newFields = n_output["fields"]; + + execute(n_topo, + n_coordset, + n_fields, + n_options, + n_newTopo, + n_newCoordset, + n_newFields); + } + + /*! + * \brief Execute the clipping operation using the data stored in the specified \a clipField. + * + * \param[in] n_topo The node that contains the input mesh topology. + * \param[in] n_coordset The node that contains the input mesh coordset. + * \param[in] n_fields The node that contains the input fields. + * \param[in] n_options A Conduit node that contains clipping options. + * \param[out] n_newTopo A node that will contain the new clipped topology. + * \param[out] n_newCoordset A node that will contain the new coordset for the clipped topology. + * \param[out] n_newFields A node that will contain the new fields for the clipped topology. + * + * \note The clipField field must currently be vertex-associated. Also, the output topology will be an unstructured topology with mixed shape types. + */ + void execute(const conduit::Node &n_topo, + const conduit::Node &n_coordset, + const conduit::Node &n_fields, + const conduit::Node &n_options, + conduit::Node &n_newTopo, + conduit::Node &n_newCoordset, + conduit::Node &n_newFields) + { + namespace bputils = axom::mir::utilities::blueprint; + const auto allocatorID = axom::execution_space::allocatorID(); + AXOM_ANNOTATE_SCOPE("ClipField"); + + const std::string newTopologyName = n_newTopo.name(); + // Reset the output nodes just in case they've been reused. + n_newTopo = conduit::Node(); + n_newCoordset = conduit::Node(); + n_newFields = conduit::Node(); + + // Make the selected zones and get the size. + ClipOptions opts(n_options); + axom::mir::SelectedZones selectedZones( + m_topologyView.numberOfZones(), + n_options); + const auto nzones = selectedZones.view().size(); + + // Give the intersector a chance to further initialize. + { + AXOM_ANNOTATE_SCOPE("Initialize intersector"); + m_intersector.initialize(n_options, n_fields); + } + + // Load clip table data and make views. + m_clipTables.load(m_topologyView.dimension()); + ClipTableViews clipTableViews; + createClipTableViews(clipTableViews, m_topologyView.dimension()); + + // Allocate some memory and store views in ZoneData, FragmentData. + AXOM_ANNOTATE_BEGIN("allocation"); + axom::Array clipCases(nzones, + nzones, + allocatorID); // The clip case for a zone. + axom::Array pointsUsed( + nzones, + nzones, + allocatorID); // Which points are used over all selected fragments in a zone + ZoneData zoneData; + zoneData.m_clipCasesView = clipCases.view(); + zoneData.m_pointsUsedView = pointsUsed.view(); + + // Allocate some memory and store views in NodeData. + NodeData nodeData; +#if defined(AXOM_REDUCE_BLEND_GROUPS) + const auto nnodes = m_coordsetView.numberOfNodes(); + axom::Array nodeUsed(nnodes, nnodes, allocatorID); + nodeData.m_nodeUsedView = nodeUsed.view(); +#endif + + // Allocate some memory and store views in FragmentData. + axom::Array fragments( + nzones, + nzones, + allocatorID); // The number of fragments (child zones) produced for a zone. + axom::Array fragmentsSize( + nzones, + nzones, + allocatorID); // The connectivity size for all selected fragments in a zone. + axom::Array fragmentOffsets(nzones, nzones, allocatorID); + axom::Array fragmentSizeOffsets(nzones, nzones, allocatorID); + + FragmentData fragmentData; + fragmentData.m_fragmentsView = fragments.view(); + fragmentData.m_fragmentsSizeView = fragmentsSize.view(); + fragmentData.m_fragmentOffsetsView = fragmentOffsets.view(); + fragmentData.m_fragmentSizeOffsetsView = fragmentSizeOffsets.view(); + + axom::Array blendGroups( + nzones, + nzones, + allocatorID); // Number of blend groups in a zone. + axom::Array blendGroupsLen( + nzones, + nzones, + allocatorID); // Length of the blend groups in a zone. + axom::Array blendOffset( + nzones, + nzones, + allocatorID); // Start of zone's blend group indices + axom::Array blendGroupOffsets( + nzones, + nzones, + allocatorID); // Start of zone's blend group offsets in definitions. + AXOM_ANNOTATE_END("allocation"); + + // Make sure the naming policy knows the number of nodes. + m_naming.setMaxId(m_coordsetView.numberOfNodes()); + + // Make an object to help manage building the blend groups. + BlendGroupBuilderType builder; + builder.setNamingPolicy(m_naming.view()); + builder.setBlendGroupSizes(blendGroups.view(), blendGroupsLen.view()); + + // Compute sizes and offsets + computeSizes(clipTableViews, + builder, + zoneData, + nodeData, + fragmentData, + opts, + selectedZones); + computeFragmentSizes(fragmentData, selectedZones); + computeFragmentOffsets(fragmentData); + + // Compute original node count that we're preserving, make node maps. +#if defined(AXOM_REDUCE_BLEND_GROUPS) + const int compactSize = countOriginalNodes(nodeData); + axom::Array compactNodes(compactSize, compactSize, allocatorID); + axom::Array oldNodeToNewNode(nnodes, nnodes, allocatorID); + nodeData.m_originalIdsView = compactNodes.view(); + nodeData.m_oldNodeToNewNodeView = oldNodeToNewNode.view(); + createNodeMaps(nodeData); + + nodeUsed.clear(); + nodeData.m_nodeUsedView = axom::ArrayView(); +#endif + + // Further initialize the blend group builder. + IndexType blendGroupsSize = 0, blendGroupLenSize = 0; + builder.computeBlendGroupSizes(blendGroupsSize, blendGroupLenSize); + builder.setBlendGroupOffsets(blendOffset.view(), blendGroupOffsets.view()); + builder.computeBlendGroupOffsets(); + + // Allocate memory for blend groups. + AXOM_ANNOTATE_BEGIN("allocation2"); + axom::Array blendNames(blendGroupsSize, blendGroupsSize, allocatorID); + axom::Array blendGroupSizes(blendGroupsSize, + blendGroupsSize, + allocatorID); + axom::Array blendGroupStart(blendGroupsSize, + blendGroupsSize, + allocatorID); + axom::Array blendIds(blendGroupLenSize, + blendGroupLenSize, + allocatorID); + axom::Array blendCoeff(blendGroupLenSize, + blendGroupLenSize, + allocatorID); + + // Make the blend groups. + builder.setBlendViews(blendNames.view(), + blendGroupSizes.view(), + blendGroupStart.view(), + blendIds.view(), + blendCoeff.view()); + AXOM_ANNOTATE_END("allocation2"); + makeBlendGroups(clipTableViews, builder, zoneData, opts, selectedZones); + + // Make the blend groups unique + axom::Array uNames; + axom::Array uIndices; +#if defined(AXOM_REDUCE_BLEND_GROUPS) + axom::Array newUniqueNames; + axom::Array newUniqueIndices; +#endif + { + AXOM_ANNOTATE_SCOPE("unique"); + axom::mir::utilities::Unique::execute( + builder.blendNames(), + uNames, + uIndices); + builder.setUniqueNames(uNames.view(), uIndices.view()); + +#if defined(AXOM_REDUCE_BLEND_GROUPS) + // Filter the unique names/indices to remove single node blend groups. + builder.filterUnique(newUniqueNames, newUniqueIndices); + uNames.clear(); + uIndices.clear(); +#endif + } + + // Make BlendData. + bputils::BlendData blend = builder.makeBlendData(); + blend.m_originalIdsView = nodeData.m_originalIdsView; + + // Make the clipped mesh + makeTopology(clipTableViews, + builder, + zoneData, + nodeData, + fragmentData, + opts, + selectedZones, + newTopologyName, + n_newTopo, + n_newCoordset, + n_newFields); + + // Make the coordset + makeCoordset(blend, n_coordset, n_newCoordset); + + // Get the fields that we want to process. + std::map fieldsToProcess; + int numElementFields = 0; + if(opts.fields(fieldsToProcess)) + { + // Fields were present in the options. Count the element fields. + for(auto it = fieldsToProcess.begin(); it != fieldsToProcess.end(); it++) + { + const conduit::Node &n_field = n_fields.fetch_existing(it->first); + if(n_field.fetch_existing("topology").as_string() == n_topo.name()) + { + numElementFields += + (n_field.fetch_existing("association").as_string() == "element") ? 1 + : 0; + } + } + } + else + { + // Fields were not present in the options. Select all fields that have the same topology as n_topo. + for(conduit::index_t i = 0; i < n_fields.number_of_children(); i++) + { + const conduit::Node &n_field = n_fields[i]; + if(n_field.fetch_existing("topology").as_string() == n_topo.name()) + { + numElementFields += + (n_field.fetch_existing("association").as_string() == "element") ? 1 + : 0; + + fieldsToProcess[n_field.name()] = n_field.name(); + } + } + } + const std::string newNodes = opts.newNodesField(); + if(!newNodes.empty() && n_fields.has_child(newNodes)) + { + fieldsToProcess[newNodes] = newNodes; + } + + // Make slice indices if we have element fields. + bputils::SliceData slice; + axom::Array sliceIndices; + if(numElementFields > 0) + { + AXOM_ANNOTATE_SCOPE("sliceIndices"); + sliceIndices = axom::Array(fragmentData.m_finalNumZones, + fragmentData.m_finalNumZones, + allocatorID); + auto sliceIndicesView = sliceIndices.view(); + + // Fill in sliceIndicesView. + const auto selectedZonesView = selectedZones.view(); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + const auto zoneIndex = selectedZonesView[index]; + const auto start = fragmentData.m_fragmentOffsetsView[index]; + for(int i = 0; i < fragmentData.m_fragmentsView[index]; i++) + sliceIndicesView[start + i] = zoneIndex; + }); + slice.m_indicesView = sliceIndicesView; + } + + makeFields(blend, slice, newTopologyName, fieldsToProcess, n_fields, n_newFields); + + makeOriginalElements(fragmentData, + opts, + selectedZones, + n_fields, + n_newTopo, + n_newFields); + + markNewNodes(blend, newNodes, newTopologyName, n_newFields); + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + using FragmentData = internal::FragmentData; + + /*! + * \brief Contains some per-zone data that we want to hold onto between methods. + */ + struct ZoneData + { + axom::ArrayView m_clipCasesView {}; + axom::ArrayView m_pointsUsedView {}; + }; + + /*! + * \brief Contains some per-node data that we want to hold onto between methods. + */ + struct NodeData + { + axom::ArrayView m_nodeUsedView {}; + axom::ArrayView m_oldNodeToNewNodeView {}; + axom::ArrayView m_originalIdsView {}; + }; + + /*! + * \brief Make a bitset that indicates the parts of the selection that are selected. + */ + int getSelection(const ClipOptions &opts) const + { + int selection = 0; + if(opts.inside()) axom::utilities::setBitOn(selection, 0); + if(opts.outside()) axom::utilities::setBitOn(selection, 1); + SLIC_ASSERT(selection > 0); + return selection; + } + + /*! + * \brief Create views for the clip tables of various shapes. + * + * \param[out] views The views array that will contain the table views. + * \param dimension The dimension the topology (so we can load a subset of tables) + */ + void createClipTableViews(ClipTableViews &views, int dimension) + { + AXOM_ANNOTATE_SCOPE("createClipTableViews"); + if(dimension == -1 || dimension == 2) + { + views[internal::getClipTableIndex(views::Tri_ShapeID)] = + m_clipTables[ST_TRI].view(); + views[internal::getClipTableIndex(views::Quad_ShapeID)] = + m_clipTables[ST_QUA].view(); + } + if(dimension == -1 || dimension == 3) + { + views[internal::getClipTableIndex(views::Tet_ShapeID)] = + m_clipTables[ST_TET].view(); + views[internal::getClipTableIndex(views::Pyramid_ShapeID)] = + m_clipTables[ST_PYR].view(); + views[internal::getClipTableIndex(views::Wedge_ShapeID)] = + m_clipTables[ST_WDG].view(); + views[internal::getClipTableIndex(views::Hex_ShapeID)] = + m_clipTables[ST_HEX].view(); + } + } + + /*! + * \brief Iterate over zones and their respective fragments to determine sizes + * for fragments and blend groups. + * + * \param[in] clipTableViews An object that holds views of the clipping table data. + * \param[in] builder This object holds views to blend group data and helps with building/access. + * \param[in] zoneData This object holds views to per-zone data. + * \param[in] nodeData This object holds views to per-node data. + * \param[in] fragmentData This object holds views to per-fragment data. + * \param[inout] opts Clipping options. + * + * \note Objects that we need to capture into kernels are passed by value (they only contain views anyway). Data can be modified through the views. + */ + void computeSizes(ClipTableViews clipTableViews, + BlendGroupBuilderType builder, + ZoneData zoneData, + NodeData nodeData, + FragmentData fragmentData, + const ClipOptions &opts, + const SelectedZones &selectedZones) const + { + AXOM_ANNOTATE_SCOPE("computeSizes"); + const auto selection = getSelection(opts); + + auto blendGroupsView = builder.state().m_blendGroupsView; + auto blendGroupsLenView = builder.state().m_blendGroupsLenView; + + // Initialize nodeUsed data for nodes. + axom::for_all( + nodeData.m_nodeUsedView.size(), + AXOM_LAMBDA(axom::IndexType index) { nodeData.m_nodeUsedView[index] = 0; }); + + const auto deviceIntersector = m_intersector.view(); + + const TopologyView deviceTopologyView(m_topologyView); + const auto selectedZonesView = selectedZones.view(); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + + // Get the clip case for the current zone. + const auto clipcase = + deviceIntersector.determineClipCase(zoneIndex, zone.getIds()); + zoneData.m_clipCasesView[szIndex] = clipcase; + + // Iterate over the shapes in this clip case to determine the number of blend groups. + const auto clipTableIndex = internal::getClipTableIndex(zone.id()); + const auto &ctView = clipTableViews[clipTableIndex]; + + int thisBlendGroups = + 0; // The number of blend groups produced in this case. + int thisBlendGroupLen = 0; // The total length of the blend groups. + int thisFragments = + 0; // The number of zone fragments produced in this case. + int thisFragmentsNumIds = + 0; // The number of points used to make all the fragment zones. + BitSet ptused = 0; // A bitset indicating which ST_XX nodes are used. + + auto it = ctView.begin(clipcase); + const auto end = ctView.end(clipcase); + for(; it != end; it++) + { + // Get the current shape in the clip case. + const auto fragment = *it; + + if(fragment[0] == ST_PNT) + { + if(internal::generatedPointIsSelected(fragment[2], selection)) + { + const int nIds = static_cast(fragment[3]); + + for(int ni = 0; ni < nIds; ni++) + { + const auto pid = fragment[4 + ni]; + + // Increase the blend size to include this center point. + if(pid <= P7) + { + // corner point + thisBlendGroupLen++; + } + else if(pid >= EA && pid <= EL) + { + // edge point + thisBlendGroupLen += 2; + } + } + + // This center or face point counts as a blend group. + thisBlendGroups++; + + // Mark the point used. + axom::utilities::setBitOn(ptused, N0 + fragment[1]); + } + } + else + { + if(internal::shapeIsSelected(fragment[1], selection)) + { + thisFragments++; + const int nIdsThisFragment = fragment.size() - 2; + thisFragmentsNumIds += nIdsThisFragment; + + // Mark the points this fragment used. + for(int i = 2; i < fragment.size(); i++) + { + axom::utilities::setBitOn(ptused, fragment[i]); + } + } + } + } + + // Save the flags for the points that were used in this zone + zoneData.m_pointsUsedView[szIndex] = ptused; + +#if defined(AXOM_REDUCE_BLEND_GROUPS) + // NOTE: We are not going to emit blend groups for P0..P7 points. + + // If the zone uses a node, set that node in nodeUsedView. + for(IndexType pid = P0; pid <= P7; pid++) + { + if(axom::utilities::bitIsSet(ptused, pid)) + { + const auto nodeId = zone.getId(pid); + // NOTE: Multiple threads may write to this node but they all write the same value. + nodeData.m_nodeUsedView[nodeId] = 1; + } + } +#else + // Count which points in the original cell are used. + for(IndexType pid = P0; pid <= P7; pid++) + { + const int incr = axom::utilities::bitIsSet(ptused, pid) ? 1 : 0; + + thisBlendGroupLen += incr; // {p0} + thisBlendGroups += incr; + } +#endif + + // Count edges that are used. + for(IndexType pid = EA; pid <= EL; pid++) + { + const int incr = axom::utilities::bitIsSet(ptused, pid) ? 1 : 0; + + thisBlendGroupLen += 2 * incr; // {p0 p1} + thisBlendGroups += incr; + } + + // Save the results. + fragmentData.m_fragmentsView[szIndex] = thisFragments; + fragmentData.m_fragmentsSizeView[szIndex] = thisFragmentsNumIds; + + // Set blend group sizes for this zone. + blendGroupsView[szIndex] = thisBlendGroups; + blendGroupsLenView[szIndex] = thisBlendGroupLen; + }); // for_selected_zones + +#if defined(AXOM_DEBUG_CLIP_FIELD) + std::cout + << "------------------------ computeSizes ------------------------" + << std::endl; + internal::printHost("fragmentData.m_fragmentsView", + fragmentData.m_fragmentsView); + internal::printHost("fragmentData.m_fragmentsSizeView", + fragmentData.m_fragmentsSizeView); + internal::printHost("blendGroupsView", blendGroupsView); + internal::printHost("blendGroupsLenView", blendGroupsLenView); + internal::printHost("zoneData.m_pointsUsedView", zoneData.m_pointsUsedView); + internal::printHost("zoneData.m_clipCasesView", zoneData.m_clipCasesView); + std::cout + << "--------------------------------------------------------------" + << std::endl; +#endif + } + + /*! + * \brief Compute the total number of fragments and their size. + * + * \param[inout] fragmentData The object that contains data about the zone fragments. + */ + void computeFragmentSizes(FragmentData &fragmentData, + const SelectedZones &selectedZones) const + { + AXOM_ANNOTATE_SCOPE("computeFragmentSizes"); + const auto nzones = selectedZones.view().size(); + + // Sum the number of fragments. + RAJA::ReduceSum fragment_sum(0); + const auto fragmentsView = fragmentData.m_fragmentsView; + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType szIndex) { + fragment_sum += fragmentsView[szIndex]; + }); + fragmentData.m_finalNumZones = fragment_sum.get(); + + // Sum the fragment connectivity sizes. + RAJA::ReduceSum fragment_nids_sum(0); + const auto fragmentsSizeView = fragmentData.m_fragmentsSizeView; + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType szIndex) { + fragment_nids_sum += fragmentsSizeView[szIndex]; + }); + fragmentData.m_finalConnSize = fragment_nids_sum.get(); + } + + /*! + * \brief Compute fragment offsets. + * + * \param[inout] fragmentData The object that contains data about the zone fragments. + */ + void computeFragmentOffsets(FragmentData &fragmentData) const + { + AXOM_ANNOTATE_SCOPE("computeFragmentOffsets"); + axom::exclusive_scan(fragmentData.m_fragmentsView, + fragmentData.m_fragmentOffsetsView); + axom::exclusive_scan(fragmentData.m_fragmentsSizeView, + fragmentData.m_fragmentSizeOffsetsView); + +#if defined(AXOM_DEBUG_CLIP_FIELD) + std::cout << "------------------------ computeFragmentOffsets " + "------------------------" + << std::endl; + internal::printHost("fragmentData.m_fragmentOffsetsView", + fragmentData.m_fragmentOffsetsView); + internal::printHost("fragmentData.m_fragmentSizeOffsetsView", + fragmentData.m_fragmentSizeOffsetsView); + std::cout << "-------------------------------------------------------------" + "-----------" + << std::endl; +#endif + } + +#if defined(AXOM_REDUCE_BLEND_GROUPS) + /*! + * \brief Counts the number of original nodes used by the selected fragments. + * + * \param nodeData The node data (passed by value on purpose) + * \return The number of original nodes used by selected fragments. + */ + int countOriginalNodes(NodeData nodeData) const + { + AXOM_ANNOTATE_SCOPE("countOriginalNodes"); + // Count the number of original nodes we'll use directly. + RAJA::ReduceSum nUsed_reducer(0); + const auto nodeUsedView = nodeData.m_nodeUsedView; + axom::for_all( + nodeUsedView.size(), + AXOM_LAMBDA(axom::IndexType index) { + nUsed_reducer += nodeUsedView[index]; + }); + return nUsed_reducer.get(); + } + + /*! + * \brief Creates the node lists/maps. + * + * \param nodeData The node data that contains views where the node data is stored. + */ + void createNodeMaps(NodeData nodeData) const + { + AXOM_ANNOTATE_SCOPE("createNodeMaps"); + const int allocatorID = axom::execution_space::allocatorID(); + + // Make offsets into a compact array. + const auto nnodes = nodeData.m_nodeUsedView.size(); + axom::Array nodeOffsets(nnodes, nnodes, allocatorID); + auto nodeOffsetsView = nodeOffsets.view(); + axom::exclusive_scan(nodeData.m_nodeUsedView, nodeOffsetsView); + + // Make the compact node list and oldToNew map. + axom::for_all( + nnodes, + AXOM_LAMBDA(axom::IndexType index) { + IndexType newId = 0; + if(nodeData.m_nodeUsedView[index] > 0) + { + nodeData.m_originalIdsView[nodeOffsetsView[index]] = index; + newId = nodeOffsetsView[index]; + } + nodeData.m_oldNodeToNewNodeView[index] = newId; + }); + + #if defined(AXOM_DEBUG_CLIP_FIELD) + std::cout << "---------------------------- createNodeMaps " + "----------------------------" + << std::endl; + internal::printHost("nodeData.m_nodeUsedView", nodeData.m_nodeUsedView); + internal::printHost("nodeData.m_originalIdsView", nodeData.m_originalIdsView); + internal::printHost("nodeData.m_oldNodeToNewNodeView", + nodeData.m_oldNodeToNewNodeView); + std::cout << "-------------------------------------------------------------" + "-----------" + << std::endl; + #endif + } +#endif + + /*! + * \brief Fill in the data for the blend group views. + * + * \param[in] clipTableViews An object that holds views of the clipping table data. + * \param[in] builder This object holds views to blend group data and helps with building/access. + * \param[in] zoneData This object holds views to per-zone data. + * \param[inout] opts Clipping options. + * + * \note Objects that we need to capture into kernels are passed by value (they only contain views anyway). Data can be modified through the views. + */ + void makeBlendGroups(ClipTableViews clipTableViews, + BlendGroupBuilderType builder, + ZoneData zoneData, + const ClipOptions &opts, + const SelectedZones &selectedZones) const + { + AXOM_ANNOTATE_SCOPE("makeBlendGroups"); + const auto selection = getSelection(opts); + + const auto deviceIntersector = m_intersector.view(); + const TopologyView deviceTopologyView(m_topologyView); + const auto selectedZonesView = selectedZones.view(); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + + // Get the clip case for the current zone. + const auto clipcase = zoneData.m_clipCasesView[szIndex]; + + // Iterate over the shapes in this clip case to determine the number of blend groups. + const auto clipTableIndex = internal::getClipTableIndex(zone.id()); + const auto &ctView = clipTableViews[clipTableIndex]; + + // These are the points used in this zone's fragments. + const BitSet ptused = zoneData.m_pointsUsedView[szIndex]; + + // Get the blend groups for this zone. + auto groups = builder.blendGroupsForZone(szIndex); + + auto it = ctView.begin(clipcase); + const auto end = ctView.end(clipcase); + for(; it != end; it++) + { + // Get the current shape in the clip case. + const auto fragment = *it; + + if(fragment[0] == ST_PNT) + { + if(internal::generatedPointIsSelected(fragment[2], selection)) + { + const int nIds = static_cast(fragment[3]); + const auto one_over_n = 1.f / static_cast(nIds); + + groups.beginGroup(); + for(int ni = 0; ni < nIds; ni++) + { + const auto ptid = fragment[4 + ni]; + + // Add the point to the blend group. + if(ptid <= P7) + { + // corner point. + groups.add(zone.getId(ptid), one_over_n); + } + else if(ptid >= EA && ptid <= EL) + { + // edge point. + const auto edgeIndex = ptid - EA; + const auto edge = zone.getEdge(edgeIndex); + const auto id0 = zone.getId(edge[0]); + const auto id1 = zone.getId(edge[1]); + + // Figure out the blend for edge. + const auto t = + deviceIntersector.computeWeight(zoneIndex, id0, id1); + + groups.add(id0, one_over_n * (1.f - t)); + groups.add(id1, one_over_n * t); + } + } + groups.endGroup(); + } + } + } +#if !defined(AXOM_REDUCE_BLEND_GROUPS) + // Add blend group for each original point that was used. + // NOTE - this can add a lot of blend groups with 1 node. + for(IndexType pid = P0; pid <= P7; pid++) + { + if(axom::utilities::bitIsSet(ptused, pid)) + { + groups.beginGroup(); + groups.add(zone.getId(pid), 1.f); + groups.endGroup(); + } + } +#endif + // Add blend group for each edge point that was used. + for(IndexType pid = EA; pid <= EL; pid++) + { + if(axom::utilities::bitIsSet(ptused, pid)) + { + const auto edgeIndex = pid - EA; + const auto edge = zone.getEdge(edgeIndex); + const auto id0 = zone.getId(edge[0]); + const auto id1 = zone.getId(edge[1]); + + // Figure out the blend for edge. + const auto t = deviceIntersector.computeWeight(zoneIndex, id0, id1); + + // Close to the endpoints, just count the edge blend group + // as an endpoint to ensure better blend group matching later. + constexpr decltype(t) LOWER = 1.e-4; + constexpr decltype(t) UPPER = 1. - LOWER; + groups.beginGroup(); + if(t < LOWER) + { + groups.add(id0, 1.f); + } + else if(t > UPPER) + { + groups.add(id1, 1.f); + } + else + { + groups.add(id0, 1.f - t); + groups.add(id1, t); + } + groups.endGroup(); + } + } + }); + } + + /*! + * \brief Make the clipped mesh topology. + * + * \param[in] clipTableViews An object that holds views of the clipping table data. + * \param[in] builder This object holds views to blend group data and helps with building/access. + * \param[in] zoneData This object holds views to per-zone data. + * \param[in] nodeData This object holds views to per-node data. + * \param[in] fragmentData This object holds views to per-fragment data. + * \param[in] opts Clipping options. + * \param[in] selectedZones The selected zones. + * \param[in] newTopologyName The name of the new topology. + * \param[out] n_newTopo The node that will contain the new topology. + * \param[out] n_newCoordset The node that will contain the new coordset. + * \param[out] n_newFields The node that will contain the new fields. + * + * \note Objects that we need to capture into kernels are passed by value (they only contain views anyway). Data can be modified through the views. + */ + void makeTopology(ClipTableViews clipTableViews, + BlendGroupBuilderType builder, + ZoneData zoneData, + NodeData nodeData, + FragmentData fragmentData, + const ClipOptions &opts, + const SelectedZones &selectedZones, + const std::string &newTopologyName, + conduit::Node &n_newTopo, + conduit::Node &n_newCoordset, + conduit::Node &n_newFields) const + { + AXOM_ANNOTATE_SCOPE("makeTopology"); + namespace bputils = axom::mir::utilities::blueprint; + constexpr auto connTypeID = bputils::cpp2conduit::id; + const auto selection = getSelection(opts); + + AXOM_ANNOTATE_BEGIN("allocation"); + n_newTopo["type"] = "unstructured"; + n_newTopo["coordset"] = n_newCoordset.name(); + + // Get the ID of a Conduit allocator that will allocate through Axom with device allocator allocatorID. + // _mir_utilities_c2a_begin + utilities::blueprint::ConduitAllocateThroughAxom c2a; + const int conduitAllocatorID = c2a.getConduitAllocatorID(); + + // Allocate connectivity. + conduit::Node &n_conn = n_newTopo["elements/connectivity"]; + n_conn.set_allocator(conduitAllocatorID); + n_conn.set(conduit::DataType(connTypeID, fragmentData.m_finalConnSize)); + auto connView = bputils::make_array_view(n_conn); + // _mir_utilities_c2a_end + + // Allocate shapes. + conduit::Node &n_shapes = n_newTopo["elements/shapes"]; + n_shapes.set_allocator(conduitAllocatorID); + n_shapes.set(conduit::DataType(connTypeID, fragmentData.m_finalNumZones)); + auto shapesView = bputils::make_array_view(n_shapes); + + // Allocate sizes. + conduit::Node &n_sizes = n_newTopo["elements/sizes"]; + n_sizes.set_allocator(conduitAllocatorID); + n_sizes.set(conduit::DataType(connTypeID, fragmentData.m_finalNumZones)); + auto sizesView = bputils::make_array_view(n_sizes); + + // Allocate offsets. + conduit::Node &n_offsets = n_newTopo["elements/offsets"]; + n_offsets.set_allocator(conduitAllocatorID); + n_offsets.set(conduit::DataType(connTypeID, fragmentData.m_finalNumZones)); + auto offsetsView = bputils::make_array_view(n_offsets); + + // Allocate a color variable to keep track of the "color" of the fragments. + conduit::Node &n_color = n_newFields[opts.colorField()]; + n_color["topology"] = newTopologyName; + n_color["association"] = "element"; + conduit::Node &n_color_values = n_color["values"]; + n_color_values.set_allocator(conduitAllocatorID); + n_color_values.set(conduit::DataType::int32(fragmentData.m_finalNumZones)); + auto colorView = bputils::make_array_view(n_color_values); + + // Fill in connectivity values in case we leave empty slots later. + axom::for_all( + connView.size(), + AXOM_LAMBDA(axom::IndexType index) { connView[index] = 0; }); + +#if defined(AXOM_DEBUG_CLIP_FIELD) + // Initialize the values beforehand. For debugging. + axom::for_all( + shapesView.size(), + AXOM_LAMBDA(axom::IndexType index) { + shapesView[index] = -2; + sizesView[index] = -3; + offsetsView[index] = -4; + colorView[index] = -5; + }); +#endif + AXOM_ANNOTATE_END("allocation"); + + // Here we fill in the new connectivity, sizes, shapes. + // We get the node ids from the unique blend names, de-duplicating points when making the new connectivity. + // + // NOTE: During development, I ran into problems with this kernel not executing + // due to point_2_new being too large. The solution was to reduce the values + // for EA-EL, N0-N3 to shrink the array to the point where it can fit in + // memory available to the thread. + // +#if defined(AXOM_CLIP_FILTER_DEGENERATES) + RAJA::ReduceBitOr degenerates_reduce(0); +#endif + { + AXOM_ANNOTATE_SCOPE("build"); + const auto origSize = nodeData.m_originalIdsView.size(); + + const TopologyView deviceTopologyView(m_topologyView); + const auto selectedZonesView = selectedZones.view(); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + + // If there are no fragments, return from lambda. + if(fragmentData.m_fragmentsView[szIndex] == 0) return; + + // Seek to the start of the blend groups for this zone. + auto groups = builder.blendGroupsForZone(szIndex); + + // Go through the points in the order they would have been added as blend + // groups, get their blendName, and then overall index of that blendName + // in uNames, the unique list of new dof names. That will be their index + // in the final points. + const BitSet ptused = zoneData.m_pointsUsedView[szIndex]; + ConnectivityType point_2_new[N3 + 1]; + for(BitSet pid = N0; pid <= N3; pid++) + { + if(axom::utilities::bitIsSet(ptused, pid)) + { + point_2_new[pid] = origSize + groups.uniqueBlendGroupIndex(); + groups++; + } + } +#if defined(AXOM_REDUCE_BLEND_GROUPS) + // For single nodes, we did not make a blend group. We look up the new + // node id from nodeData.m_oldNodeToNewNodeView. + for(BitSet pid = P0; pid <= P7; pid++) + { + if(axom::utilities::bitIsSet(ptused, pid)) + { + const auto nodeId = zone.getId(pid); + point_2_new[pid] = nodeData.m_oldNodeToNewNodeView[nodeId]; + } + } +#else + for(BitSet pid = P0; pid <= P7; pid++) + { + if(axom::utilities::bitIsSet(ptused, pid)) + { + point_2_new[pid] = origSize + groups.uniqueBlendGroupIndex(); + groups++; + } + } +#endif + for(BitSet pid = EA; pid <= EL; pid++) + { + if(axom::utilities::bitIsSet(ptused, pid)) + { +#if defined(AXOM_REDUCE_BLEND_GROUPS) + // There is a chance that the edge blend group was emitted with a + // single node if the edge was really close to a corner node. + if(groups.size() == 1) + { + const auto nodeId = groups.id(0); + point_2_new[pid] = nodeData.m_oldNodeToNewNodeView[nodeId]; + } + else + { + point_2_new[pid] = origSize + groups.uniqueBlendGroupIndex(); + } +#else + point_2_new[pid] = origSize + groups.uniqueBlendGroupIndex(); +#endif + groups++; + } + } + + // This is where the output fragment connectivity start for this zone + int outputIndex = fragmentData.m_fragmentSizeOffsetsView[szIndex]; + // This is where the output fragment sizes/shapes start for this zone. + int sizeIndex = fragmentData.m_fragmentOffsetsView[szIndex]; +#if defined(AXOM_CLIP_FILTER_DEGENERATES) + bool degenerates = false; +#endif + // Iterate over the selected fragments and emit connectivity for them. + const auto clipcase = zoneData.m_clipCasesView[szIndex]; + const auto clipTableIndex = internal::getClipTableIndex(zone.id()); + const auto ctView = clipTableViews[clipTableIndex]; + auto it = ctView.begin(clipcase); + const auto end = ctView.end(clipcase); + for(; it != end; it++) + { + // Get the current shape in the clip case. + const auto fragment = *it; + const auto fragmentShape = fragment[0]; + + if(fragmentShape != ST_PNT) + { + if(internal::shapeIsSelected(fragment[1], selection)) + { + // Output the nodes used in this zone. + const int fragmentSize = fragment.size(); + offsetsView[sizeIndex] = outputIndex; + for(int i = 2; i < fragmentSize; i++) + connView[outputIndex++] = point_2_new[fragment[i]]; + + const auto nIdsThisFragment = fragmentSize - 2; +#if defined(AXOM_CLIP_FILTER_DEGENERATES) + // Set the output zone size, checking to see whether it is degenerate. + degenerates |= internal::DegenerateHandler< + TopologyView::dimension(), + ExecSpace, + ConnectivityType>::setSize(fragmentData.m_fragmentsView, + connView, + sizesView, + szIndex, + nIdsThisFragment, + sizeIndex, + outputIndex); +#else + sizesView[sizeIndex] = nIdsThisFragment; +#endif + shapesView[sizeIndex] = + internal::ST_Index_to_ShapeID(fragmentShape); + colorView[sizeIndex] = fragment[1] - COLOR0; + sizeIndex++; + } + } + } + +#if defined(AXOM_CLIP_FILTER_DEGENERATES) + // Reduce overall whether there are degenerates. + degenerates_reduce |= degenerates; +#endif + }); // for_selected_zones + +#if defined(AXOM_DEBUG_CLIP_FIELD) + std::cout + << "------------------------ makeTopology ------------------------" + << std::endl; + std::cout << "degenerates_reduce=" << degenerates_reduce.get() << std::endl; + // internal::printHost("selectedZones", selectedZones.view()); + internal::printHost("m_fragmentsView", fragmentData.m_fragmentsView); + // internal::printHost("zoneData.m_clipCasesView", zoneData.m_clipCasesView); + // internal::printHost("zoneData.m_pointsUsedView", zoneData.m_pointsUsedView); + internal::printHost("conn", connView); + internal::printHost("sizes", sizesView); + internal::printHost("offsets", offsetsView); + internal::printHost("shapes", shapesView); + internal::printHost("color", colorView); + std::cout + << "--------------------------------------------------------------" + << std::endl; +#endif + } + +#if defined(AXOM_CLIP_FILTER_DEGENERATES) + // Filter out shapes that were marked as zero-size, adjusting connectivity and other arrays. + if(degenerates_reduce.get()) + { + internal::DegenerateHandler::filterZeroSizes(fragmentData, + n_sizes, + n_offsets, + n_shapes, + n_color_values, + sizesView, + offsetsView, + shapesView, + colorView); + } +#endif + + // Figure out which shapes were used. + BitSet shapesUsed = findUsedShapes(shapesView); + +#if defined(AXOM_DEBUG_CLIP_FIELD) + std::cout + << "------------------------ makeTopology ------------------------" + << std::endl; + internal::printHost("selectedZones", selectedZones.view()); + internal::printHost("m_fragmentsView", fragmentData.m_fragmentsView); + internal::printHost("zoneData.m_clipCasesView", zoneData.m_clipCasesView); + internal::printHost("zoneData.m_pointsUsedView", zoneData.m_pointsUsedView); + internal::printHost("conn", connView); + internal::printHost("sizes", sizesView); + internal::printHost("offsets", offsetsView); + internal::printHost("shapes", shapesView); + internal::printHost("color", colorView); + std::cout + << "--------------------------------------------------------------" + << std::endl; +#endif + + // If inside and outside are not selected, remove the color field since we should not need it. + if(!(opts.inside() && opts.outside())) + { + n_newFields.remove(opts.colorField()); + } + +#if defined(AXOM_CLIP_FILTER_DEGENERATES) + // Handle some quad->tri degeneracies, depending on dimension. + shapesUsed = + internal::DegenerateHandler::quadtri(shapesUsed, + connView, + sizesView, + offsetsView, + shapesView); +#endif + + // Add shape information to the connectivity. + SLIC_ASSERT_MSG(shapesUsed != 0, "No shapes were produced!"); + const auto shapeMap = shapeMap_FromFlags(shapesUsed); + SLIC_ASSERT_MSG(shapeMap.empty() == false, "The shape map is empty!"); + if(axom::utilities::popcount(static_cast(shapesUsed)) > 1) + { + n_newTopo["elements/shape"] = "mixed"; + conduit::Node &n_shape_map = n_newTopo["elements/shape_map"]; + for(auto it = shapeMap.cbegin(); it != shapeMap.cend(); it++) + n_shape_map[it->first] = it->second; + } + else + { + n_newTopo["elements"].remove("shapes"); + n_newTopo["elements/shape"] = shapeMap.begin()->first; + } + } + + /*! + * \brief Find the shapes that were used. + * + * \param shapesView The view that contains the shapes. + * + * \return A BitSet where bits are marked for each shape used. + */ + BitSet findUsedShapes(axom::ArrayView shapesView) const + { + AXOM_ANNOTATE_SCOPE("findUsedShapes"); + + RAJA::ReduceBitOr shapesUsed_reduce(0); + const axom::IndexType nShapes = shapesView.size(); + axom::for_all( + nShapes, + AXOM_LAMBDA(axom::IndexType index) { + BitSet shapeBit = 1 << shapesView[index]; + shapesUsed_reduce |= shapeBit; + }); + BitSet shapesUsed = shapesUsed_reduce.get(); + return shapesUsed; + } + + /*! + * \brief Make the new coordset using the blend data and the input coordset/coordsetview. + * + * \param blend The BlendData that we need to construct the new coordset. + * \param n_coordset The input coordset, which is passed for metadata. + * \param[out] n_newCoordset The new coordset. + */ + void makeCoordset(const BlendData &blend, + const conduit::Node &n_coordset, + conduit::Node &n_newCoordset) const + { + AXOM_ANNOTATE_SCOPE("makeCoordset"); + // _mir_utilities_coordsetblender_start + axom::mir::utilities::blueprint:: + CoordsetBlender + cb; + cb.execute(blend, m_coordsetView, n_coordset, n_newCoordset); + // _mir_utilities_coordsetblender_end + } + + /*! + * \brief Make new fields for the output topology. + * + * \param blend The BlendData that we need to construct the new vertex fields. + * \param slice The SliceData we need to construct new element fields. + * \param topologyName The name of the new field's topology. + * \param fieldMap A map containing the names of the fields that we'll operate on. + * \param n_fields The source fields. + * \param[out] n_out_fields The node that will contain the new fields. + */ + void makeFields(const BlendData &blend, + const SliceData &slice, + const std::string &topologyName, + const std::map &fieldMap, + const conduit::Node &n_fields, + conduit::Node &n_out_fields) const + { + AXOM_ANNOTATE_SCOPE("makeFields"); + constexpr bool ss = + axom::mir::views::view_traits::supports_strided_structured(); + + for(auto it = fieldMap.begin(); it != fieldMap.end(); it++) + { + const conduit::Node &n_field = n_fields.fetch_existing(it->first); + const std::string association = n_field["association"].as_string(); + if(association == "element") + { + // Conditionally support strided-structured. + bool handled = + internal::StridedStructuredFields::sliceElementField( + m_topologyView, + slice, + n_field, + n_out_fields[it->second]); + + if(!handled) + { + axom::mir::utilities::blueprint::FieldSlicer s; + s.execute(slice, n_field, n_out_fields[it->second]); + } + + n_out_fields[it->second]["topology"] = topologyName; + } + else if(association == "vertex") + { + // Conditionally support strided-structured. + bool handled = + internal::StridedStructuredFields::blendVertexField( + m_topologyView, + blend, + n_field, + n_out_fields[it->second]); + + if(!handled) + { + // Blend the field normally. + axom::mir::utilities::blueprint::FieldBlender< + ExecSpace, + axom::mir::utilities::blueprint::SelectSubsetPolicy> + b; + b.execute(blend, n_field, n_out_fields[it->second]); + } + + n_out_fields[it->second]["topology"] = topologyName; + } + } + } + + /*! + * \brief Make an originalElements field so we can know each output zone's original zone number in the input mesh. + * + * \param[in] fragmentData This object holds views to per-fragment data. + * \param[in] opts Clipping options. + * \param[in] selectedZones The selected zones. + * \param[in] n_fields The node that contains the input mesh's fields. + * \param[out] n_newTopo The node that will contain the new topology. + * \param[out] n_newFields The node that will contain the new fields. + * + * \note Objects that we need to capture into kernels are passed by value (they only contain views anyway). Data can be modified through the views. + */ + void makeOriginalElements(FragmentData fragmentData, + const ClipOptions &opts, + const SelectedZones &selectedZones, + const conduit::Node &n_fields, + conduit::Node &n_newTopo, + conduit::Node &n_newFields) const + { + AXOM_ANNOTATE_SCOPE("makeOriginalElements"); + namespace bputils = axom::mir::utilities::blueprint; + constexpr auto connTypeID = bputils::cpp2conduit::id; + + utilities::blueprint::ConduitAllocateThroughAxom c2a; + const int conduitAllocatorID = c2a.getConduitAllocatorID(); + + const auto selectedZonesView = selectedZones.view(); + const auto nzones = selectedZonesView.size(); + const std::string originalElements(opts.originalElementsField()); + + if(n_fields.has_child(originalElements)) + { + // originalElements already exists. We need to map it forward. + const conduit::Node &n_orig = n_fields[originalElements]; + const conduit::Node &n_orig_values = n_orig["values"]; + views::IndexNode_to_ArrayView(n_orig_values, [&](auto origValuesView) { + using value_type = typename decltype(origValuesView)::value_type; + conduit::Node &n_origElem = n_newFields[originalElements]; + n_origElem["association"] = "element"; + n_origElem["topology"] = opts.topologyName(n_newTopo.name()); + conduit::Node &n_values = n_origElem["values"]; + n_values.set_allocator(conduitAllocatorID); + n_values.set(conduit::DataType(n_orig_values.dtype().id(), + fragmentData.m_finalNumZones)); + auto valuesView = bputils::make_array_view(n_values); + makeOriginalElements_copy(fragmentData, + selectedZones, + valuesView, + origValuesView); + }); + } + else + { + // Make a new node and populate originalElement. + conduit::Node &n_orig = n_newFields[originalElements]; + n_orig["association"] = "element"; + n_orig["topology"] = opts.topologyName(n_newTopo.name()); + conduit::Node &n_values = n_orig["values"]; + n_values.set_allocator(conduitAllocatorID); + n_values.set(conduit::DataType(connTypeID, fragmentData.m_finalNumZones)); + auto valuesView = bputils::make_array_view(n_values); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + const int sizeIndex = fragmentData.m_fragmentOffsetsView[index]; + const int nFragments = fragmentData.m_fragmentsView[index]; + const auto zoneIndex = selectedZonesView[index]; + for(int i = 0; i < nFragments; i++) + valuesView[sizeIndex + i] = zoneIndex; + }); + } + } + + /*! + * \brief Assist setting original elements that already exist, based on selected zones. + * + * \param[in] fragmentData This object holds views to per-fragment data. + * \param[in] selectedZones The selected zones. + * \param[out] valuesView The destination values view. + * \param[in] origValuesView The source values view. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void makeOriginalElements_copy(FragmentData fragmentData, + const SelectedZones &selectedZones, + DataView valuesView, + DataView origValuesView) const + { + const auto selectedZonesView = selectedZones.view(); + const auto nzones = selectedZonesView.size(); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + const int sizeIndex = fragmentData.m_fragmentOffsetsView[index]; + const int nFragments = fragmentData.m_fragmentsView[index]; + const auto zoneIndex = selectedZonesView[index]; + for(int i = 0; i < nFragments; i++) + valuesView[sizeIndex + i] = origValuesView[zoneIndex]; + }); + } + + /*! + * \brief Given a flag that includes bitwise-or'd shape ids, make a map that indicates which Conduit shapes are used. + * + * \param shapes This is a bitwise-or of various (1 << ShapeID) values. + * + * \return A map of Conduit shape name to ShapeID value. + */ + std::map shapeMap_FromFlags(std::uint64_t shapes) const + { + std::map sm; + + if(axom::utilities::bitIsSet(shapes, views::Line_ShapeID)) + sm["line"] = views::Line_ShapeID; + + if(axom::utilities::bitIsSet(shapes, views::Tri_ShapeID)) + sm["tri"] = views::Tri_ShapeID; + + if(axom::utilities::bitIsSet(shapes, views::Quad_ShapeID)) + sm["quad"] = views::Quad_ShapeID; + + if(axom::utilities::bitIsSet(shapes, views::Polygon_ShapeID)) + sm["polygon"] = views::Polygon_ShapeID; + + if(axom::utilities::bitIsSet(shapes, views::Tet_ShapeID)) + sm["tet"] = views::Tet_ShapeID; + + if(axom::utilities::bitIsSet(shapes, views::Pyramid_ShapeID)) + sm["pyramid"] = views::Pyramid_ShapeID; + + if(axom::utilities::bitIsSet(shapes, views::Wedge_ShapeID)) + sm["wedge"] = views::Wedge_ShapeID; + + if(axom::utilities::bitIsSet(shapes, views::Hex_ShapeID)) + sm["hex"] = views::Hex_ShapeID; + + if(axom::utilities::bitIsSet(shapes, views::Polyhedron_ShapeID)) + sm["polyhedron"] = views::Polyhedron_ShapeID; + + return sm; + } + + /*! + * \brief If we're making a field that marks the new nodes that were created as + * a result of clipping, update those nodes now. + * + * \param blend The blend data used to create nodal fields. + * \param newNodes The name of the new nodes field. + * \param topoName The name of the output topology. + * \param[inout] n_newFields The fields node for the output mesh. + */ + void markNewNodes(const BlendData &blend, + const std::string &newNodes, + const std::string &topoName, + conduit::Node &n_newFields) const + { + namespace bputils = axom::mir::utilities::blueprint; + AXOM_ANNOTATE_SCOPE("markNewNodes"); + if(!newNodes.empty()) + { + const auto origSize = blend.m_originalIdsView.size(); + const auto blendSize = blend.m_selectedIndicesView.size(); + const auto outputSize = origSize + blendSize; + using Precision = int; + constexpr Precision one = 1; + constexpr Precision zero = 0; + + if(n_newFields.has_child(newNodes)) + { + // Update the field. The field would have gone through field blending. + // We can mark the new nodes with fresh values. This comes up in + // applications that call Clip multiple times. + + conduit::Node &n_new_nodes = n_newFields.fetch_existing(newNodes); + conduit::Node &n_new_nodes_values = n_new_nodes["values"]; + auto valuesView = bputils::make_array_view(n_new_nodes_values); + + // Update values for the blend groups only. + axom::for_all( + blendSize, + AXOM_LAMBDA(axom::IndexType bgid) { + valuesView[origSize + bgid] = one; + }); + } + else + { + // Make the field for the first time. + // Allocate Conduit data through Axom. + bputils::ConduitAllocateThroughAxom c2a; + conduit::Node &n_new_nodes = n_newFields[newNodes]; + n_new_nodes["topology"] = topoName; + n_new_nodes["association"] = "vertex"; + conduit::Node &n_new_nodes_values = n_new_nodes["values"]; + n_new_nodes_values.set_allocator(c2a.getConduitAllocatorID()); + n_new_nodes_values.set( + conduit::DataType(bputils::cpp2conduit::id, outputSize)); + auto valuesView = bputils::make_array_view(n_new_nodes_values); + + // Fill in values. Everything below origSize is an original node. + // Everything above is a blended node. + axom::for_all( + outputSize, + AXOM_LAMBDA(axom::IndexType index) { + valuesView[index] = (index < origSize) ? zero : one; + }); + } + } + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + TopologyView m_topologyView {}; + CoordsetView m_coordsetView {}; + Intersector m_intersector {}; + axom::mir::clipping::ClipTableManager m_clipTables {}; + NamingPolicy m_naming {}; +}; + +} // end namespace clipping +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/clipping/ClipOptions.hpp b/src/axom/mir/clipping/ClipOptions.hpp new file mode 100644 index 0000000000..f1f29d7a5d --- /dev/null +++ b/src/axom/mir/clipping/ClipOptions.hpp @@ -0,0 +1,118 @@ +// 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) +#ifndef AXOM_MIR_CLIP_OPTIONS_HPP_ +#define AXOM_MIR_CLIP_OPTIONS_HPP_ + +#include "axom/mir/Options.hpp" + +namespace axom +{ +namespace mir +{ +namespace clipping +{ +/** + * \brief This class provides a kind of schema over the clipping options, as well + * as default values, and some utilities functions. + */ +class ClipOptions : public axom::mir::Options +{ +public: + /** + * \brief Constructor + * + * \param options The node that contains the clipping options. + */ + ClipOptions(const conduit::Node &options) : axom::mir::Options(options) { } + + /** + * \brief Return the name of the field used for clipping. + * \return The name of the field used for clipping. + */ + std::string clipField() const + { + return options().fetch_existing("clipField").as_string(); + } + + /** + * \brief Return the clip value. + * \return The clip value. + */ + float clipValue() const + { + return options().has_child("clipValue") + ? options().fetch_existing("clipValue").to_float() + : 0.f; + } + + /** + * \brief Return the name of the new color field to be created. + * \return The name of the new color field to be created. + */ + std::string colorField() const + { + std::string name("color"); + if(options().has_child("colorField")) + name = options().fetch_existing("colorField").as_string(); + return name; + } + + /** + * \brief Return the name of the new original elements field to be created. + * \return The name of the new original elements to be created. + */ + std::string originalElementsField() const + { + std::string name("originalElements"); + if(options().has_child("originalElementsField")) + name = options().fetch_existing("originalElementsField").as_string(); + return name; + } + + /** + * \brief Return the name of the new nodes field to be created. If the name + * is not set then it will not be created. + * \return The name of the new field to be created. + */ + std::string newNodesField() const + { + std::string name; + if(options().has_child("newNodesField")) + name = options().fetch_existing("newNodesField").as_string(); + return name; + } + + /** + * \brief Whether the "inside" of the clipping field is selected. + * \return 1 of the inside clipping is selected, false otherwise. + */ + bool inside() const + { + return options().has_path("inside") + ? (options().fetch_existing("inside").to_int() > 0) + : true; + } + + /** + * \brief Whether the "outside" of the clipping field is selected. + * \return 1 of the outside clipping is selected, false otherwise. + */ + bool outside() const + { + return options().has_path("outside") + ? (options().fetch_existing("outside").to_int() > 0) + : false; + } + +private: + /// Access the base class' options. + const conduit::Node &options() const { return this->m_options; } +}; + +} // end namespace clipping +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/clipping/ClipTableManager.hpp b/src/axom/mir/clipping/ClipTableManager.hpp new file mode 100644 index 0000000000..b311869a8c --- /dev/null +++ b/src/axom/mir/clipping/ClipTableManager.hpp @@ -0,0 +1,524 @@ +// 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) + +#ifndef AXOM_MIR_CLIPPING_CLIP_TABLE_MANAGER_HPP_ +#define AXOM_MIR_CLIPPING_CLIP_TABLE_MANAGER_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/clipping/ClipCases.h" + +namespace axom +{ +namespace mir +{ +namespace clipping +{ +/*! + * \accelerated + * \brief This class contains a view of table data and it provides an + * iterator for traversing shapes in a case. + */ +class TableView +{ +public: + using IndexData = int; + using TableData = unsigned char; + using IndexView = axom::ArrayView; + using TableDataView = axom::ArrayView; + + /*! + * \brief An iterator for shapes within a table case. + */ + class iterator + { + public: + /*! + * \brief Return the index of the iterator's current shape. + * \return The index of the iterator's current shape. + */ + AXOM_HOST_DEVICE + inline int index() const { return m_currentShape; } + + /*! + * \brief Return the number of shapes in the iterator's case. + * \return The number of shapes in the iterator's case. + */ + AXOM_HOST_DEVICE + inline int size() const { return m_numShapes; } + + /*! + * \brief Increment the iterator, moving it to the next shape. + */ + AXOM_HOST_DEVICE + inline void operator++() + { + if(m_currentShape < m_numShapes) + { + const TableData *ptr = m_shapeStart + m_offset; + m_offset += shapeLength(ptr); + m_currentShape++; + } + } + + /*! + * \brief Increment the iterator, moving it to the next shape. + */ + AXOM_HOST_DEVICE + inline void operator++(int) + { + if(m_currentShape < m_numShapes) + { + const TableData *ptr = m_shapeStart + m_offset; + m_offset += shapeLength(ptr); + m_currentShape++; + } + } + + /*! + * \brief Compare 2 iterators for equality. + * \param it The iterator to be compared to this. + * \return true if the iterators are equal; false otherwise. + */ + AXOM_HOST_DEVICE + inline bool operator==(const iterator &it) const + { + // Do not worry about m_offset + return m_shapeStart == it.m_shapeStart && + m_currentShape == it.m_currentShape && m_numShapes == it.m_numShapes; + } + + /*! + * \brief Compare 2 iterators to see if not equal. + * \param it The iterator to be compared to this. + * \return true if the iterators are different; false otherwise. + */ + AXOM_HOST_DEVICE + inline bool operator!=(const iterator &it) const + { + // Do not worry about m_offset + return m_shapeStart != it.m_shapeStart || + m_currentShape != it.m_currentShape || m_numShapes != it.m_numShapes; + } + + /*! + * \brief Dereference operator that wraps the current shape data in an array + * view so the caller can use the shape data. + */ + AXOM_HOST_DEVICE + inline TableDataView operator*() const + { + TableData *ptr = m_shapeStart + m_offset; + const auto len = shapeLength(ptr); + return TableDataView(ptr, len); + } +#if !defined(AXOM_DEVICE_CODE) + private: + void printShape(std::ostream &os, TableData shape) const + { + switch(shape) + { + case ST_PNT: + os << "ST_PNT"; + break; + case ST_TRI: + os << "ST_TRI"; + break; + case ST_QUA: + os << "ST_QUA"; + break; + case ST_TET: + os << "ST_TET"; + break; + case ST_PYR: + os << "ST_PYR"; + break; + case ST_WDG: + os << "ST_WDG"; + break; + case ST_HEX: + os << "ST_HEX"; + break; + } + } + void printColor(std::ostream &os, TableData color) const + { + switch(color) + { + case COLOR0: + os << "COLOR0"; + break; + case COLOR1: + os << "COLOR1"; + break; + case NOCOLOR: + os << "NOCOLOR"; + break; + } + } + void printIds(std::ostream &os, const TableData *ids, int n) const + { + for(int i = 0; i < n; i++) + { + if(/*ids[i] >= P0 &&*/ ids[i] <= P7) + os << "P" << static_cast(ids[i]); + else if(ids[i] >= EA && ids[i] <= EL) + { + char c = 'A' + (ids[i] - EA); + os << "E" << c; + } + else if(ids[i] >= N0 && ids[i] <= N3) + { + os << "N" << static_cast(ids[i] - N0); + } + os << " "; + } + } + + public: + void print(std::ostream &os) const + { + TableData *ptr = m_shapeStart + m_offset; + printShape(os, ptr[0]); + os << " "; + int offset = 2; + if(ptr[0] == ST_PNT) + { + os << static_cast(ptr[1]); // point number. + os << " "; + + printColor(os, ptr[2]); + os << " "; + + os << static_cast(ptr[3]); // npts + os << " "; + offset = 4; + } + else + { + printColor(os, ptr[1]); + os << " "; + } + + const auto n = shapeLength(ptr) - offset; + printIds(os, ptr + offset, n); + } +#endif + private: + friend class TableView; + + /*! + * \brief Given the input shape, return how many values to advance to get to the next shape. + * + * \param shape The shape type. + * + * \return The number of values to advance. + */ + AXOM_HOST_DEVICE + size_t shapeLength(const TableData *caseData) const + { + size_t retval = 0; + const auto shape = caseData[0]; + switch(shape) + { + case ST_PNT: + retval = 4 + caseData[3]; + break; + case ST_TRI: + retval = 2 + 3; + break; + case ST_QUA: + retval = 2 + 4; + break; + case ST_TET: + retval = 2 + 4; + break; + case ST_PYR: + retval = 2 + 5; + break; + case ST_WDG: + retval = 2 + 6; + break; + case ST_HEX: + retval = 2 + 8; + break; + } + return retval; + } + + TableData *m_shapeStart {nullptr}; + int m_offset {0}; + int m_currentShape {0}; + int m_numShapes {0}; + }; + + /*! + * \brief Constructor + */ + AXOM_HOST_DEVICE + TableView() : m_shapes(), m_offsets(), m_table() { } + + /*! + * \brief Constructor + * + * \param shapes The number of shapes in each table case. + * \param offsets The offsets to each shape case in the \a table. + * \param table The table data that contains all cases. + */ + AXOM_HOST_DEVICE + TableView(const IndexView &shapes, + const IndexView &offsets, + const TableDataView &table) + : m_shapes(shapes) + , m_offsets(offsets) + , m_table(table) + { } + + /*! + * \brief Return the number of cases for the clipping table. + * + * \return The number of cases for the clipping table. + */ + AXOM_HOST_DEVICE + size_t size() const { return m_shapes.size(); } + + /*! + * \brief Return the iterator for the beginning of a case. + * + * \param caseId The case whose begin iterator we want. + * \return The iterator at the begin of the case. + */ + AXOM_HOST_DEVICE + iterator begin(size_t caseId) const + { + assert(static_cast(caseId) < m_shapes.size()); + iterator it; + it.m_shapeStart = const_cast(m_table.data() + m_offsets[caseId]); + it.m_offset = 0; + it.m_currentShape = 0; + it.m_numShapes = m_shapes[caseId]; + return it; + } + + /*! + * \brief Return the iterator for the end of a case. + * + * \param caseId The case whose end iterator we want. + * \return The iterator at the end of the case. + */ + AXOM_HOST_DEVICE + iterator end(size_t caseId) const + { + assert(static_cast(caseId) < m_shapes.size()); + iterator it; + it.m_shapeStart = const_cast(m_table.data() + m_offsets[caseId]); + it.m_offset = 0; // not checked in iterator::operator== + it.m_currentShape = m_shapes[caseId]; + it.m_numShapes = m_shapes[caseId]; + return it; + } + +private: + IndexView m_shapes; // The number of shapes in each case. + IndexView m_offsets; // The offset to the case in the table. + TableDataView m_table; // The table data that contains the shapes. +}; + +/*! + * \brief This class manages data table arrays and can produce a view for the data. + */ +template +class Table +{ +public: + using IndexData = int; + using TableData = unsigned char; + using IndexDataArray = axom::Array; + using TableDataArray = axom::Array; + + /*! + * \brief Returns whether the table data have been loaded. + * \return True if the data have been loaded; false otherwise. + */ + bool isLoaded() const { return m_shapes.size() > 0; } + + /*! + * \brief Load table data into the arrays, moving data as needed. + * + * \param n The number of cases in the clip table. + * \param shapes The number of shapes produced by clipping cases. + * \param offsets The offset into the table for each clipping case. + * \param table The clipping table data. + * \param tableLen The size of the clipping table data. + */ + void load(size_t n, + const IndexData *shapes, + const IndexData *offsets, + const TableData *table, + size_t tableLen) + { + const int allocatorID = execution_space::allocatorID(); + + // Allocate space. + m_shapes = IndexDataArray(n, n, allocatorID); + m_offsets = IndexDataArray(n, n, allocatorID); + m_table = TableDataArray(tableLen, tableLen, allocatorID); + + // Copy data to the arrays. + axom::copy(m_shapes.data(), shapes, n * sizeof(int)); + axom::copy(m_offsets.data(), offsets, n * sizeof(int)); + axom::copy(m_table.data(), table, tableLen * sizeof(unsigned char)); + } + + /*! + * \brief Create a view to access the table data. + * + * \return A view of the table data. + */ + TableView view() + { + return TableView(m_shapes.view(), m_offsets.view(), m_table.view()); + } + +private: + IndexDataArray m_shapes; + IndexDataArray m_offsets; + TableDataArray m_table; +}; + +/*! + * \brief Manage several clipping tables. + */ +template +class ClipTableManager +{ +public: + static constexpr int NumberOfTables = ST_MAX - ST_MIN; + + /*! + * \brief Return a reference to the clipping table, which is loaded on demand. + * + * \param shape The shape type to be retrieved. + * + * \return A reference to the clipping table. + */ + Table &operator[](size_t shape) + { + const size_t index = shapeToIndex(shape); + assert(shape < ST_MAX); + loadShape(shape); + return m_tables[index]; + } + + /*! + * \brief Load tables based on dimension. + * \param dim The dimension of shapes to load. + */ + void load(int dim) + { + for(const auto shape : shapes(dim)) loadShape(shape); + } + + /*! + * \brief Return a vector of clipping shape ids for the given dimension. + * + * \param The spatial dimension. + * + * \return A vector of clipping shape ids. + */ + std::vector shapes(int dim) const + { + std::vector s; + if(dim == -1 || dim == 2) + { + for(const auto value : std::vector {ST_TRI, ST_QUA}) + s.push_back(value); + } + if(dim == -1 || dim == 3) + { + for(const auto value : std::vector {ST_TET, ST_PYR, ST_WDG, ST_HEX}) + s.push_back(value); + } + return s; + } + +private: + /*! + * \brief Turn a shape into an table index. + * + * \param shape The shape type ST_XXX. + * + * \return An index into the m_tables array. + */ + constexpr static size_t shapeToIndex(size_t shape) { return shape - ST_MIN; } + + /*! + * \brief Load the clipping table for a shape. + * + * \param shape The shape whose table will be loaded. + */ + void loadShape(size_t shape) + { + const auto index = shapeToIndex(shape); + if(!m_tables[index].isLoaded()) + { + if(shape == ST_TRI) + { + m_tables[index].load(axom::mir::clipping::visit::numClipCasesTri, + axom::mir::clipping::visit::numClipShapesTri, + axom::mir::clipping::visit::startClipShapesTri, + axom::mir::clipping::visit::clipShapesTri, + axom::mir::clipping::visit::clipShapesTriSize); + } + else if(shape == ST_QUA) + { + m_tables[index].load(axom::mir::clipping::visit::numClipCasesQua, + axom::mir::clipping::visit::numClipShapesQua, + axom::mir::clipping::visit::startClipShapesQua, + axom::mir::clipping::visit::clipShapesQua, + axom::mir::clipping::visit::clipShapesQuaSize); + } + else if(shape == ST_TET) + { + m_tables[index].load(axom::mir::clipping::visit::numClipCasesTet, + axom::mir::clipping::visit::numClipShapesTet, + axom::mir::clipping::visit::startClipShapesTet, + axom::mir::clipping::visit::clipShapesTet, + axom::mir::clipping::visit::clipShapesTetSize); + } + else if(shape == ST_PYR) + { + m_tables[index].load(axom::mir::clipping::visit::numClipCasesPyr, + axom::mir::clipping::visit::numClipShapesPyr, + axom::mir::clipping::visit::startClipShapesPyr, + axom::mir::clipping::visit::clipShapesPyr, + axom::mir::clipping::visit::clipShapesPyrSize); + } + else if(shape == ST_WDG) + { + m_tables[index].load(axom::mir::clipping::visit::numClipCasesWdg, + axom::mir::clipping::visit::numClipShapesWdg, + axom::mir::clipping::visit::startClipShapesWdg, + axom::mir::clipping::visit::clipShapesWdg, + axom::mir::clipping::visit::clipShapesWdgSize); + } + else if(shape == ST_HEX) + { + m_tables[index].load(axom::mir::clipping::visit::numClipCasesHex, + axom::mir::clipping::visit::numClipShapesHex, + axom::mir::clipping::visit::startClipShapesHex, + axom::mir::clipping::visit::clipShapesHex, + axom::mir::clipping::visit::clipShapesHexSize); + } + } + } + + axom::StackArray, NumberOfTables> m_tables {}; +}; + +} // end namespace clipping +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/clipping/README.md b/src/axom/mir/clipping/README.md new file mode 100644 index 0000000000..9c2fdc83c2 --- /dev/null +++ b/src/axom/mir/clipping/README.md @@ -0,0 +1,38 @@ +============== +VisIt License +============== + +The clipping tables have been borrowed from the VisIt software. The VisIt license +appears below: + +``` +BSD 3-Clause License + +Copyright (c) 2000 - 2024, Lawrence Livermore National Security, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` diff --git a/src/axom/mir/docs/sphinx/figures/clipfield.png b/src/axom/mir/docs/sphinx/figures/clipfield.png new file mode 100644 index 0000000000..7ebcd6de10 Binary files /dev/null and b/src/axom/mir/docs/sphinx/figures/clipfield.png differ diff --git a/src/axom/mir/docs/sphinx/figures/mir_concentric_circles.png b/src/axom/mir/docs/sphinx/figures/mir_concentric_circles.png new file mode 100644 index 0000000000..f1d76bba32 Binary files /dev/null and b/src/axom/mir/docs/sphinx/figures/mir_concentric_circles.png differ diff --git a/src/axom/mir/docs/sphinx/index.rst b/src/axom/mir/docs/sphinx/index.rst new file mode 100644 index 0000000000..013673b251 --- /dev/null +++ b/src/axom/mir/docs/sphinx/index.rst @@ -0,0 +1,33 @@ +.. ## Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level COPYRIGHT file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +======================= +MIR User Documentation +======================= + +Axom's Material Interface Reconstruction (MIR) component provides algorithms for +reconstructing the interfaces between different materials in multimaterial +meshes. The algorithms take Blueprint meshes +containing a coordset, topology, and matset as input and they output a new Blueprint +node with a new coordset, topology, and matset that contains at most 1 material per zone. + +The MIR component also contains some useful components that can be used to develop +other algorithms that process Blueprint meshes. + + +API Documentation +----------------- + +Doxygen generated API documentation can be found here: `API documentation <../../../../doxygen/html/coretop.html>`_ + + +.. toctree:: + :caption: Contents + :maxdepth: 1 + + mir_algorithms + mir_views + mir_clipping + mir_utilities diff --git a/src/axom/mir/docs/sphinx/mir_algorithms.rst b/src/axom/mir/docs/sphinx/mir_algorithms.rst new file mode 100644 index 0000000000..d73d64fcec --- /dev/null +++ b/src/axom/mir/docs/sphinx/mir_algorithms.rst @@ -0,0 +1,159 @@ +.. ## 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) + +****************************************************** +MIR Algorithms +****************************************************** + +The MIR component contains MIR algorithms that will take a Blueprint mesh as input, +perform MIR on it, and output a new Blueprint mesh with the reconstructed output. +A Blueprint mesh is contained in a ``conduit::Node`` and it follows the `Blueprint protocol `_, +which means the node contains specific items that describe the mesh coordinates, topology, fields, and materials. + +####### +Inputs +####### + +MIR algorithms are designed to accept a Conduit node containing various options that can +influence how the algorithm operates. The MIR algorithm copies the options node to the memory +space where it will be used. The only required option is ``matset``, the name of the matset +to operate on. Other options have sensible defaults described in the table below. + ++---------------------------------+------------------------------------------------------+ +| Option | Description | ++=================================+======================================================+ +|``coordsetName: name`` | The name of the new coordset in the output mesh. If | +| | it is not provided, the output coordset will have the| +| | same name as the input coordset. | ++---------------------------------+------------------------------------------------------+ +|``fields:`` | The fields node lets the caller provide a list of | +| | field names that will be processed and added to the | +| | output mesh. The form is *currentName:newName*. If | +| | the *fields* node is not given, the algorithm will | +| | process all input fields. If the fields node is empty| +| | then no fields will be processed. | ++---------------------------------+------------------------------------------------------+ +| ``matset: name`` | A required string argument that specifies the name | +| | of the matset that will be operated on. | ++---------------------------------+------------------------------------------------------+ +| ``matsetName: name`` | An optional string argument that specifies the name | +| | of the matset to create in the output. If the name | +| | is not given, the output matset will have the same | +| | name as the input matset. | ++---------------------------------+------------------------------------------------------+ +| ``originalElementsField: name`` | The name of the field in which to store the original | +| | elements map. The default is "originalElements". | ++---------------------------------+------------------------------------------------------+ +| ``selectedZones: [zone list]`` | An optional argument that provides a list of zone ids| +| | on which to operate. The output mesh will only have | +| | contributions from zone numbers in this list, if it | +| | is given. | ++---------------------------------+------------------------------------------------------+ +| ``topologyName: name`` | The name of the new topology in the output mesh. If | +| | it is not provided, the output topology will have the| +| | same name as the input topology. | ++---------------------------------+------------------------------------------------------+ + +############### +EquiZAlgorithm +############### + +The `Equi-Z MIR algorithm `_ by J. Meredith +is a useful visualization-oriented algorithm for MIR. Equi-Z can reconstruct mixed-material +zones that contain many materials per zone. Whereas many MIR algorithms +produce disjointed element output, Equi-Z creates output that mostly forms continuous +surfaces and shapes. Continuity is achieved by averaging material volume fractions to +the mesh nodes for each material and then performing successive clipping for each +material, using the node-averaged volume fractions to determine where clipping occurs +along each edge. The basic algorithm is lookup-based so shape decomposition for a +clipped-zone can be easily determined. The clipping stage produces numerous zone fragments +that are marked with the appropriate material number and moved into the next material +clipping stage. This concludes when all zones are comprised of only 1 material. From, +there points are made unique and a new output mesh is created. + +Axom's implementation of Equi-Z is data parallel and can run on the CPU and the GPU. +First, the zones of interest are identified and they are classified as clean or mixed. +Clean zones consist of a single material and are pulled out early into a new mesh while mixed +zones are processed further. The Equi-Z algorithm reconstructs zones with boundaries along +material interfaces. The clean zones mesh and reconstructed zones mesh are merged to form +a single output mesh. The mesh may consist of multiple Blueprint shape types in an +unstructured "mixed" topology. + +Axom's implementation supports 2D/3D zones from structured or unstructured topologies +made of Finite Element Zoo elements *(e.g. triangles, quadrilaterals, tetrahedra, pyramids, +wedges, hexahedra, or topologically-compatible mixtures)*. The MIR logic for Equi-Z is +encapsulated in ``axom::mir::EquizAlgorithm``, which is a class that is templated on view objects. +View objects help provide an interface between the Blueprint data and the MIR algorithm. +At a minimum, an execution space and three views are required to instantiate the +``axom::mir::EquiZAlgorithm`` class. The execution space determines which compute backend will +be used to execute the algorithm. The Blueprint data must exist in a compatible +memory space for the execution space. The views are: *CoordsetView*, *TopologyView*, and +*MaterialView*. The *CoordsetView* template argument lets the algorithm access the mesh's +coordset using concrete data types and supports queries that return points. The +*TopologyView* provides a set of operations that can be performed on meshes, mainly a +method for retrieving individual zones that can be used in device kernels. +The *MaterialView* provides an interface for querying matsets. + +Once view types have been created and views have been instantiated, the ``EquiZAlgorithm`` +algorithm can be instantiated and used. The ``EquiZAlgorithm`` class provides a single +``execute()`` method that takes the input mesh, an options node, and a node to contain +the output mesh. The output mesh will exist in the same memory space as the input mesh, +which again, must be compatible with the selected execution space. The ``axom::mir::utilities::blueprint::copy()`` +function can be used to copy Conduit nodes from one memory space to another. + +.. literalinclude:: ../../examples/concentric_circles/runMIR.hpp + :start-after: _equiz_mir_start + :end-before: _equiz_mir_end + :language: C++ + +The MIR output will contain a new field called, by default, *"originalElements"* that +indicates which original zone number gave rise to the reconstructed zone. This field +makes it possible to map back to the original mesh. The name of the field can be changed +by providing a new name by setting "originalElementsField" in the options. + +##################### +Example Application +##################### + +The mir_concentric_circles application generates a uniform mesh with square zones, populated +with circular mixed material shells. The application performs MIR on the input mesh and writes +a file containing the reconstructed mesh. All program arguments (listed in the table below) +are optional. + ++--------------------+---------------------------------------------------------------+ +| Argument | Description | ++====================+===============================================================+ +| --gridsize number | The number of zones along an axis. The default is 5. | ++--------------------+---------------------------------------------------------------+ +| --numcircles number| The number of number of circles to use for material creation. | +| | The default is 2. | ++--------------------+---------------------------------------------------------------+ +| --output filepath | The file path for output files. The default is "output". | ++--------------------+---------------------------------------------------------------+ +| --policy policy | Set the execution policy (seq, omp, cuda, hip) | ++--------------------+---------------------------------------------------------------+ +| --caliper mode | The caliper mode (none, report) | ++--------------------+---------------------------------------------------------------+ + +To run the example program from the Axom build directory, follow these steps: + + ./examples/mir_concentric_circles --gridsize 100 --numcircles 5 --output mir + +.. figure:: figures/mir_concentric_circles.png + :figwidth: 800px + + Diagram showing MIR output from the *mir_concentric_circles* application. + +##################### +Visualization +##################### + +The `VisIt software `_ can be used to +view the Blueprint output from MIR algorithms. Blueprint data is saved in an HDF5 +format and the top level file has a ".root" extension. Open the ".root" file in VisIt +to get started and then add a *FilledBoundary* plot of the material defined on the +mesh topology. Plotting the mesh lines will reveal that there is a single material +per zone. If the input mesh is visualized in a similar manner, zones with multiple +materials will contain different colors for each material. diff --git a/src/axom/mir/docs/sphinx/mir_clipping.rst b/src/axom/mir/docs/sphinx/mir_clipping.rst new file mode 100644 index 0000000000..8ad50a98f0 --- /dev/null +++ b/src/axom/mir/docs/sphinx/mir_clipping.rst @@ -0,0 +1,137 @@ +.. ## 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) + +************* +Clipping +************* + +The MIR component provides a clipping algorithm that can perform isosurface-based +clipping and return volumetric output for zones and partial zones that are "inside" +or "outside" the clip boundary. The clipping algorithm is implemented in the +``axom::mir::clipping::ClipField`` class. The class can be instantiated with several +template arguments that govern where it will execute, which coordset and topology +types it supports, and how it performs intersection. The input to the algorithm is +a Blueprint mesh. When instantiated with coordset and topology views appropriate +for the input data, the algorithm can operate on a wide variety of mesh types. This +includes 2D/3D structured and unstructured topologies that can be represented using +finite elements. + +By default, the algorithm will clip using a field but other intersection routines +can be substituted via a template argument to facilitate creation of clipping using +planes, spheres, surfaces of revolution, etc. The Equi-Z algorithm uses ClipField +with an intersector that examines material volume fractions to determine the clipped geometry. + +####### +Inputs +####### + +Like the MIR algorithms, the clipping algorithm is designed to accept a Conduit node +containing various options that influence how the algorithm operates. The clipping +algorithm copies the options node to the memory space where it will be used. + ++---------------------------------+------------------------------------------------------+ +| Option | Description | ++=================================+======================================================+ +| ``clipField: name`` | A required string argument that specifies the name | +| | of the field that is used for clipping. At present, | +| | the field must be a vertex-associated field. | ++---------------------------------+------------------------------------------------------+ +| ``clipValue: value`` | An optional numeric argument that specifies the | +| | value in the field at which the clip boundary is | +| | defined. The default is 0. | ++---------------------------------+------------------------------------------------------+ +| ``colorField: name`` | If inside=1 and outside=1 then a color field is | +| | generated so it is possible to tell apart regions of | +| | the clip output that were inside or outside the clip | +| | boundary. This field permits the user to change the | +| | name of the color field, which is called "color" by | +| | default. | ++---------------------------------+------------------------------------------------------+ +| ``coordsetName: name`` | The name of the new coordset in the output mesh. If | +| | it is not provided, the output coordset will have the| +| | same name as the input coordset. | ++---------------------------------+------------------------------------------------------+ +|``fields:`` | The fields node lets the caller provide a list of | +| | field names that will be processed and added to the | +| | output mesh. The form is *currentName:newName*. If | +| | the *fields* node is not given, the algorithm will | +| | process all input fields. If the fields node is empty| +| | then no fields will be processed. | ++---------------------------------+------------------------------------------------------+ +| ``inside: number`` | Indicates to the clipping algorithm that it should | +| | preserve zone fragments that were "inside" the clip | +| | boundary. Set to 1 to enable, 0 to disable. The | +| | algorithm will generate these fragments by default. | ++---------------------------------+------------------------------------------------------+ +| ``originalElementsField: name`` | The name of the field in which to store the original | +| | elements map. The default is "originalElements". | ++---------------------------------+------------------------------------------------------+ +| ``outside: number`` | Indicates to the clipping algorithm that it should | +| | preserve zone fragments "outside" the clip boundary. | +| | Set to 1 to enable, 0 to disable. These fragments are| +| | not on by default. | ++---------------------------------+------------------------------------------------------+ +| ``selectedZones: [zone list]`` | An optional argument that provides a list of zone ids| +| | on which to operate. The output mesh will only have | +| | contributions from zone numbers in this list, if it | +| | is given. | ++---------------------------------+------------------------------------------------------+ +| ``topologyName: name`` | The name of the new topology in the output mesh. If | +| | it is not provided, the output topology will have the| +| | same name as the input topology. | ++---------------------------------+------------------------------------------------------+ + +########## +ClipField +########## + +To use the ``ClipField`` class, one must have Blueprint data with at least one vertex-associated +field. Views for the coordset and topology are created and their types are used to instantiate +a ``ClipField`` object. The ``ClipField`` constructor takes a Conduit node for the input Blueprint mesh, a Conduit +node that contains the options, and a 3rd output Conduit node that will contain the clipped +mesh and fields. The input mesh node needs to contain data arrays for coordinates, mesh +topology, and fields. These data must exist in the memory space of the targeted device. +Other Conduit nodes that contain strings or single numbers that can fit within a Conduit +node are safe remaining in host memory. If the mesh is not in the desired memory space, it +can be moved using ``axom::mir::utilities::blueprint::copy()``. + +.. code-block:: cpp + + #include "axom/mir.hpp" + + // Set up views for the mesh in deviceRoot node. + auto coordsetView = axom::mir::views::make_rectilinear_coordset::view(deviceRoot["coordsets/coords"]); + auto topologyView = axom::mir::views::make_rectilinear<3>::view(deviceRoot["topologies/Mesh"]); + + // Make a clipper. + using CoordsetView = decltype(coordsetView); + using TopologyView = decltype(topologyView); + using Clip = axom::mir::clipping::ClipField; + Clip clipper(topologyView, coordsetView); + + // Run the clip algorithm + conduit::Node options; + options["clipField"] = "data"; + options["clipValue"] = 3.5; + options["outside"] = 1; + options["inside"] = 0; + clipper.execute(deviceRoot, options, clipOutput); + + +.. figure:: figures/clipfield.png + :figwidth: 800px + + Diagram showing original mesh colored by clipping field (left), original mesh colored by a radial field (middle), and the clipped mesh colored by the radial field (right). + + +^^^^^^^^^^^^^ +Intersectors +^^^^^^^^^^^^^ + +An intersector is a policy class that is passed as a template argument to ``ClipField``. The +intersector determines how the ``ClipField`` algorithm will generate intersection cases, for +each zone in the mesh. The ``ClipField`` algorithm default intersector uses a field to determine clip +cases, resulting in isosurface behavior for the geometry intersections. Alternative intersectors +can be provided to achieve other types of intersections. diff --git a/src/axom/mir/docs/sphinx/mir_utilities.rst b/src/axom/mir/docs/sphinx/mir_utilities.rst new file mode 100644 index 0000000000..11825ce8c9 --- /dev/null +++ b/src/axom/mir/docs/sphinx/mir_utilities.rst @@ -0,0 +1,238 @@ +.. ## 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) + +****************************************************** +MIR Blueprint Utilities +****************************************************** + +The MIR component contains several useful building blocks for writing algorithms +for Blueprint meshes. + + * Structured as classes with an ``execute()`` method + * Often templated on execution space and views + +####################### +Copying Blueprint Data +####################### + +If a ``conduit::Node`` containing Blueprint data is not in a memory space appropriate +for the target execution space, the data can be copied to a suitable memory space using +the ``axom::mir::utilities::blueprint::copy()`` function. The target execution +space (and thus its memory space) is specified using the ``copy`` function's ``ExecSpace`` +template argument. The ``copy`` function copies the source ``conduit::Node`` to +the destination ``conduit::Node``, making sure to use the appropriate Axom allocator for +non-string bulk arrays (e.g. arrays of ints, floats, doubles, etc.). Data small enough to +fit in a ``conduit::Node`` and strings are left in the host memory space, which lets +algorithms on the host side query them. For data that have been moved to the device, +their sizes and data types can still be queried using normal Conduit mechanisms such +as the ``conduit::Node::dtype()`` method. + +.. code-block:: cpp + + conduit::Node hostMesh, deviceMesh, hostMesh2; + // host->device + axom::mir::utilities::blueprint::copy>(deviceMesh, hostMesh); + // device->host + axom::mir::utilities::blueprint::copy(hostMesh2, deviceMesh); + +############################ +ConduitAllocateThroughAxom +############################ + +When writing algorithms that construct Blueprint data, it is helpful to force Conduit +to allocate its memory through Axom's allocation routines and then make an ``axom::ArrayView`` +of the data in the Conduit node. This prevents data from having to be copied from an Axom +data structure into a Conduit node since it can be constructed from the start inside the +Conduit node. The size of the array must be known. + +The ``axom::mir::utilities::blueprint::ConduitAllocateThroughAxom`` +class is a template class that takes an execution space as a template argument. The class +installs an allocation routine in Conduit that can be used to allocate data through +Axom. The Conduit allocator is set on each ``conduit::Node`` before setting data into +the object. + +.. literalinclude:: ../../clipping/ClipField.hpp + :start-after: _mir_utilities_c2a_begin + :end-before: _mir_utilities_c2a_end + :language: C++ + +########## +ClipField +########## + +The ``axom::mir::clipping::ClipField`` class intersects all the zones in the input Blueprint +mesh with an implicit surface where the selected input field equals zero and produces a new +Blueprint mesh based on the selected zone fragments produced by the intersection. This can be thought +of as an isosurface algorithm but with a volumetric output mesh where the mesh is either inside or +outside of the selected isovalue. The ``ClipField`` class has multiple template arguments to +select the execution space, the type of topology view, the type of coordset view, and the +type of intersector used to determine intersections. The default intersection uses an isosurface- +based intersection method, though other intersectors could be created to perform plane +or sphere intersections. + +.. literalinclude:: ../../tests/mir_clipfield.cpp + :start-after: _mir_utilities_clipfield_start + :end-before: _mir_utilities_clipfield_end + :language: C++ + +################ +CoordsetBlender +################ + +The ``axom::mir::utilities::blueprint::CoordsetBlender`` class takes a ``BlendData`` and makes +a new explicit coordset where each new point corresponds to one blend group. A "BlendData" is +an object that groups several array views that describe a set of blend groups. Each blend group +is formed from a list of node ids and weight values. A new coordinate is formed by looking +up the points in the blend group in the source coordset and multiplying them by their weights +and summing them together to produce the new point for the output coordset. Classes such as +``ClipField`` use ``CoordsetBlender`` to make new coordsets that contain points that were a +combination of multiple points in the input coordset. + +.. literalinclude:: ../../clipping/ClipField.hpp + :start-after: _mir_utilities_coordsetblender_start + :end-before: _mir_utilities_coordsetblender_end + :language: C++ + +################ +CoordsetSlicer +################ + +The ``axom::mir::utilities::blueprint::CoordsetSlicer`` class takes ``SliceData`` and makes a +new explicit coordset where each point corresponds to a single index from the node indices +stored in SliceData. This class can be used to select a subset of a coordset, reorder nodes +in a coordset, or repeat nodes in a coordset. + +.. literalinclude:: ../../utilities/ExtractZones.hpp + :start-after: _mir_utilities_coordsetslicer_begin + :end-before: _mir_utilities_coordsetslicer_end + :language: C++ + +################## +ExtractZones +################## + +The ``axom::mir::utilities::ExtractZones`` class takes a list of selected zone ids and extracts +a new mesh from a source mesh that includes only the selected zones. There is a derived class +``ExtractZonesAndMatset`` that also extracts a matset, if present. + +.. literalinclude:: ../../utilities/ExtractZones.hpp + :start-after: _mir_utilities_extractzones_begin + :end-before: _mir_utilities_extractzones_end + :language: C++ + +############# +FieldBlender +############# + +The ``axom::mir::utilities::blueprint::FieldBlender`` class is similar to the ``CoordsetBlender`` +class, except that it operates on a field instead of coordsets. The class is used to create a +new field that includes values derived from multiple weighted source values. + +############ +FieldSlicer +############ + +The ``axom::mir::utilities::blueprint::FieldSlicer`` class selects specific indices from a +field and makes a new field. + +.. literalinclude:: ../../tests/mir_slicers.cpp + :start-after: _mir_utilities_fieldslicer_begin + :end-before: _mir_utilities_fieldslicer_end + :language: C++ + +################## +MakeUnstructured +################## + +The ``axom::mir::utilities::blueprint::MakeUnstructured`` class takes a structured topology +and creates a new unstructured topology. This class does not need views to wrap the input +structured topology. + +.. literalinclude:: ../../tests/mir_blueprint_utilities.cpp + :start-after: _mir_utilities_makeunstructured_begin + :end-before: _mir_utilities_makeunstructured_end + :language: C++ + +################## +MatsetSlicer +################## + +The ``axom::mir::utilities::blueprint::MatsetSlicer`` class is similar to the ``FieldSlicer`` +class except it slices matsets instead of fields. The same ``SliceData`` can be passed to +MatsetSlicer to pull out and assemble a new matset data for a specific list of zones. + +.. literalinclude:: ../../utilities/ExtractZones.hpp + :start-after: _mir_utilities_matsetslicer_begin + :end-before: _mir_utilities_matsetslicer_end + :language: C++ + +################## +MergeMeshes +################## + +The ``axom::mir::utilities::blueprint::MergeMeshes`` class merges data for coordsets, +topology, and fields from multiple input meshes into a new combined mesh. The class also +supports renaming nodes using a map that converts a local mesh's node ids to the final +output node numbering, enabling meshes to be merged such that some nodes get combined. +A derived class can also merge matsets. + +.. literalinclude:: ../../EquiZAlgorithm.hpp + :start-after: _mir_utilities_mergemeshes_begin + :end-before: _mir_utilities_mergemeshes_end + :language: C++ + +########################### +NodeToZoneRelationBuilder +########################### + +The ``axom::mir::utilities::blueprint::NodeToZoneRelationBuilder`` class creates a Blueprint +O2M (one to many) relation that relates node numbers to the zones that contain them. This mapping +is akin to inverting the normal mesh connectivity which is a map of zones to node ids. The O2M +relation is useful for recentering data from the zones to the nodes. + +.. literalinclude:: ../../tests/mir_node_to_zone_relation.cpp + :start-after: _mir_utilities_n2zrel_begin + :end-before: _mir_utilities_n2zrel_end + :language: C++ + +############### +RecenterFields +############### + +The ``axom::mir::utilities::blueprint::RecenterFields`` class uses an O2M relation to average +field data from multiple values to an averaged value. In Axom, this is used to convert a field +associated with the elements to a new field associated with the nodes. + +.. literalinclude:: ../../tests/mir_blueprint_utilities.cpp + :start-after: _mir_utilities_recenterfield_begin + :end-before: _mir_utilities_recenterfield_end + :language: C++ + +################## +Unique +################## + +The ``axom::mir::utilities::Unique`` class can take an unsorted list of values and produce a +sorted list of unique outputs, along with a list of offsets into the original values to identify +one representative value in the original list for each unique value. This class is used to help +merge points. + +.. literalinclude:: ../../tests/mir_clipfield.cpp + :start-after: _mir_utilities_unique_begin + :end-before: _mir_utilities_unique_end + :language: C++ + +################## +ZoneListBuilder +################## + +The ``axom::mir::utilities::blueprint::ZoneListBuilder`` class takes a matset view and a list +of selected zone ids and makes two output lists of zone ids that correspond to clean zones and +mixed zones (more than 1 material in the zone). + +.. literalinclude:: ../../EquiZAlgorithm.hpp + :start-after: _mir_utilities_zlb_begin + :end-before: _mir_utilities_zlb_end + :language: C++ diff --git a/src/axom/mir/docs/sphinx/mir_views.rst b/src/axom/mir/docs/sphinx/mir_views.rst new file mode 100644 index 0000000000..6918d9a8b2 --- /dev/null +++ b/src/axom/mir/docs/sphinx/mir_views.rst @@ -0,0 +1,300 @@ +.. ## Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level COPYRIGHT file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +****** +Views +****** + +The MIR component provides lightweight, device-compatible, view classes that add a C++ interface +for Blueprint data. Blueprint data defines several object protocols represented with arrays of +various data types. Views can simplify the process of writing algorithms to support Blueprint data. +Views do not own their data so they can be easily copied, making them suitable for use in device +kernels. + +---------- +ArrayView +---------- + +Axom provides ``axom::ArrayView`` to wrap data in a non-owning data structure that can be passed to +kernels. The MIR component provides the ``axom::mir::utilities::blueprint::make_array_view()`` +function to help wrap arrays stored in ``conduit::Node`` to ``axom::ArrayView``. To use the +``make_array_view`` function, one must know the type held within the Conduit node. If that is +not the case, then consider using one of the dispatch ''Node_to_ArrayView'' functions. + +.. code-block:: cpp + + // Make an axom::ArrayView for X coordinate components. + auto x = axom::mir::utilities::blueprint::make_array_view(n_mesh["coordsets/coords/values/x"]); + + +---------- +Coordsets +---------- + +Blueprint supports multiple coordset *(coordinate set)* types: uniform, rectilinear, explicit. Axom provides functions +to explicitly create coordset views for each of these types. + +.. code-block:: cpp + + // Make a 2D uniform coordset view + auto view1 = axom::mir::views::make_uniform_coordset<2>::view(n_mesh["coordsets/coords"]); + // Make a 3D uniform coordset view + auto view2 = axom::mir::views::make_uniform_coordset<3>::view(n_mesh["coordsets/coords"]); + // Make a 2D rectilinear coordset view with float coordinates + auto view3 = axom::mir::views::make_rectilinear_coordset::view(n_mesh["coordsets/coords"]); + // Make a 3D rectilinear coordset view with double coordinates + auto view4 = axom::mir::views::make_rectilinear_coordset::view(n_mesh["coordsets/coords"]); + // Make a 2D explicit coordset view with float coordinates + auto view5 = axom::mir::views::make_explicit_coordset::view(n_mesh["coordsets/coords"]); + // Make a 3D explicit coordset view with double coordinates + auto view6 = axom::mir::views::make_explicit_coordset::view(n_mesh["coordsets/coords"]); + + +---------------- +Topology Views +---------------- + +Topology views provide a layer on top of the Blueprint mesh topology that enables Axom algorithms +to be written while not needing to care specifically about topology types and data types. +Axom provides topology views for structured meshes and unstructured meshes. + +^^^^^^^^^^^^^^^^^^^^^^ +Structured Mesh Views +^^^^^^^^^^^^^^^^^^^^^^ + +The structured mesh topology view, ``axom::mir::views::StructuredTopologyView``, pertains to any of the Blueprint +structured topology types. The ``StructuredTopologyView`` class is a template that takes an indexing policy +as a template argument. The indexing policy computes zone indices and converts to/from +logical/global indices. The ``StridedStructuredIndexingPolicy`` class supports indexing for +strided-structured Blueprint meshes, which are structured meshes that exist over a sub-window +of the overall mesh. There are helper functions for creating structured topology views from +a Conduit node. + +.. code-block:: cpp + + conduit::Node &n_topo1 = n_mesh["topologies/mesh2d"]; + conduit::Node &n_topo2 = n_mesh["topologies/mesh3d"]; + conduit::Node &n_topo3 = n_mesh["topologies/mesh2dss"]; + // Make a 2D structured mesh view from the topology. + auto topologyView1 = axom::mir::views::make_structured<2>::view(n_topo1); + // Make a 3D structured mesh view from the topology. + auto topologyView2 = axom::mir::views::make_structured<2>::view(n_topo2); + // Make a 2D strided-structured mesh view from the topology. + auto topologyView3 = axom::mir::views::make_strided_structured<2>::view(n_topo3); + +^^^^^^^^^^^^^^^^^^^^^^^^ +Unstructured Mesh Views +^^^^^^^^^^^^^^^^^^^^^^^^ + +There are 3 unstructured mesh views, covering single shape meshes, mixed shape meshes, and polyhedral meshes. +The ``axom::mir::views::UnstructuredTopologySingleShapeView`` class wraps a +Blueprint topology that contains a single zone/shape type. The zone type is a template argument +that determines the type of zone that is held within the topology. + +.. code-block:: cpp + + // Make a topology view for a tetrahedral mesh with int connectivity. + namespace bputils = axom::mir::utilities::blueprint; + const conduit::Node &n_topo = n_mesh["topologies/mesh"]; + const auto connView = bputils::make_array_view(n_topo["elements/connectivity"]); + axom::mir::views::UnstructuredTopologySingleShapeView> view(connView); + +There are multiple shape types defined in ``axom/mir/views/Shapes.hpp`` that can be used with +the ``UnstructuredTopologySingleShapeView`` class: *TriShape*, *QuadShape*, *TetShape*, *PyramidShape*, +*WedgeShape*, and *HexShape*. + +Blueprint supports *mixed* topologies that contain multiple shape types. These topologies are +handled using the ``axom::mir::views::UnstructuredTopologyMixedShapeView``. Additional array +views are needed to supply the sizes, offsets, and shapes arrays. + +.. code-block:: cpp + + // A shape map helps map values from the values used in the Blueprint topology to + // the shape ids used in Axom. + const conduit::Node &n_topo = n_mesh["topologies/mesh"]; + const int allocatorID = axom::execution_space::allocatorID(); + axom::Array ids, values; + auto shapeMap = axom::mir::views::buildShapeMap(n_topo, ids, values, allocatorID); + + namespace bputils = axom::mir::utilities::blueprint; + axom::mir::views::UnstructuredTopologyMixedShapeView view( + bputils::make_array_view(n_topo["elements/connectivity"), + bputils::make_array_view(n_topo["elements/sizes"), + bputils::make_array_view(n_topo["elements/offsets"), + bputils::make_array_view(n_topo["elements/shapes"), + shapeMap); + +The final unstructured topology view is ``axom::mir::views::UnstructuredTopologyPolyhedralView`` +and it provides a view interface to polyhedral meshes. + +.. literalinclude:: ../../views/dispatch_unstructured_topology.hpp + :start-after: _mir_views_ph_topoview_begin + :end-before: _mir_views_ph_topoview_end + :language: C++ + + +Once a suitable topology view type has wrapped a Blueprint topology, it can be used in +device kernels to obtain zone information. + +.. code-block:: cpp + + auto topologyView = ... + axom::for_all(topologyView.numberOfZones(), AXOM_LAMBDA(axom::IndexType zoneIndex) + { + // Get the current zone. + const auto zone = topologyView.zone(zoneIndex); + + // Iterate over this zone's nodes. + for(const auto &nodeId : zone.getIds()) + { + // Do something. + } + }); + +---------- +Matsets +---------- + +The MIR component provides material views to wrap Blueprint matsets behind an interface that +supports queries of the matset data without having to care much about its internal representation. +Blueprint provides 4 flavors of matset, each with a different representation. The +``axom::mir::views::UnibufferMaterialView`` class wraps unibuffer matsets, which consist of +several arrays that define materials for each zone in the associated topology. The view's +methods allow algorithms to query the list of materials for each zone. + +.. literalinclude:: ../../tests/mir_views.cpp + :start-after: _mir_views_matsetview_begin + :end-before: _mir_views_matsetview_end + :language: C++ + +---------- +Dispatch +---------- + +There are several helper functions that wrap a Conduit node in a specific view type +and **dispatch** the view to a user-supplied lambda for further processing. The lambda +function will typically be instantiated multiple times to handle various data types and +object types (e.g. coordsets). Generic lambdas can be used to process multiple views +and it is possible to nest dispatch functions to handle added complexity. + +^^^^^^^^^^^ +Array Data +^^^^^^^^^^^ + +Blueprint data can readily be wrapped in ``axom::ArrayView`` using the ``axom::mir::utilities::blueprint::make_array_view()`` +function. There are dispatch functions for ``conduit::Node`` data arrays that automate the +wrapping to ``axom::ArrayView`` and passing the views to a user-supplied lambda. + +To generically wrap any type of datatype supported by Conduit, the ``axom::mir::views::Node_to_ArrayView()`` +function can be used. This template function takes a variable number of ``conduit::Node`` +arguments and a generic lambda function that accepts the view arguments. The lambda gets +instantiated for every supported Conduit data type. + +.. code-block:: cpp + + conduit::Node n; // Assume it contains data values + axom::mir::views::Node_to_ArrayView(n["foo"], n["bar"], [&](auto fooView, auto barView) + { + // Use fooView and barView axom::ArrayView objects to access data. + // They can have different types. + }); + +Using ``axom::mir::views::Node_to_ArrayView`` with multiple data values can instantiate +the supplied lambda many times so be careful. It is more common when wrapping multiple +nodes that they are the same type. The ``axom::mir::views::Node_to_ArrayView_same`` function +ensures that the lambdas get instantiated with views that wrap the Conduit nodes in +array views that of the same type. + +.. code-block:: cpp + + conduit::Node n; // Assume it contains data values + axom::mir::views::Node_to_ArrayView_same(n["foo"], n["bar"], [&](auto fooView, auto barView) + { + // Use fooView and barView axom::ArrayView objects to access data. + // They have the same types. + }); + +When dealing with mesh data structures, it is common to have data that are using only integer +types or only floating-point types. Axom provides functions that limit the lambda instantiation +to only those selected types using the following functions: + + * ``axom::mir::views::IndexNode_to_ArrayView()`` + * ``axom::mir::views::IndexNode_to_ArrayView_same()`` + * ``axom::mir::views::FloatNode_to_ArrayView()`` + * ``axom::mir::views::FloatNode_to_ArrayView_same()`` + +The "Index" functions limit lambda instantiation to common index types signed/unsigned 32/64-bit +integers. The "Float" functions instantiate lambdas with float32 and float64 types. + + +^^^^^^^^^^^ +Coordsets +^^^^^^^^^^^ + +The ``axom::mir::views::dispatch_coordset()`` function can wrap Blueprint coordsets in an +appropriate view and pass it to a lambda function. + +.. code-block:: cpp + + const conduit::Node &n_coordset = n_mesh["coordsets/coords"]; + axom::mir::views::dispatch_coordset(n_coordset, [&](auto coordsetView) { + // Get the C++ type of the coordset. + using CoordsetView = decltype(coordsetView); + // Implement algorithm using coordsetView. + }); + +^^^^^^^^^^^ +Topologies +^^^^^^^^^^^ + +Dispatch functions for topologies enable creation of algorithms that can operate on multiple +topology types through a topology view. These dispatch functions can be called for specific +topology types such as unstructured topologies or they can be called to implement algorithms +that can operate on any topology. + +.. code-block:: cpp + + const conduit::Node &n_topo = n_mesh["topologies/mesh"]; + // Handle rectilinear topology type. + axom::mir::views::dispatch_rectilinear_topology(n_topo, [&](auto topologyView) { + }); + // Handle structured topology types + axom::mir::views::dispatch_structured_topology(n_topo, [&](auto topologyView) { + }); + // Handle unstructured topology types + axom::mir::views::dispatch_unstructured_topology(n_topo, [&](auto topologyView) { + }); + // Handle any topology type. + axom::mir::views::dispatch_topology(n_topo, [&](auto topologyView) { + }); + +Nesting dispatch functions permits the calling code to handle both coordset views and +topology views using a compact amount of code. For portability, the actual +algorithm should be placed in a function or class member method when instantiated from +the anonymous lambda function from the dispatch functions. + +.. code-block:: cpp + + struct Algorithm + { + void execute(const conduit::Node &n_mesh) + { + // Handle product of coordset types and topology types. + axom::mir::views::dispatch_coordset(n_mesh["coordsets/coords"], [&](auto coordsetView) + { + axom::mir::views::dispatch_topologies(n_mesh["topologies/mesh"], [&](auto topologyView) + { + implementation(coordsetView, topologyView); + }); + }); + } + + template + void implementation(CoordsetView coordsetView, TopologyView topologyView) const + { + // Algorithm that involves coordsetView and topologyView. + } + }; + diff --git a/src/axom/mir/doxygen_mainpage.md b/src/axom/mir/doxygen_mainpage.md new file mode 100644 index 0000000000..c0e8dc5a8a --- /dev/null +++ b/src/axom/mir/doxygen_mainpage.md @@ -0,0 +1,46 @@ +MIR {#mirtop} +============= + +Axom's [Mir](@ref axom::mir), (M)aterial (I)interface (R)econstruction component +provides classes that can process Blueprint meshes with mixed materials into new +Blueprint meshes such that there is a single material per zone. In addition, the +MIR component provides useful building blocks for developing algorithms using +Blueprint meshes. There are views that simplify dealing with Conduit data and +utility algorithms for processing and constructing meshes. + +# Design goals {#mirgoals} + +This component's algorithms are mainly delivered as classes that are templated on +an execution space, allowing them to operate on a variety of computing backends. +The algorithms take Conduit nodes (containing Blueprint data) as input and +output new Blueprint data in an output Conduit node. Where possible, algorithms +have been broken out into classes to promote reuse. + +# Views {#mirviews} + +Blueprint defines protocols for representing various mesh and data constructs in +a hierarchical form inside Conduit nodes. There are objects defined for coordinate +sets (coordsets), mesh topologies, mesh fields, material sets (matsets), and there +are various flavors of each type of object. This can make it difficult to write +algorithms against Blueprint data since the data live in Conduit nodes with different +names and they may have different formats. Conduit can also use multiple data types +for any of the data arrays that represent objects. Views were developed to simplify +some of these challenges by providing common templated interfaces that let different +types of objects be accessed in a uniform way. Templating helps to deal with the +data types. The views also provide functions that can wrap a Conduit node +in a suitable view type and then dispatch that view to a generic user-provided lambda, +enabling algorithms to be instantiated for multiple data types with a compact amount +of code. + +# MIR {#mir} + +The MIR component provides an implementation of the Equi-Z MIR algorithm, though +additional algorithms are planned. + +# Utilities {#mirutilities} + +The MIR component provides algorithms for performing useful mesh operations such as +extracting sub-meshes, merging meshes, clipping meshes, and creating relations. +These building blocks can be reused to ease the process of writing additional algorithms +that operate on Blueprint meshes. + diff --git a/src/axom/mir/examples/CMakeLists.txt b/src/axom/mir/examples/CMakeLists.txt new file mode 100644 index 0000000000..41c4a9c100 --- /dev/null +++ b/src/axom/mir/examples/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +# other Axom Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (BSD-3-Clause) + +add_subdirectory(concentric_circles) +add_subdirectory(tutorial_simple) diff --git a/src/axom/mir/examples/concentric_circles/CMakeLists.txt b/src/axom/mir/examples/concentric_circles/CMakeLists.txt new file mode 100644 index 0000000000..22386b074e --- /dev/null +++ b/src/axom/mir/examples/concentric_circles/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +# other Axom Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (BSD-3-Clause) + +set( mir_example_dependencies + core + slic + mir + ) + +# Speed up compilation by separating out the execspaces into different runMIR_xxx.cpp files. +axom_add_executable( + NAME mir_concentric_circles + SOURCES mir_concentric_circles.cpp runMIR_seq.cpp runMIR_omp.cpp runMIR_cuda.cpp runMIR_hip.cpp + OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} + DEPENDS_ON ${mir_example_dependencies} + FOLDER axom/mir/examples + ) + +if(AXOM_ENABLE_TESTS) + set (_policies "seq") + if(RAJA_FOUND AND UMPIRE_FOUND) + blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) + blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) + blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) + endif() + + foreach(_policy ${_policies}) + set(_testname "mir_concentric_circles_${_policy}") + axom_add_test( + NAME ${_testname} + COMMAND mir_concentric_circles + --gridsize 100 + --numcircles 5 + --policy ${_policy} + --disable-write + ) + + set_tests_properties(${_testname} PROPERTIES + PASS_REGULAR_EXPRESSION "Material interface reconstruction time:") + endforeach() +endif() diff --git a/src/axom/mir/examples/concentric_circles/mir_concentric_circles.cpp b/src/axom/mir/examples/concentric_circles/mir_concentric_circles.cpp new file mode 100644 index 0000000000..0ca7867a83 --- /dev/null +++ b/src/axom/mir/examples/concentric_circles/mir_concentric_circles.cpp @@ -0,0 +1,208 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/config.hpp" +#include "axom/core.hpp" // for axom macros +#include "axom/slic.hpp" +#include "axom/mir.hpp" // for Mir classes & functions +#include "runMIR.hpp" + +#include +#include + +#include + +// namespace aliases +namespace mir = axom::mir; +namespace bputils = axom::mir::utilities::blueprint; + +using RuntimePolicy = axom::runtime_policy::Policy; + +//-------------------------------------------------------------------------------- +void conduit_debug_err_handler(const std::string &s1, const std::string &s2, int i1) +{ + SLIC_ERROR( + axom::fmt::format("Error from Conduit: s1={}, s2={}, i1={}", s1, s2, i1)); + // This is on purpose. + while(1) + ; +} + +//-------------------------------------------------------------------------------- +void printNode(const conduit::Node &n) +{ + conduit::Node options; + options["num_children_threshold"] = 10000; + options["num_elements_threshold"] = 10000; + n.to_summary_string_stream(std::cout, options); +} + +//-------------------------------------------------------------------------------- +int runMIR(RuntimePolicy policy, + int gridSize, + int numCircles, + const std::string &outputFilePath, + bool writeFiles) +{ + // Initialize a mesh for testing MIR + auto timer = axom::utilities::Timer(true); + mir::MeshTester tester; + conduit::Node mesh; + { + AXOM_ANNOTATE_SCOPE("generate"); + tester.initTestCaseFive(gridSize, numCircles, mesh); + // printNode(mesh); + } + timer.stop(); + SLIC_INFO("Mesh init time: " << timer.elapsedTimeInMilliSec() << " ms."); + + // Output initial mesh. +#if defined(CONDUIT_RELAY_IO_HDF5_ENABLED) + std::string protocol("hdf5"); +#else + std::string protocol("yaml"); +#endif + if(writeFiles) + { + AXOM_ANNOTATE_SCOPE("save_input"); + conduit::relay::io::blueprint::save_mesh(mesh, "concentric_circles", protocol); + } + + // Begin material interface reconstruction + timer.start(); + conduit::Node options, resultMesh; + options["matset"] = "mat"; + + int retval = 0; + if(policy == RuntimePolicy::seq) + { + retval = runMIR_seq(mesh, options, resultMesh); + } +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #if defined(AXOM_USE_OPENMP) + else if(policy == RuntimePolicy::omp) + { + retval = runMIR_omp(mesh, options, resultMesh); + } + #endif + #if defined(AXOM_USE_CUDA) + else if(policy == RuntimePolicy::cuda) + { + constexpr int CUDA_BLOCK_SIZE = 256; + using cuda_exec = axom::CUDA_EXEC; + retval = runMIR_cuda(mesh, options, resultMesh); + } + #endif + #if defined(AXOM_USE_HIP) + else if(policy == RuntimePolicy::hip) + { + retval = runMIR_hip(mesh, options, resultMesh); + } + #endif +#endif + else + { + retval = -1; + SLIC_ERROR("Unhandled policy."); + } + timer.stop(); + SLIC_INFO("Material interface reconstruction time: " + << timer.elapsedTimeInMilliSec() << " ms."); + + // Output results + if(writeFiles) + { + AXOM_ANNOTATE_SCOPE("save_output"); + conduit::relay::io::blueprint::save_mesh(resultMesh, outputFilePath, protocol); + } + + return retval; +} + +//-------------------------------------------------------------------------------- +int main(int argc, char **argv) +{ + axom::slic::SimpleLogger logger(axom::slic::message::Info); + + // Define command line options. + bool handler = true; + int gridSize = 5; + int numCircles = 2; + bool disable_write = false; + std::string outputFilePath("output"); + axom::CLI::App app; + app.add_flag("--handler", handler) + ->description("Install a custom error handler that loops forever.") + ->capture_default_str(); + app.add_option("--gridsize", gridSize) + ->check(axom::CLI::PositiveNumber) + ->description("The number of zones along an axis."); + app.add_option("--numcircles", numCircles) + ->check(axom::CLI::PositiveNumber) + ->description("The number of circles to use for material creation."); + app.add_option("--output", outputFilePath) + ->description("The file path for HDF5/YAML output files"); + app.add_flag("--disable-write", disable_write) + ->description("Disable writing data files"); + +#if defined(AXOM_USE_CALIPER) + std::string annotationMode("report"); + app.add_option("--caliper", annotationMode) + ->description( + "caliper annotation mode. Valid options include 'none' and 'report'. " + "Use 'help' to see full list.") + ->capture_default_str() + ->check(axom::utilities::ValidCaliperMode); +#endif + + RuntimePolicy policy {RuntimePolicy::seq}; + std::stringstream pol_sstr; + pol_sstr << "Set MIR runtime policy method."; +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; + #ifdef AXOM_USE_OPENMP + pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; + #endif + #ifdef AXOM_USE_CUDA + pol_sstr << "\nSet to 'cuda' or 2 to use the RAJA CUDA policy."; + #endif + #ifdef AXOM_USE_HIP + pol_sstr << "\nSet to 'hip' or 3 to use the RAJA HIP policy."; + #endif +#endif + app.add_option("-p, --policy", policy, pol_sstr.str()) + ->capture_default_str() + ->transform( + axom::CLI::CheckedTransformer(axom::runtime_policy::s_nameToPolicy)); + + // Parse command line options. + app.parse(argc, argv); + + if(handler) + { + conduit::utils::set_error_handler(conduit_debug_err_handler); + } +#if defined(AXOM_USE_CALIPER) + axom::utilities::raii::AnnotationsWrapper annotations_raii_wrapper( + annotationMode); +#endif + + int retval = 0; + try + { + retval = runMIR(policy, gridSize, numCircles, outputFilePath, !disable_write); + } + catch(std::invalid_argument const &e) + { + SLIC_WARNING("Bad input. " << e.what()); + retval = -2; + } + catch(std::out_of_range const &e) + { + SLIC_WARNING("Integer overflow. " << e.what()); + retval = -3; + } + return retval; +} diff --git a/src/axom/mir/examples/concentric_circles/runMIR.hpp b/src/axom/mir/examples/concentric_circles/runMIR.hpp new file mode 100644 index 0000000000..181add255b --- /dev/null +++ b/src/axom/mir/examples/concentric_circles/runMIR.hpp @@ -0,0 +1,86 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#ifndef AXOM_MIR_EXAMPLES_CONCENTRIC_CIRCLES_RUNMIR_HPP +#define AXOM_MIR_EXAMPLES_CONCENTRIC_CIRCLES_RUNMIR_HPP +#include "axom/config.hpp" +#include "axom/core.hpp" // for axom macros +#include "axom/slic.hpp" +#include "axom/mir.hpp" // for Mir classes & functions + +template +int runMIR(const conduit::Node &hostMesh, + const conduit::Node &options, + conduit::Node &hostResult) +{ + namespace bputils = axom::mir::utilities::blueprint; + using namespace axom::mir::views; + SLIC_INFO(axom::fmt::format("Using policy {}", + axom::execution_space::name())); + + // Check materials. + constexpr int MAXMATERIALS = 20; + auto materialInfo = materials(hostMesh["matsets/mat"]); + if(materialInfo.size() >= MAXMATERIALS) + { + SLIC_WARNING( + axom::fmt::format("To use more than {} materials, recompile with " + "larger MAXMATERIALS value.", + MAXMATERIALS)); + return -4; + } + + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + AXOM_ANNOTATE_BEGIN("runMIR"); + // _equiz_mir_start + // Make views (we know beforehand which types to make) + using CoordsetView = ExplicitCoordsetView; + CoordsetView coordsetView( + bputils::make_array_view(deviceMesh["coordsets/coords/values/x"]), + bputils::make_array_view(deviceMesh["coordsets/coords/values/y"])); + + using TopoView = UnstructuredTopologySingleShapeView>; + TopoView topoView(bputils::make_array_view( + deviceMesh["topologies/mesh/elements/connectivity"])); + + using MatsetView = UnibufferMaterialView; + MatsetView matsetView; + matsetView.set( + bputils::make_array_view(deviceMesh["matsets/mat/material_ids"]), + bputils::make_array_view(deviceMesh["matsets/mat/volume_fractions"]), + bputils::make_array_view(deviceMesh["matsets/mat/sizes"]), + bputils::make_array_view(deviceMesh["matsets/mat/offsets"]), + bputils::make_array_view(deviceMesh["matsets/mat/indices"])); + + using MIR = + axom::mir::EquiZAlgorithm; + MIR m(topoView, coordsetView, matsetView); + conduit::Node deviceResult; + m.execute(deviceMesh, options, deviceResult); + // _equiz_mir_end + + AXOM_ANNOTATE_END("runMIR"); + + bputils::copy(hostResult, deviceResult); + + return 0; +} + +// Prototypes. +int runMIR_seq(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_omp(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_cuda(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_hip(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); + +#endif diff --git a/src/axom/mir/examples/concentric_circles/runMIR_cuda.cpp b/src/axom/mir/examples/concentric_circles/runMIR_cuda.cpp new file mode 100644 index 0000000000..3db6cc827f --- /dev/null +++ b/src/axom/mir/examples/concentric_circles/runMIR_cuda.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_CUDA) +int runMIR_cuda(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + constexpr int CUDA_BLOCK_SIZE = 256; + using cuda_exec = axom::CUDA_EXEC; + return runMIR(mesh, options, result); +} +#else +int runMIR_cuda(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/concentric_circles/runMIR_hip.cpp b/src/axom/mir/examples/concentric_circles/runMIR_hip.cpp new file mode 100644 index 0000000000..b191a3615c --- /dev/null +++ b/src/axom/mir/examples/concentric_circles/runMIR_hip.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_HIP) +int runMIR_hip(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + constexpr int HIP_BLOCK_SIZE = 64; + using hip_exec = axom::HIP_EXEC; + return runMIR(mesh, options, result); +} +#else +int runMIR_hip(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/concentric_circles/runMIR_omp.cpp b/src/axom/mir/examples/concentric_circles/runMIR_omp.cpp new file mode 100644 index 0000000000..5b96df37bb --- /dev/null +++ b/src/axom/mir/examples/concentric_circles/runMIR_omp.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && \ + defined(AXOM_USE_OPENMP) +int runMIR_omp(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + return runMIR(mesh, options, result); +} +#else +int runMIR_omp(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/concentric_circles/runMIR_seq.cpp b/src/axom/mir/examples/concentric_circles/runMIR_seq.cpp new file mode 100644 index 0000000000..eebdb27f17 --- /dev/null +++ b/src/axom/mir/examples/concentric_circles/runMIR_seq.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +int runMIR_seq(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + return runMIR(mesh, options, result); +} diff --git a/src/axom/mir/examples/tutorial_simple/CMakeLists.txt b/src/axom/mir/examples/tutorial_simple/CMakeLists.txt new file mode 100644 index 0000000000..2caec4f81e --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/CMakeLists.txt @@ -0,0 +1,65 @@ +# Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +# other Axom Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (BSD-3-Clause) + +set( mir_example_dependencies + core + slic + mir + ) + +# Break the different MIR cases into multiple files to speed up compilation. +set(mir_tutorial_simple_sources + mir_tutorial_simple.cpp + runMIR_seq.cpp + runMIR_seq_tri.cpp + runMIR_seq_quad.cpp + runMIR_seq_hex.cpp + runMIR_omp.cpp + runMIR_omp_tri.cpp + runMIR_omp_quad.cpp + runMIR_omp_hex.cpp + runMIR_cuda.cpp + runMIR_cuda_tri.cpp + runMIR_cuda_quad.cpp + runMIR_cuda_hex.cpp + runMIR_hip.cpp + runMIR_hip_tri.cpp + runMIR_hip_quad.cpp + runMIR_hip_hex.cpp + ) + +axom_add_executable( + NAME mir_tutorial_simple + SOURCES ${mir_tutorial_simple_sources} + OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} + DEPENDS_ON ${mir_example_dependencies} + FOLDER axom/mir/examples + ) + +if(AXOM_ENABLE_TESTS) + set(_test_numbers 1 2 3 4 5) + set (_policies "seq") + if(RAJA_FOUND AND UMPIRE_FOUND) + blt_list_append(TO _policies ELEMENTS "omp" IF AXOM_ENABLE_OPENMP) + blt_list_append(TO _policies ELEMENTS "cuda" IF AXOM_ENABLE_CUDA) + blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) + endif() + + foreach(_policy ${_policies}) + foreach(_testnum ${_test_numbers}) + set(_testname "mir_tutorial_simple_${_policy}_${_testnum}") + axom_add_test( + NAME ${_testname} + COMMAND mir_tutorial_simple + --test-case ${_testnum} + --policy ${_policy} + --disable-write + ) + + set_tests_properties(${_testname} PROPERTIES + PASS_REGULAR_EXPRESSION "Reconstruction time:") + endforeach() + endforeach() +endif() diff --git a/src/axom/mir/examples/tutorial_simple/mir_tutorial_simple.cpp b/src/axom/mir/examples/tutorial_simple/mir_tutorial_simple.cpp new file mode 100644 index 0000000000..e64a3eee54 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/mir_tutorial_simple.cpp @@ -0,0 +1,267 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/slam.hpp" +#include "axom/mir.hpp" + +#include "runMIR.hpp" + +#include +#include + +#include + +// namespace aliases +namespace numerics = axom::numerics; +namespace slam = axom::slam; +namespace mir = axom::mir; +namespace fs = axom::utilities::filesystem; +namespace bputils = axom::mir::utilities::blueprint; + +using RuntimePolicy = axom::runtime_policy::Policy; + +// Enable when EquiZ supports iteration. +// #define AXOM_EQUIZ_SUPPORTS_ITERATION + +//-------------------------------------------------------------------------------- +/// Contain program options. +struct Input +{ + int m_test_case {1}; // valid values 1,2,3,4,5 + bool m_should_iterate {false}; + int m_iter_count {0}; + double m_iter_percent {0.}; + bool m_verbose {false}; + bool m_disable_write {false}; + std::string m_output_dir {}; + RuntimePolicy m_policy {RuntimePolicy::seq}; + std::string m_annotationMode {"report"}; + axom::CLI::App m_app {}; + + /// Parse command line. + int parse(int argc, char **argv) + { + m_app.add_option("--test-case", m_test_case) + ->check(axom::CLI::Range(1, 5)) + ->description("Select the test case."); + + m_output_dir = axom::utilities::filesystem::getCWD(); + m_app.add_option("--output-dir", m_output_dir) + ->check(axom::CLI::ExistingDirectory) + ->description("The directory for HDF5/YAML output files"); + +#if defined(AXOM_EQUIZ_SUPPORTS_ITERATION) + m_app.add_option("--iter-count", m_iter_count) + ->check(axom::CLI::Range(1, 100)) + ->description("The number of iterations for MIR"); + + m_app.add_option("--iter-percent", m_iter_percent) + ->check(axom::CLI::Bound(1.e-6, 10.) + ->description("The percent error for iterative MIR"); +#endif + + m_app.add_flag("--verbose", m_verbose)->description("Verbose output"); + m_app.add_flag("--disable-write", m_disable_write)->description("Disable writing data files"); + +#if defined(AXOM_USE_CALIPER) + m_app.add_option("--caliper", m_annotationMode) + ->description( + "caliper annotation mode. Valid options include 'none' and 'report'. ") + ->capture_default_str() + ->check(axom::utilities::ValidCaliperMode); +#endif + + std::stringstream pol_sstr; + pol_sstr << "Set MIR runtime policy."; +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + pol_sstr << "\nSet to 'seq' or 0 to use the RAJA sequential policy."; + #ifdef AXOM_USE_OPENMP + pol_sstr << "\nSet to 'omp' or 1 to use the RAJA OpenMP policy."; + #endif + #ifdef AXOM_USE_CUDA + pol_sstr << "\nSet to 'cuda' or 2 to use the RAJA CUDA policy."; + #endif + #ifdef AXOM_USE_HIP + pol_sstr << "\nSet to 'hip' or 3 to use the RAJA HIP policy."; + #endif +#endif + m_app.add_option("-p, --policy", m_policy, pol_sstr.str()) + ->capture_default_str() + ->transform( + axom::CLI::CheckedTransformer(axom::runtime_policy::s_nameToPolicy)); + + // Parse command line options. + try + { + m_app.parse(argc, argv); + } + catch(const axom::CLI::ParseError &e) + { + return m_app.exit(e); + } + + return 0; + } + + bool shouldIterate() const { return m_should_iterate; } + int numIterations() const { return m_iter_count; } + int iterPercentage() const { return m_iter_percent; } + bool writeFiles() const { return !m_disable_write; } +}; + +//-------------------------------------------------------------------------------- +/// Print a Conduit node. +void printNode(const conduit::Node &n) +{ + conduit::Node options; + options["num_children_threshold"] = 10000; + options["num_elements_threshold"] = 10000; + n.to_summary_string_stream(std::cout, options); +} + +//-------------------------------------------------------------------------------- +/*! + * \brief Tutorial main showing how to initialize test cases and perform mir. + */ +int main(int argc, char **argv) +{ + axom::slic::SimpleLogger logger(axom::slic::message::Info); + + // Parse arguments + Input params; + int retval = params.parse(argc, argv); + if(retval != 0) + { + return retval; + } +#if defined(AXOM_USE_CALIPER) + axom::utilities::raii::AnnotationsWrapper annotations_raii_wrapper( + params.m_annotationMode); +#endif + + // Make the mesh + conduit::Node mesh; + mir::MeshTester tester; + auto timer = axom::utilities::Timer(true); + switch(params.m_test_case) + { + case 1: + tester.initTestCaseOne(mesh); + break; + case 2: + tester.initTestCaseTwo(mesh); + break; + case 3: + tester.initTestCaseThree(mesh); + break; + case 4: + tester.initTestCaseFour(mesh); + break; + case 5: + { + constexpr int GRIDSIZE = 25; + constexpr int MAXMATERIALS = 12; + tester.initTestCaseFive(GRIDSIZE, MAXMATERIALS, mesh); + } + break; + case 6: + { + constexpr int GRIDSIZE = 15; + constexpr int MAXMATERIALS = 3; + tester.initTestCaseSix(GRIDSIZE, MAXMATERIALS, mesh); + } + break; + } + timer.stop(); + SLIC_INFO("Mesh init time: " << timer.elapsedTimeInMilliSec() << " ms."); + +#if defined(CONDUIT_RELAY_IO_HDF5_ENABLED) + std::string protocol("hdf5"); +#else + std::string protocol("yaml"); +#endif + + // Save input mesh + if(params.writeFiles()) + { + std::string filepath, filename("inputMesh"); + filepath = + axom::utilities::filesystem::joinPath(params.m_output_dir, filename); + conduit::relay::io::blueprint::save_mesh(mesh, filepath, protocol); + } + if(params.m_verbose) + { + SLIC_INFO("Initial mesh:"); + printNode(mesh); + } + + // Begin material interface reconstruction + timer.start(); + + // Set up options. + conduit::Node options; + options["matset"] = "mat"; +#if defined(AXOM_EQUIZ_SUPPORTS_ITERATION) + // Future options + options["iterate"] = params.shouldIterate() ? 1 : 0; + options["iterate_percentage"] = params.iterPercentage(); +#endif + + // Run MIR. Note - the runMIR_xxx functions currently handle just the + // topology types that are created by MeshTester: unstructured (tet, quad, hex). + conduit::Node resultMesh; + if(params.m_policy == RuntimePolicy::seq) + { + retval = runMIR_seq(mesh, options, resultMesh); + } +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #if defined(AXOM_USE_OPENMP) + else if(params.m_policy == RuntimePolicy::omp) + { + retval = runMIR_omp(mesh, options, resultMesh); + } + #endif + #if defined(AXOM_USE_CUDA) + else if(params.m_policy == RuntimePolicy::cuda) + { + retval = runMIR_cuda(mesh, options, resultMesh); + } + #endif + #if defined(AXOM_USE_HIP) + else if(params.m_policy == RuntimePolicy::hip) + { + retval = runMIR_hip(mesh, options, resultMesh); + } + #endif +#endif + else + { + retval = -1; + SLIC_ERROR("Unhandled policy."); + } + timer.stop(); + SLIC_INFO("Reconstruction time: " << timer.elapsedTimeInMilliSec() << " ms."); + + // Save output. + if(retval == 0 && params.writeFiles()) + { + std::string filepath, filename("processedMesh"); + filepath = + axom::utilities::filesystem::joinPath(params.m_output_dir, filename); + conduit::relay::io::blueprint::save_mesh(resultMesh, filepath, protocol); + } + + if(params.m_verbose) + { + SLIC_INFO("Final mesh:"); + printNode(resultMesh); + } + + return retval; +} + +//-------------------------------------------------------------------------------- diff --git a/src/axom/mir/examples/tutorial_simple/runMIR.hpp b/src/axom/mir/examples/tutorial_simple/runMIR.hpp new file mode 100644 index 0000000000..bf41b68c47 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR.hpp @@ -0,0 +1,209 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#ifndef AXOM_MIR_EXAMPLES_TUTORIAL_SIMPLE_RUNMIR_HPP +#define AXOM_MIR_EXAMPLES_TUTORIAL_SIMPLE_RUNMIR_HPP +#include "axom/config.hpp" +#include "axom/core.hpp" // for axom macros +#include "axom/slic.hpp" +#include "axom/mir.hpp" // for Mir classes & functions + +#include + +//-------------------------------------------------------------------------------- +/*! + * \brief Run MIR on the tri input mesh. + * + * \tparam ExecSpace The execution space where the algorithm will run. + * + * \param hostMesh A conduit node that contains the test mesh. + * \param options A conduit node that contains the test mesh. + * \param hostResult A conduit node that will contain the MIR results. + */ +template +int runMIR_tri(const conduit::Node &hostMesh, + const conduit::Node &options, + conduit::Node &hostResult) +{ + namespace bputils = axom::mir::utilities::blueprint; + std::string shape = hostMesh["topologies/mesh/elements/shape"].as_string(); + SLIC_INFO(axom::fmt::format("Using policy {}", + axom::execution_space::name())); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + conduit::Node &n_coordset = deviceMesh["coordsets/coords"]; + conduit::Node &n_topo = deviceMesh["topologies/mesh"]; + conduit::Node &n_matset = deviceMesh["matsets/mat"]; + auto connView = + bputils::make_array_view(n_topo["elements/connectivity"]); + + // Make matset view. (There's often 1 more material so add 1) + constexpr int MAXMATERIALS = 12; + using MatsetView = + axom::mir::views::UnibufferMaterialView; + MatsetView matsetView; + matsetView.set(bputils::make_array_view(n_matset["material_ids"]), + bputils::make_array_view(n_matset["volume_fractions"]), + bputils::make_array_view(n_matset["sizes"]), + bputils::make_array_view(n_matset["offsets"]), + bputils::make_array_view(n_matset["indices"])); + + // Make Coord/Topo views. + conduit::Node deviceResult; + auto coordsetView = + axom::mir::views::make_explicit_coordset::view(n_coordset); + using CoordsetView = decltype(coordsetView); + using TopologyView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::TriShape>; + TopologyView topologyView(connView); + + using MIR = + axom::mir::EquiZAlgorithm; + MIR m(topologyView, coordsetView, matsetView); + m.execute(deviceMesh, options, deviceResult); + + // device->host + bputils::copy(hostResult, deviceResult); + + return 0; +} + +//-------------------------------------------------------------------------------- +/*! + * \brief Run MIR on the quad input mesh. + * + * \tparam ExecSpace The execution space where the algorithm will run. + * + * \param hostMesh A conduit node that contains the test mesh. + * \param options A conduit node that contains the test mesh. + * \param hostResult A conduit node that will contain the MIR results. + */ +template +int runMIR_quad(const conduit::Node &hostMesh, + const conduit::Node &options, + conduit::Node &hostResult) +{ + namespace bputils = axom::mir::utilities::blueprint; + SLIC_INFO(axom::fmt::format("Using policy {}", + axom::execution_space::name())); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + conduit::Node &n_coordset = deviceMesh["coordsets/coords"]; + conduit::Node &n_topo = deviceMesh["topologies/mesh"]; + conduit::Node &n_matset = deviceMesh["matsets/mat"]; + auto connView = + bputils::make_array_view(n_topo["elements/connectivity"]); + + // Make matset view. (There's often 1 more material so add 1) + constexpr int MAXMATERIALS = 12; + using MatsetView = + axom::mir::views::UnibufferMaterialView; + MatsetView matsetView; + matsetView.set(bputils::make_array_view(n_matset["material_ids"]), + bputils::make_array_view(n_matset["volume_fractions"]), + bputils::make_array_view(n_matset["sizes"]), + bputils::make_array_view(n_matset["offsets"]), + bputils::make_array_view(n_matset["indices"])); + + // Make Coord/Topo views. + conduit::Node deviceResult; + auto coordsetView = + axom::mir::views::make_explicit_coordset::view(n_coordset); + using CoordsetView = decltype(coordsetView); + using TopologyView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::QuadShape>; + TopologyView topologyView(connView); + + using MIR = + axom::mir::EquiZAlgorithm; + MIR m(topologyView, coordsetView, matsetView); + m.execute(deviceMesh, options, deviceResult); + + // device->host + bputils::copy(hostResult, deviceResult); + + return 0; +} + +//-------------------------------------------------------------------------------- +/*! + * \brief Run MIR on the hex input mesh. + * + * \tparam ExecSpace The execution space where the algorithm will run. + * + * \param hostMesh A conduit node that contains the test mesh. + * \param options A conduit node that contains the test mesh. + * \param hostResult A conduit node that will contain the MIR results. + */ +template +int runMIR_hex(const conduit::Node &hostMesh, + const conduit::Node &options, + conduit::Node &hostResult) +{ + namespace bputils = axom::mir::utilities::blueprint; + SLIC_INFO(axom::fmt::format("Using policy {}", + axom::execution_space::name())); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + conduit::Node &n_coordset = deviceMesh["coordsets/coords"]; + conduit::Node &n_topo = deviceMesh["topologies/mesh"]; + conduit::Node &n_matset = deviceMesh["matsets/mat"]; + auto connView = + bputils::make_array_view(n_topo["elements/connectivity"]); + + // Make matset view. (There's often 1 more material so add 1) + constexpr int MAXMATERIALS = 12; + using MatsetView = + axom::mir::views::UnibufferMaterialView; + MatsetView matsetView; + matsetView.set(bputils::make_array_view(n_matset["material_ids"]), + bputils::make_array_view(n_matset["volume_fractions"]), + bputils::make_array_view(n_matset["sizes"]), + bputils::make_array_view(n_matset["offsets"]), + bputils::make_array_view(n_matset["indices"])); + + // Make Coord/Topo views. + conduit::Node deviceResult; + auto coordsetView = + axom::mir::views::make_explicit_coordset::view(n_coordset); + using CoordsetView = decltype(coordsetView); + using TopologyView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::HexShape>; + TopologyView topologyView(connView); + + using MIR = + axom::mir::EquiZAlgorithm; + MIR m(topologyView, coordsetView, matsetView); + m.execute(deviceMesh, options, deviceResult); + + // device->host + bputils::copy(hostResult, deviceResult); + + return 0; +} + +// Prototypes. +int runMIR_seq(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_omp(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_cuda(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_hip(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); + +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_cuda.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_cuda.cpp new file mode 100644 index 0000000000..c2b4ab8ff2 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_cuda.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_CUDA) +// Prototypes +int runMIR_cuda_tri(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_cuda_quad(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_cuda_hex(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); + +int runMIR_cuda(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + std::string shape = mesh["topologies/mesh/elements/shape"].as_string(); + int retval = 0; + if(shape == "tri") + retval = runMIR_cuda_tri(mesh, options, result); + else if(shape == "quad") + retval = runMIR_cuda_quad(mesh, options, result); + else if(shape == "hex") + retval = runMIR_cuda_hex(mesh, options, result); + return retval; +} +#else +int runMIR_cuda(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_cuda_hex.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_cuda_hex.cpp new file mode 100644 index 0000000000..6fcdbf8e49 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_cuda_hex.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_CUDA) +int runMIR_cuda_hex(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + constexpr int CUDA_BLOCK_SIZE = 256; + using cuda_exec = axom::CUDA_EXEC; + return runMIR_hex(mesh, options, result); +} +#else +int runMIR_cuda_hex(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_cuda_quad.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_cuda_quad.cpp new file mode 100644 index 0000000000..757e8a095c --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_cuda_quad.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_CUDA) +int runMIR_cuda_quad(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + constexpr int CUDA_BLOCK_SIZE = 256; + using cuda_exec = axom::CUDA_EXEC; + return runMIR_quad(mesh, options, result); +} +#else +int runMIR_cuda_quad(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_cuda_tri.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_cuda_tri.cpp new file mode 100644 index 0000000000..8cd4749465 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_cuda_tri.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_CUDA) +int runMIR_cuda_tri(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + constexpr int CUDA_BLOCK_SIZE = 256; + using cuda_exec = axom::CUDA_EXEC; + return runMIR_tri(mesh, options, result); +} +#else +int runMIR_cuda_tri(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_hip.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_hip.cpp new file mode 100644 index 0000000000..32a25e6389 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_hip.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_HIP) +// Prototypes +int runMIR_hip_tri(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_hip_quad(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_hip_hex(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); + +int runMIR_hip(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + std::string shape = mesh["topologies/mesh/elements/shape"].as_string(); + int retval = 0; + if(shape == "tri") + retval = runMIR_hip_tri(mesh, options, result); + else if(shape == "quad") + retval = runMIR_hip_quad(mesh, options, result); + else if(shape == "hex") + retval = runMIR_hip_hex(mesh, options, result); + return retval; +} +#else +int runMIR_hip(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_hip_hex.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_hip_hex.cpp new file mode 100644 index 0000000000..5b349be5f6 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_hip_hex.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_HIP) +int runMIR_hip_hex(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + constexpr int HIP_BLOCK_SIZE = 64; + using hip_exec = axom::HIP_EXEC; + return runMIR_hex(mesh, options, result); +} +#else +int runMIR_hip_hex(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_hip_quad.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_hip_quad.cpp new file mode 100644 index 0000000000..9e20f66aef --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_hip_quad.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_HIP) +int runMIR_hip_quad(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + constexpr int HIP_BLOCK_SIZE = 64; + using hip_exec = axom::HIP_EXEC; + return runMIR_quad(mesh, options, result); +} +#else +int runMIR_hip_quad(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_hip_tri.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_hip_tri.cpp new file mode 100644 index 0000000000..38735d8385 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_hip_tri.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && defined(AXOM_USE_HIP) +int runMIR_hip_tri(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + constexpr int HIP_BLOCK_SIZE = 64; + using hip_exec = axom::HIP_EXEC; + return runMIR_tri(mesh, options, result); +} +#else +int runMIR_hip_tri(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_omp.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_omp.cpp new file mode 100644 index 0000000000..302a991e71 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_omp.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && \ + defined(AXOM_USE_OPENMP) + +// Prototypes +int runMIR_omp_tri(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_omp_quad(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_omp_hex(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); + +int runMIR_omp(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + std::string shape = mesh["topologies/mesh/elements/shape"].as_string(); + int retval = 0; + if(shape == "tri") + retval = runMIR_omp_tri(mesh, options, result); + else if(shape == "quad") + retval = runMIR_omp_quad(mesh, options, result); + else if(shape == "hex") + retval = runMIR_omp_hex(mesh, options, result); + return retval; +} +#else +int runMIR_omp(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_omp_hex.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_omp_hex.cpp new file mode 100644 index 0000000000..9a17743c47 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_omp_hex.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && \ + defined(AXOM_USE_OPENMP) +int runMIR_omp_hex(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + return runMIR_hex(mesh, options, result); +} +#else +int runMIR_omp_hex(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_omp_quad.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_omp_quad.cpp new file mode 100644 index 0000000000..b99644841b --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_omp_quad.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && \ + defined(AXOM_USE_OPENMP) +int runMIR_omp_quad(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + return runMIR_quad(mesh, options, result); +} +#else +int runMIR_omp_quad(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_omp_tri.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_omp_tri.cpp new file mode 100644 index 0000000000..538aa701f2 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_omp_tri.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && \ + defined(AXOM_USE_OPENMP) +int runMIR_omp_tri(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + return runMIR_tri(mesh, options, result); +} +#else +int runMIR_omp_tri(const conduit::Node &AXOM_UNUSED_PARAM(mesh), + const conduit::Node &AXOM_UNUSED_PARAM(options), + conduit::Node &AXOM_UNUSED_PARAM(result)) +{ + return 0; +} +#endif diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_seq.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_seq.cpp new file mode 100644 index 0000000000..8fec795f25 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_seq.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +// Prototypes +int runMIR_seq_tri(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_seq_quad(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); +int runMIR_seq_hex(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result); + +int runMIR_seq(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + std::string shape = mesh["topologies/mesh/elements/shape"].as_string(); + int retval = 0; + if(shape == "tri") + retval = runMIR_seq_tri(mesh, options, result); + else if(shape == "quad") + retval = runMIR_seq_quad(mesh, options, result); + else if(shape == "hex") + retval = runMIR_seq_hex(mesh, options, result); + return retval; +} diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_seq_hex.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_seq_hex.cpp new file mode 100644 index 0000000000..a3bb0d53e9 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_seq_hex.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +int runMIR_seq_hex(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + return runMIR_hex(mesh, options, result); +} diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_seq_quad.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_seq_quad.cpp new file mode 100644 index 0000000000..0bef896d62 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_seq_quad.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +int runMIR_seq_quad(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + return runMIR_quad(mesh, options, result); +} diff --git a/src/axom/mir/examples/tutorial_simple/runMIR_seq_tri.cpp b/src/axom/mir/examples/tutorial_simple/runMIR_seq_tri.cpp new file mode 100644 index 0000000000..75dc064860 --- /dev/null +++ b/src/axom/mir/examples/tutorial_simple/runMIR_seq_tri.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "runMIR.hpp" + +int runMIR_seq_tri(const conduit::Node &mesh, + const conduit::Node &options, + conduit::Node &result) +{ + return runMIR_tri(mesh, options, result); +} diff --git a/src/axom/mir/future/ClipFieldFilter.cpp b/src/axom/mir/future/ClipFieldFilter.cpp new file mode 100644 index 0000000000..c25e1d5d8e --- /dev/null +++ b/src/axom/mir/future/ClipFieldFilter.cpp @@ -0,0 +1,105 @@ +// 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/mir/ClipFieldFilter.hpp" + +// clang-format off +#if defined (AXOM_USE_RAJA) && defined (AXOM_USE_UMPIRE) + using seq_exec = axom::SEQ_EXEC; + + #if defined(AXOM_USE_OPENMP) + using omp_exec = axom::OMP_EXEC; + #endif + + #if defined(AXOM_USE_CUDA) && defined(__CUDACC__) + constexpr int CUDA_BLOCK_SIZE = 256; + using cuda_exec = axom::CUDA_EXEC; + #endif + + #if defined(AXOM_USE_HIP) + constexpr int HIP_BLOCK_SIZE = 64; + using hip_exec = axom::HIP_EXEC; + #endif +#endif +// clang-format on + +namespace axom +{ +namespace mir +{ +namespace clipping +{ +void ClipFieldFilter::execute(const conduit::Node &n_input, + const conduit::Node &n_options, + conduit::Node &n_output) +{ + ClipOptions opts(n_options); + const std::string clipFieldName = opts.clipField(); + + const conduit::Node &n_fields = n_input.fetch_existing("fields"); + const conduit::Node &n_clipField = n_fields.fetch_existing(clipFieldName); + const std::string &topoName = n_clipField["topology"].as_string(); + const conduit::Node &n_topo = n_input.fetch_existing("topologies/" + topoName); + const std::string &coordsetName = n_topo["coordset"].as_string(); + const conduit::Node &n_coordset = + n_input.fetch_existing("coordsets/" + coordsetName); + + execute(n_topo, + n_coordset, + n_fields, + n_options, + n_output["topologies/" + opts.topologyName(topoName)], + n_output["coordsets/" + opts.coordsetName(coordsetName)], + n_output["fields"]); +} + +void ClipFieldFilter::execute(const conduit::Node &n_topo, + const conduit::Node &n_coordset, + const conduit::Node &n_fields, + const conduit::Node &n_options, + conduit::Node &n_newTopo, + conduit::Node &n_newCoordset, + conduit::Node &n_newFields) +{ + // Instantiate the algorithm for the right device and invoke it. + if(m_runtime == axom::runtime_policy::Policy::seq) + { + ClipFieldFilterDevice clipper; + clipper.execute(n_topo, + n_coordset, + n_fields, + n_options, + n_newTopo, + n_newCoordset, + n_newFields); + } +#if 0 + #if defined(AXOM_USE_OPENMP) + else if(m_runtime == axom::runtime_policy::Policy::omp) + { + ClipFieldFilterDevice clipper; + clipper.execute(n_topo, n_coordset, n_fields, n_options, n_newTopo, n_newCoordset, n_newFields); + } + #endif + #if defined(AXOM_USE_CUDA) + else if(m_runtime == axom::runtime_policy::Policy::cuda) + { + ClipFieldFilterDevice clipper; + clipper.execute(n_topo, n_coordset, n_fields, n_options, n_newTopo, n_newCoordset, n_newFields); + } + #endif + #if defined(AXOM_USE_HIP) + else if(m_runtime == axom::runtime_policy::Policy::hip) + { + ClipFieldFilterDevice clipper; + clipper.execute(n_topo, n_coordset, n_fields, n_options, n_newTopo, n_newCoordset, n_newFields); + } + #endif +#endif +} + +} // end namespace clipping +} // end namespace mir +} // end namespace axom diff --git a/src/axom/mir/future/ClipFieldFilter.hpp b/src/axom/mir/future/ClipFieldFilter.hpp new file mode 100644 index 0000000000..bc5f01dd0e --- /dev/null +++ b/src/axom/mir/future/ClipFieldFilter.hpp @@ -0,0 +1,81 @@ +// 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) +#ifndef AXOM_MIR_CLIP_FIELD_FILTER_HPP_ +#define AXOM_MIR_CLIP_FIELD_FILTER_HPP_ + +#include "axom/core.hpp" +#include "axom/mir.hpp" + +namespace axom +{ +namespace mir +{ +namespace clipping +{ +/** + * \brief This class runs a ClipField algorithm. + * + */ +class ClipFieldFilter +{ +public: + /// Constructor + ClipFieldFilter() : m_runtime(axom::runtime_policy::Policy::seq) { } + + /** + * \brief Set the runtime policy. + * \param value The new runtime policy. + */ + void setRuntime(axom::runtime_policy::Policy value) { m_runtime = value; } + + /** + * \brief Get the runtime policy. + * + */ + axom::runtime_policy::Policy runtime() const { return m_runtime; } + + /** + * \brief Execute the clipping operation using the specified options. + * + * \param[in] n_input The Conduit node that contains the topology, coordsets, and fields. + * \param[in] n_options A Conduit node that contains clipping options. + * \param[out] n_output A Conduit node that will hold the clipped output mesh. This should be a different node from \a n_input. + * + * \note The clipField field must currently be vertex-associated. + */ + void execute(const conduit::Node &n_input, + const conduit::Node &n_options, + conduit::Node &n_output); + + /** + * \brief Execute the clipping operation using the specified options. + * + * \param[in] n_topo The node that contains the input mesh topology. + * \param[in] n_coordset The node that contains the input mesh coordset. + * \param[in] n_fields The node that contains the input fields. + * \param[in] n_options A Conduit node that contains clipping options. + * \param[out] n_newTopo A node that will contain the new clipped topology. + * \param[out] n_newCoordset A node that will contain the new coordset for the clipped topology. + * \param[out] n_newFields A node that will contain the new fields for the clipped topology. + * + * \note The clipField field must currently be vertex-associated. Also, the output topology will be an unstructured topology with mixed shape types. + */ + void execute(const conduit::Node &n_topo, + const conduit::Node &n_coordset, + const conduit::Node &n_fields, + const conduit::Node &n_options, + conduit::Node &n_newTopo, + conduit::Node &n_newCoordset, + conduit::Node &n_newFields); + +private: + axom::runtime_policy::Policy m_runtime; +}; + +} // end namespace clipping +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/future/ClipFieldFilterDevice.hpp b/src/axom/mir/future/ClipFieldFilterDevice.hpp new file mode 100644 index 0000000000..06dad0bcce --- /dev/null +++ b/src/axom/mir/future/ClipFieldFilterDevice.hpp @@ -0,0 +1,150 @@ +// 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) +#ifndef AXOM_MIR_CLIP_FIELD_FILTER_DEVICE_HPP_ +#define AXOM_MIR_CLIP_FIELD_FILTER_DEVICE_HPP_ + +#include "axom/core.hpp" +#include "axom/mir.hpp" +#include "axom/mir/views/dispatch_coordset.hpp" +#include "axom/mir/views/dispatch_topology.hpp" + +namespace axom +{ +namespace mir +{ +namespace clipping +{ +/** + * \brief This class instantiates ClipField so it runs on specific device but + * can operate on a variety of Blueprint topologies/coordsets/types. + * + * \tparam ExecSpace The execution space where the algorithm will run. + */ +template +class ClipFieldFilterDevice +{ +public: + /// Constructor + ClipFieldFilterDevice() { } + + /** + * \brief Execute the clipping operation using the specified options. + * + * \param[in] n_input The Conduit node that contains the topology, coordsets, and fields. + * \param[in] n_options A Conduit node that contains clipping options. + * \param[out] n_output A Conduit node that will hold the clipped output mesh. This should be a different node from \a n_input. + * + * \note The clipField field must currently be vertex-associated. + */ + void execute(const conduit::Node &n_input, + const conduit::Node &n_options, + conduit::Node &n_output) + { + ClipOptions opts(n_options); + const std::string clipFieldName = opts.clipField(); + + const conduit::Node &n_fields = n_input.fetch_existing("fields"); + const conduit::Node &n_clipField = n_fields.fetch_existing(clipFieldName); + const std::string &topoName = n_clipField["topology"].as_string(); + const conduit::Node &n_topo = + n_input.fetch_existing("topologies/" + topoName); + const std::string &coordsetName = n_topo["coordset"].as_string(); + const conduit::Node &n_coordset = + n_input.fetch_existing("coordsets/" + coordsetName); + + execute(n_topo, + n_coordset, + n_fields, + n_options, + n_output["topologies/" + opts.topologyName(topoName)], + n_output["coordsets/" + opts.coordsetName(coordsetName)], + n_output["fields"]); + } + + /** + * \brief Returns a bitset that records which shapes are supported. + * \return A bitset that encodes the supported shapes. + */ + static constexpr int supported_shapes() + { + int bitset = 0; + axom::utilities::setBitOn(bitset, views::Tri_ShapeID); + axom::utilities::setBitOn(bitset, views::Quad_ShapeID); + axom::utilities::setBitOn(bitset, views::Pyramid_ShapeID); + axom::utilities::setBitOn(bitset, views::Wedge_ShapeID); + axom::utilities::setBitOn(bitset, views::Hex_ShapeID); + axom::utilities::setBitOn(bitset, views::Mixed_ShapeID); + return bitset; + } + + /** + * \brief Execute the clipping operation using the specified options. + * + * \param[in] n_topo The node that contains the input mesh topology. + * \param[in] n_coordset The node that contains the input mesh coordset. + * \param[in] n_fields The node that contains the input fields. + * \param[in] n_options A Conduit node that contains clipping options. + * \param[out] n_newTopo A node that will contain the new clipped topology. + * \param[out] n_newCoordset A node that will contain the new coordset for the clipped topology. + * \param[out] n_newFields A node that will contain the new fields for the clipped topology. + * + * \note The clipField field must currently be vertex-associated. Also, the output topology will be an unstructured topology with mixed shape types. + */ + void execute(const conduit::Node &n_topo, + const conduit::Node &n_coordset, + const conduit::Node &n_fields, + const conduit::Node &n_options, + conduit::Node &n_newTopo, + conduit::Node &n_newCoordset, + conduit::Node &n_newFields) + { +#if 0 + // NOTE - there are 2 dispatches here so we can get coordset and topology views. + // This instantiates the lambda that creates the ClipField object for + // the various view types so we can handle most Blueprint topologies. + // However, this expansion makes this file take a long time to build. + // + // Perhaps we could split the topology dispatch somehow to split the + // compilation responsibility among more cores for a faster build. + // + axom::mir::views::dispatch_coordset(n_coordset, [&](auto coordsetView) { + // We dispatch to 2D/3D topologies with supported shapes. + constexpr int dims = axom::mir::views::select_dimensions(2, 3); + constexpr int shapes = supported_shapes(); + axom::mir::views::dispatch_topology( + n_topo, + [&](const std::string & /*shape*/, auto topologyView) { + using TopologyView = decltype(topologyView); + using CoordsetView = decltype(coordsetView); + + // Don't allow 3D topologies with 2D coordsets. + constexpr int RuntimeDimension = -1; + if constexpr((TopologyView::dimension() == 2) || + (TopologyView::dimension() == 3 && + CoordsetView::dimension() == 3) || + (TopologyView::dimension() == RuntimeDimension)) + { + ClipField clipper( + topologyView, + coordsetView); + clipper.execute(n_topo, + n_coordset, + n_fields, + n_options, + n_newTopo, + n_newCoordset, + n_newFields); + } + }); + }); +#endif + } +}; + +} // end namespace clipping +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/future/README.md b/src/axom/mir/future/README.md new file mode 100644 index 0000000000..8120d96f76 --- /dev/null +++ b/src/axom/mir/future/README.md @@ -0,0 +1,17 @@ +These classes instantiate ``axom::mir::ClipField`` for available devices and mesh +types. They were moved to the ``future`` directory for now since they can take +significant time to compile. + +====================== +ClipFieldFilterDevice +====================== + +This class instantiates ClipField for relevant mesh/coordset types on a specific device. + +====================== +ClipFieldFilter +====================== + +This class instantiates ClipFieldFilterDevice for all supported device types and +makes the policy selectable at runtime. + diff --git a/src/axom/mir/reference/CellClipper.cpp b/src/axom/mir/reference/CellClipper.cpp new file mode 100644 index 0000000000..bf2d567e0e --- /dev/null +++ b/src/axom/mir/reference/CellClipper.cpp @@ -0,0 +1,179 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/mir/reference/CellClipper.hpp" + +namespace axom +{ +namespace mir +{ +//-------------------------------------------------------------------------------- + +CellClipper::CellClipper() { } + +//-------------------------------------------------------------------------------- + +CellClipper::~CellClipper() { } + +//-------------------------------------------------------------------------------- + +// Computes the t-values where each edge is clipped, as well as the topology of the new output cells after clipping the original cell +// Outputs the newElements, newVertices maps and the verticesClippingTValue array[] +void CellClipper::computeClippingPoints( + const mir::Shape shapeType, + const std::vector>& vertexVF, + std::map>& newElements, + std::map>& newVertices, + axom::float64* tValues) +{ + // Determine the clipping case for the current element + unsigned int caseIndex = + determineClippingCase(shapeType, vertexVF[0], vertexVF[1]); + + std::vector> clipTable = getClipTable(shapeType); + + // Create the new polygons based on the clipping case + int currentElementIndex = 0; // the next available element index + int i = 0; + int numVertices = clipTable[caseIndex][i]; + + // for each new element in the current clipping case + while(numVertices != -1) + { + // for each vertex of the new element + for(int j = 0; j < numVertices; ++j) + { + // Find the id of the next vertex of the new element + int vID = clipTable[caseIndex][i + (j + 1)]; + + // Associate the vertex and element together + newElements[currentElementIndex].push_back(vID); + newVertices[vID].push_back(currentElementIndex); + + if(vID >= mir::utilities::numVerts(shapeType) && + vID < (mir::utilities::maxPossibleNumVerts(shapeType) - 1)) + { + int vertexOneID = mir::utilities::getEdgeEndpoint(shapeType, vID, true); + int vertexTwoID = mir::utilities::getEdgeEndpoint(shapeType, vID, false); + + tValues[vID] = computeTValueOnEdge(vertexVF[0][vertexOneID], + vertexVF[1][vertexOneID], + vertexVF[0][vertexTwoID], + vertexVF[1][vertexTwoID]); + } + } + + // Increment the element index counter, marking the current element as being finished processed + currentElementIndex++; + + // Increase index into lookup table to the next element + i += (numVertices + 1); + numVertices = clipTable[caseIndex][i]; + } +} + +//-------------------------------------------------------------------------------- + +unsigned int CellClipper::determineClippingCase( + const mir::Shape shapeType, + const std::vector& matOneVF, + const std::vector& matTwoVF) +{ + unsigned int caseIndex = 0; + + int numVertices = mir::utilities::numVerts(shapeType); + + for(int vID = 0; vID < numVertices; ++vID) + { + if(matOneVF[vID] > matTwoVF[vID]) + { + unsigned int bitIndex = (numVertices - 1) - vID; + + caseIndex |= (1 << bitIndex); + } + } + + return caseIndex; +} + +//-------------------------------------------------------------------------------- + +axom::float64 CellClipper::computeTValueOnEdge(axom::float64 vfMatOneVertexOne, + axom::float64 vfMatTwoVertexOne, + axom::float64 vfMatOneVertexTwo, + axom::float64 vfMatTwoVertexTwo) +{ + axom::float64 ret = 0.0; + + // TODO: Perhaps just handle NULL_MAT by return 0, since that is what will happen anyways? + // Handle NULL_MAT, which has a vf of -1.0, but which needs to be 0.0 for the purposes of computing the clipping point + if(vfMatOneVertexOne < 0.0) + { + vfMatOneVertexOne = 0.0; + }; + if(vfMatTwoVertexOne < 0.0) + { + vfMatTwoVertexOne = 0.0; + }; + if(vfMatOneVertexTwo < 0.0) + { + vfMatOneVertexTwo = 0.0; + }; + if(vfMatTwoVertexTwo < 0.0) + { + vfMatTwoVertexTwo = 0.0; + }; + + axom::float64 numerator = vfMatTwoVertexOne - vfMatOneVertexOne; + axom::float64 denominator = -vfMatOneVertexOne + vfMatOneVertexTwo + + vfMatTwoVertexOne - vfMatTwoVertexTwo; + + if(denominator != 0.0) + { + ret = numerator / denominator; + } + + if(ret > 1.0 || ret < 0.0) + { + // This shouldn't happen... + printf(" OUT OF BOUNDS T VALUE: %f\n", ret); + + // Clamp the t value + ret = fmin(1.0, ret); + ret = fmax(0.0, ret); + } + + return ret; +} + +//-------------------------------------------------------------------------------- + +const std::vector>& CellClipper::getClipTable( + const mir::Shape shapeType) +{ + switch(shapeType) + { + case mir::Shape::Triangle: + return triangleClipTableVec; + case mir::Shape::Quad: + return quadClipTableVec; + case mir::Shape::Tetrahedron: + return tetrahedronClipTableVec; + case mir::Shape::Pyramid: + return pyramidClipTableVec; + case mir::Shape::Triangular_Prism: + return triangularPrismClipTableVec; + case mir::Shape::Hexahedron: + return hexahedronClipTableVec; + default: + printf("No clipping table for this shape type.\n"); + return triangleClipTableVec; + } +} + +//-------------------------------------------------------------------------------- + +} // namespace mir +} // namespace axom diff --git a/src/axom/mir/reference/CellClipper.hpp b/src/axom/mir/reference/CellClipper.hpp new file mode 100644 index 0000000000..d4c4d5529f --- /dev/null +++ b/src/axom/mir/reference/CellClipper.hpp @@ -0,0 +1,116 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file CellClipper.hpp + * + * \brief Contains the specification for the CellClipper class. + * + */ + +#ifndef __CELL_CLIPPER_H +#define __CELL_CLIPPER_H + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" // unified header for slam classes and functions + +#include "axom/mir/reference/MIRMesh.hpp" +#include "axom/mir/reference/MIRUtilities.hpp" +#include "axom/mir/reference/MIRMeshTypes.hpp" +#include "axom/mir/reference/CellData.hpp" +#include "axom/mir/reference/ZooClippingTables.hpp" + +//-------------------------------------------------------------------------------- + +namespace axom +{ +namespace mir +{ +//-------------------------------------------------------------------------------- + +/** + * \class CellClipper + * + * \brief A class that contains the functionality for taking an input cell + * and determining how it should be clipped. + * + */ +class CellClipper +{ +public: + /** + * \brief Default constructor. + */ + CellClipper(); + + /** + * \brief Default destructor. + */ + ~CellClipper(); + + /** + * \brief Computes the elements, vertices, and t values resulting from splitting the element with the given vertex volume fractions. + * + * \param shapeType The shape type of the element. + * \param vertexVF The vertex volume fractions of the two materials with which to clip the cell. + * \param newElements An ordered map of the generated elements' IDs to a list of the vertex IDs in the local frame of the output element. + * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. + * \param tValues An array of t values where each of the midpoint vertices are for the newly generated elements. + * + */ + void computeClippingPoints(const mir::Shape shapeType, + const std::vector>& vertexVF, + std::map>& newElements, + std::map>& newVertices, + axom::float64* tValues); + + /** + * \brief Determines the index into the clipping table of the given shape based on the volume fractions at its vertices. + * + * \param shapeType The shape type of the element. + * \param matOneVF The volume fractions at the vertices of the element for the first material. + * \param matTwoVF The volume fractions at the vertices of the element for the second material. + * + * \return The index into the clipping table. + */ + unsigned int determineClippingCase(const mir::Shape shapeType, + const std::vector& matOneVF, + const std::vector& matTwoVF); + + /** + * \brief Computes the t value as a percent from vertex one to vertex two based on the materials given. + * + * \param vfMatOneVertexOne The volume fraction of material one present at vertex one. + * \param vfMatTwoVertexOne The volume fraction of material two present at vertex one. + * \param vfMatOneVertexTwo The volume fraction of material one present at vertex two. + * \param vfMatTwoVertexTwo The volume fraction of material two present at vertex two. + * + * \return The percent of the distance from vertex one to vertex two where the edge should be clipped. + * + * \note When one material's volume fractions dominates the other material's, then the edge should not be clipped and this function will return 0.0. + * \note When one of the materials is the NULL_MAT (and has vf = -1.0), these values are set to 0 in order to interpolate properly. + */ + axom::float64 computeTValueOnEdge(axom::float64 vfMatOneVertexOne, + axom::float64 vfMatTwoVertexOne, + axom::float64 vfMatOneVertexTwo, + axom::float64 vfMatTwoVertexTwo); + +private: + /** + * \brief Returns a reference to the appropriate clipping table to use for the shape type. + * + * \param The shape type of the element. + * + * \return A reference to the clipping table. + */ + const std::vector>& getClipTable(const mir::Shape shapeType); +}; + +//-------------------------------------------------------------------------------- + +} // namespace mir +} // namespace axom + +#endif diff --git a/src/axom/mir/reference/CellData.cpp b/src/axom/mir/reference/CellData.cpp new file mode 100644 index 0000000000..c092731901 --- /dev/null +++ b/src/axom/mir/reference/CellData.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/mir/reference/CellData.hpp" + +namespace axom +{ +namespace mir +{ +//-------------------------------------------------------------------------------- + +CellData::CellData() : m_numVerts(0), m_numElems(0) { } + +//-------------------------------------------------------------------------------- + +void CellData::mergeCell(const CellData& cellToMerge) +{ + // Initialize index offsets + int evBeginsOffset = m_topology.m_evInds.size(); + int veBeginsOffset = m_topology.m_veInds.size(); + + int vertexIndexOffset = m_numVerts; + int elementIndexOffset = m_numElems; + + // Merge the cell topology information + for(unsigned long i = 0; i < cellToMerge.m_topology.m_evInds.size(); ++i) + { + m_topology.m_evInds.push_back(cellToMerge.m_topology.m_evInds[i] + + vertexIndexOffset); + } + + for(unsigned long i = 1; i < cellToMerge.m_topology.m_evBegins.size(); ++i) + { + m_topology.m_evBegins.push_back(cellToMerge.m_topology.m_evBegins[i] + + evBeginsOffset); + } + + for(unsigned long i = 0; i < cellToMerge.m_topology.m_veInds.size(); ++i) + { + m_topology.m_veInds.push_back(cellToMerge.m_topology.m_veInds[i] + + elementIndexOffset); + } + + for(unsigned long i = 1; i < cellToMerge.m_topology.m_veBegins.size(); ++i) + { + m_topology.m_veBegins.push_back(cellToMerge.m_topology.m_veBegins[i] + + veBeginsOffset); + } + + // Merge the vertex positions + for(unsigned long i = 0; i < cellToMerge.m_mapData.m_vertexPositions.size(); + ++i) + { + m_mapData.m_vertexPositions.push_back( + cellToMerge.m_mapData.m_vertexPositions[i]); + } + + // Merge the vertex volume fractions + for(unsigned long matID = 0; matID < m_mapData.m_vertexVolumeFractions.size(); + ++matID) + { + for(unsigned long vID = 0; + vID < cellToMerge.m_mapData.m_vertexVolumeFractions[matID].size(); + ++vID) + { + m_mapData.m_vertexVolumeFractions[matID].push_back( + cellToMerge.m_mapData.m_vertexVolumeFractions[matID][vID]); + } + } + + // Merge the elements' dominant materials + for(unsigned long i = 0; + i < cellToMerge.m_mapData.m_elementDominantMaterials.size(); + ++i) + { + m_mapData.m_elementDominantMaterials.push_back( + cellToMerge.m_mapData.m_elementDominantMaterials[i]); + } + + // Merge the elements' parent ids + for(unsigned long i = 0; i < cellToMerge.m_mapData.m_elementParents.size(); ++i) + { + m_mapData.m_elementParents.push_back( + cellToMerge.m_mapData.m_elementParents[i]); + } + + // Merge the elements' shape types + for(unsigned long i = 0; i < cellToMerge.m_mapData.m_shapeTypes.size(); ++i) + { + m_mapData.m_shapeTypes.push_back(cellToMerge.m_mapData.m_shapeTypes[i]); + } + + // Merge the total number of verts and elems in the resulting cell + m_numVerts += cellToMerge.m_numVerts; + m_numElems += cellToMerge.m_numElems; +} + +//-------------------------------------------------------------------------------- + +} // namespace mir +} // namespace axom diff --git a/src/axom/mir/reference/CellData.hpp b/src/axom/mir/reference/CellData.hpp new file mode 100644 index 0000000000..e1d2e25f51 --- /dev/null +++ b/src/axom/mir/reference/CellData.hpp @@ -0,0 +1,97 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file CellData.hpp + * + * \brief Contains the specifications for the CellData class + * and CellTopologyData and CellMapData structs. + */ + +#ifndef __CELL_DATA_H__ +#define __CELL_DATA_H__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" + +#include "axom/mir/reference/MIRMeshTypes.hpp" + +namespace numerics = axom::numerics; +namespace slam = axom::slam; + +namespace axom +{ +namespace mir +{ +/** + * \struct CellTopologyData + * + * \brief Struct for collecting data that specifies a mesh or cell's connectivity/topology. + */ +struct CellTopologyData +{ + std::vector m_evInds; + std::vector m_evBegins; + std::vector m_veInds; + std::vector m_veBegins; +}; + +/** + * \struct CellMapData + * + * \brief Struct for collecting the data that will populate a mesh's map data structures. + */ +struct CellMapData +{ + std::vector m_vertexPositions; // Data that goes into MIRMesh's vertexPositions PointMap + std::vector> + m_vertexVolumeFractions; // Data that goes into MIRMesh's materialVolumeFractionsVertex ScalarMap + std::vector m_elementDominantMaterials; // Data that goes into MIRMesh's elementDominantColors IntMap + std::vector m_elementParents; // Data that goes into MIRMesh's elementParentIDs IntMap + std::vector m_shapeTypes; // Data that goes into MIRMesh's shapeType IntMap +}; + +/** + * \class CellData + * + * \brief The CellData class represents an arbitrary number of cells that are + * within the same local coordinate system (i.e. share a set of vertices + * and elements). + * + * \detail This class is intended to be used as a helper class to hold intermediate + * data while processing a mesh, and to be used as input to the MIRMesh class + * to fully initialize it. + */ +class CellData +{ +public: + /** + * \brief Default constructor. + */ + CellData(); + + /** + * \brief Default destructor. + */ + ~CellData() = default; + + /** + * \brief Merges the cell data from the given cell into this cell. + * + * \param cellToMerge The cell whose data will be taken and merged in. + */ + void mergeCell(const CellData& cellToMerge); + +public: + int m_numVerts; + int m_numElems; + + CellTopologyData m_topology; + CellMapData m_mapData; +}; +} // namespace mir +} // namespace axom + +#endif diff --git a/src/axom/mir/reference/CellGenerator.cpp b/src/axom/mir/reference/CellGenerator.cpp new file mode 100644 index 0000000000..196fb25034 --- /dev/null +++ b/src/axom/mir/reference/CellGenerator.cpp @@ -0,0 +1,190 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/mir/reference/CellGenerator.hpp" + +namespace axom +{ +namespace mir +{ +//-------------------------------------------------------------------------------- + +CellGenerator::CellGenerator() { } + +//-------------------------------------------------------------------------------- + +CellGenerator::~CellGenerator() { } + +//-------------------------------------------------------------------------------- + +void CellGenerator::generateTopologyData( + const std::map>& newElements, + const std::map>& newVertices, + CellData& out_cellData) +{ + // Store the evInds and evBegins data in the output vectors + int currentEVBeginIndex = 0; + for(auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + // Push the start index of the next element + out_cellData.m_topology.m_evBegins.push_back(currentEVBeginIndex); + + // Push the next element's vertices + for(unsigned int vIndex = 0; vIndex < itr->second.size(); ++vIndex) + { + out_cellData.m_topology.m_evInds.push_back(itr->second[vIndex]); + ++currentEVBeginIndex; + } + } + + // Push the index that occurs after the last vertex + out_cellData.m_topology.m_evBegins.push_back(currentEVBeginIndex); + + // Store the veInds and veBegins data in the output vectors + int currentVEBeginIndex = 0; + for(auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + // Push the start index of the vertex's elements + out_cellData.m_topology.m_veBegins.push_back(currentVEBeginIndex); + + // Push the next vertex's elements + for(unsigned int eIndex = 0; eIndex < itr->second.size(); eIndex++) + { + out_cellData.m_topology.m_veInds.push_back(itr->second[eIndex]); + ++currentVEBeginIndex; + } + } + + // Push the index that occurs after the last element + out_cellData.m_topology.m_veBegins.push_back(currentVEBeginIndex); +} + +//-------------------------------------------------------------------------------- + +void CellGenerator::generateVertexPositions( + const mir::Shape shapeType, + const std::map>& newVertices, + const std::vector& vertexPositions, + axom::float64* tValues, + CellData& out_cellData) +{ + for(auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + + if(vID < mir::utilities::numVerts(shapeType)) + { + // This vertex is one of the shape's original vertices + out_cellData.m_mapData.m_vertexPositions.push_back(vertexPositions[vID]); + } + else if(mir::utilities::isCenterVertex(shapeType, vID)) + { + // Average the vertex position values at the corners of the shape + mir::Point2 centroid = mir::utilities::computeAveragePoint(vertexPositions); + + out_cellData.m_mapData.m_vertexPositions.push_back(centroid); + } + else + { + // This vertex is between two of the shape's original vertices + int vIDFrom = mir::utilities::getEdgeEndpoint(shapeType, vID, true); + int vIDTo = mir::utilities::getEdgeEndpoint(shapeType, vID, false); + + out_cellData.m_mapData.m_vertexPositions.push_back( + mir::Point2::lerp(vertexPositions[vIDFrom], + vertexPositions[vIDTo], + tValues[vID])); + } + } +} + +//-------------------------------------------------------------------------------- + +void CellGenerator::generateVertexVolumeFractions( + const mir::Shape shapeType, + const std::map>& newVertices, + const std::vector>& vertexVF, + axom::float64* tValues, + CellData& out_cellData) +{ + out_cellData.m_mapData.m_vertexVolumeFractions.resize( + vertexVF.size()); // vertexVF size is the number of materials + + for(auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + + for(unsigned long matID = 0; matID < vertexVF.size(); ++matID) + { + if(vID < mir::utilities::numVerts(shapeType)) + { + // This vertex is one of the shape's original vertices + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back( + vertexVF[matID][vID]); + } + else if(mir::utilities::isCenterVertex(shapeType, vID)) + { + // Average the vertex volume fractions values at the corners of the shape + axom::float64 averageValue = + mir::utilities::computeAverageFloat(vertexVF[matID]); + + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back( + averageValue); + } + else + { + // This vertex is between two of the shape's original vertices + int vIDFrom = mir::utilities::getEdgeEndpoint(shapeType, vID, true); + int vIDTo = mir::utilities::getEdgeEndpoint(shapeType, vID, false); + + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back( + axom::utilities::lerp(vertexVF[matID][vIDFrom], + vertexVF[matID][vIDTo], + tValues[vID])); + } + } + } +} + +//-------------------------------------------------------------------------------- + +int CellGenerator::determineCleanCellMaterial( + const Shape elementShape, + const std::vector& vertexIDs, + const int matOne, + const int matTwo, + const std::vector>& vertexVF) +{ + int dominantMaterial = matOne; + + axom::float64 matOneVF = -1.0; + axom::float64 matTwoVF = -1.0; + + for(unsigned long it = 0; it < vertexIDs.size(); ++it) + { + int vID = vertexIDs[it]; + + if(vID >= 0 && vID < mir::utilities::numVerts(elementShape)) + { + if(matOne != NULL_MAT) + { + matOneVF = vertexVF[matOne][vID]; + } + if(matTwo != NULL_MAT) + { + matTwoVF = vertexVF[matTwo][vID]; + } + + dominantMaterial = (matOneVF > matTwoVF) ? matOne : matTwo; + } + } + + return dominantMaterial; +} + +//-------------------------------------------------------------------------------- + +} // namespace mir +} // namespace axom diff --git a/src/axom/mir/reference/CellGenerator.hpp b/src/axom/mir/reference/CellGenerator.hpp new file mode 100644 index 0000000000..f50a7a12ff --- /dev/null +++ b/src/axom/mir/reference/CellGenerator.hpp @@ -0,0 +1,130 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file CellGenerator.hpp + * + * \brief Contains the specification for the CellGenerator class. + * + */ + +#ifndef __CELL_GENERATOR_H__ +#define __CELL_GENERATOR_H__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" // unified header for slam classes and functions + +#include "axom/mir/reference/MIRMesh.hpp" +#include "axom/mir/reference/MIRUtilities.hpp" +#include "axom/mir/reference/MIRMeshTypes.hpp" +#include "axom/mir/reference/CellData.hpp" +#include "axom/mir/reference/ZooClippingTables.hpp" +#include "axom/mir/reference/MIRUtilities.hpp" +#include "axom/mir/reference/CellClipper.hpp" + +//-------------------------------------------------------------------------------- + +namespace axom +{ +namespace mir +{ +//-------------------------------------------------------------------------------- + +/** + * \class CellGenerator + * + * \brief A class that generates that uses the clipping information to generate + * the data needed in order to produce a clean, reconstructed mesh. + */ +class CellGenerator +{ +public: + /** + * \brief Default constructor. + */ + CellGenerator(); + + /** + * \brief Default destructor. + */ + ~CellGenerator(); + + /** + * \brief Generates the topology of the new elements resulting from a split. + * + * \param newElements An ordered map of the generated elements' IDs to a list of the vertex IDs in the local frame of the output element. + * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. + * \param out_cellData Container to store the topology data of the generated elements. + */ + void generateTopologyData(const std::map>& newElements, + const std::map>& newVertices, + CellData& out_cellData); + + /** + * \brief Generates the vertex positions values for each of the new vertices of the generated element. + * + * \param shapeType The shape type of the element. + * \param newVertices An ordered map of vertices that compose the newly generated elements. + * \param vertexPositions A vector of positions for each of the original element's vertices. + * \param tValues An array of t values where each of the midpoint vertices are for the newly generated elements. + * \param out_cellData Container to store the vertex position data of the generated elements. + * + * \note New vertex positions are interpolated from the original vertex positions. + */ + void generateVertexPositions(const mir::Shape shapeType, + const std::map>& newVertices, + const std::vector& vertexPositions, + axom::float64* tValues, + CellData& out_cellData); + + /** + * \brief Generates the vertex volume fractions for each of the new vertices of the generated element. + * + * \param shapeType The shape type of the element. + * \param newVertices An ordered map of vertices that compose the newly generated elements. + * \param vertexVF A vector of positions for each of the original element's vertices. + * \param tValues An array of t values where each of the midpoint vertices are for the newly generated elements. + * \param out_cellData Container to store the vertex position data of the generated elements. + * + * \note New vertex positions are interpolated from the original vertex volume fractions. + */ + void generateVertexVolumeFractions( + const mir::Shape shapeType, + const std::map>& newVertices, + const std::vector>& vertexVF, + axom::float64* tValues, + CellData& out_cellData); + + /** + * \brief Determines the more dominant material of the two given for the given element. + * + * \param shapeType An enumerator denoting the element's shape. + * \param vertexIDs A list of vertex IDs into the vertexVF param. + * \param matOne The ID of the first material. + * \param matTwo The ID of the second material. + * \param vertexVF The list of volume fractions associated with the given vertices in the vertexIDs param. + * + * \return The ID of the dominant material of the element. + * + * \note The dominant element for the 2D/3D cases will be the same as the material present at one of the + * original vertices that existed prior to the split. So, if you can find this vertex and its dominant + * material, then you know the dominant material of this new element. + * + * \note It is assumed that the given cell is one that results from splitting its parent cell. + */ + int determineCleanCellMaterial( + const Shape shapeType, + const std::vector& vertexIDs, + const int matOne, + const int matTwo, + const std::vector>& vertexVF); +}; + +//-------------------------------------------------------------------------------- + +} // namespace mir +} // namespace axom + +#endif diff --git a/src/axom/mir/reference/InterfaceReconstructor.cpp b/src/axom/mir/reference/InterfaceReconstructor.cpp new file mode 100644 index 0000000000..793d5a590a --- /dev/null +++ b/src/axom/mir/reference/InterfaceReconstructor.cpp @@ -0,0 +1,406 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/mir/reference/InterfaceReconstructor.hpp" + +namespace axom +{ +namespace mir +{ +//-------------------------------------------------------------------------------- + +InterfaceReconstructor::InterfaceReconstructor() { } + +//-------------------------------------------------------------------------------- + +InterfaceReconstructor::~InterfaceReconstructor() { } + +//-------------------------------------------------------------------------------- + +void InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh& inputMesh, + mir::MIRMesh& outputMesh) +{ + // Store a reference to the original mesh + m_originalMesh = inputMesh; + + // Initialize the final mesh to be the same as the input mesh + mir::MIRMesh finalMesh(m_originalMesh); + + // For each material in the mesh, split the mesh on the current material and generate a new mesh (input into next iteration) + for(int matID = 0; matID < m_originalMesh.m_numMaterials; ++matID) + { + // Copy the mesh to be split + mir::MIRMesh intermediateMesh(finalMesh); + + // Create an array to store the output of each element being split. + CellData temp_cellData[intermediateMesh.m_elems.size()]; + + // Process/split each element + for(int eID = 0; eID < intermediateMesh.m_elems.size(); ++eID) + { + // Update the materials upon which the split will occur + int currentDominantMat = intermediateMesh.m_elementDominantMaterials[eID]; + int matOne = matID; + + // Extract the data for the current element + std::vector elementVertices; + for(int vID = 0; vID < intermediateMesh.m_bdry[eID].size(); ++vID) + { + elementVertices.push_back(intermediateMesh.m_bdry[eID][vID]); + } + + // Extract the vertex volume fractions of the element to split + std::vector> originalElementVertexVF; + for(int matID = 0; matID < intermediateMesh.m_numMaterials; ++matID) + { + std::vector materialVertexVF; + for(unsigned long vID = 0; vID < elementVertices.size(); ++vID) + { + int originalVID = elementVertices[vID]; + materialVertexVF.push_back( + intermediateMesh.m_materialVolumeFractionsVertex[matID][originalVID]); + } + originalElementVertexVF.push_back(materialVertexVF); + } + + // Extract the vertex positions of the element to split + std::vector originalElementVertexPositions; + for(unsigned long vID = 0; vID < elementVertices.size(); ++vID) + { + int originalVID = elementVertices[vID]; + originalElementVertexPositions.push_back( + intermediateMesh.m_vertexPositions[originalVID]); + } + + generateCleanCells((mir::Shape)intermediateMesh.m_shapeTypes[eID], + intermediateMesh.m_elementParentIDs[eID], + currentDominantMat, + matOne, + elementVertices, + originalElementVertexVF, + originalElementVertexPositions, + temp_cellData[eID]); + } + + // Merge each of the cells into the first CellData struct + for(int eID = 1; eID < intermediateMesh.m_elems.size(); ++eID) + { + temp_cellData[0].mergeCell(temp_cellData[eID]); + } + + mir::VertSet combined_verts(temp_cellData[0].m_numVerts); + mir::ElemSet combined_elems(temp_cellData[0].m_numElems); + + // Create the final, processed mesh + mir::MIRMesh processedMesh; + processedMesh.initializeMesh(combined_verts, + combined_elems, + intermediateMesh.m_numMaterials, + temp_cellData[0].m_topology, + temp_cellData[0].m_mapData); + processedMesh.constructMeshVolumeFractionsVertex( + temp_cellData[0].m_mapData.m_vertexVolumeFractions); + + // Store the current mesh to be passed into the next iteration + finalMesh = processedMesh; + finalMesh.constructMeshRelations(); + } + + // TODO: Run post-processing on mesh to meld the mesh vertices that are within a certain epsilon + outputMesh = finalMesh; +} + +//-------------------------------------------------------------------------------- + +void InterfaceReconstructor::computeReconstructedInterfaceIterative( + mir::MIRMesh& inputMesh, + const int numIterations, + const axom::float64 percent, + mir::MIRMesh& outputMesh) +{ + int numElems = inputMesh.m_elems.size(); + int numMaterials = inputMesh.m_numMaterials; + + // Make a copy of the original input mesh + mir::MIRMesh meshToImprove(inputMesh); + + // Calculate the reconstruction on the unmodified, input mesh + computeReconstructedInterface(meshToImprove, outputMesh); + + for(int it = 0; it < numIterations; ++it) + { + printf("iteration %d\n", it); + // Calculate the output element volume fractions of the resulting output mesh + std::vector> resultingElementVF = + outputMesh.computeOriginalElementVolumeFractions(); + + // Initialize the vector to store the improved element volume fractions + std::vector> improvedElementVF; + improvedElementVF.resize(numMaterials); + + // Modify the copy of the original mesh's element volume fractions by percent difference (modify meshToImprove's elementVF) + for(int matID = 0; matID < numMaterials; ++matID) + { + for(int eID = 0; eID < numElems; ++eID) + { + axom::float64 difference = + inputMesh.m_materialVolumeFractionsElement[matID][eID] - + resultingElementVF[matID][eID]; + improvedElementVF[matID].push_back( + inputMesh.m_materialVolumeFractionsElement[matID][eID] + + (difference * percent)); + } + } + + // Reconstruct the element AND vertex VFs + meshToImprove.constructMeshVolumeFractionsMaps(improvedElementVF); + + // Calculate the reconstruction on the modified, input mesh + computeReconstructedInterface(meshToImprove, outputMesh); + } +} + +//-------------------------------------------------------------------------------- + +void InterfaceReconstructor::generateCleanCells( + mir::Shape shapeType, + const int parentElementID, + const int matOne, + const int matTwo, + const std::vector& elementVertices, + const std::vector>& originalElementVertexVF, + const std::vector& originalElementVertexPositions, + CellData& out_cellData) +{ + // Set up data structures needed to compute how element should be clipped + std::map> + newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets + std::map> + newVertices; // hashmap of the new vertices' elements | Note: maps are ordered sets + std::vector verticesPresent( + mir::utilities::maxPossibleNumVerts(shapeType), + 0); // Vector of flags denoting whether the vertex is present in the current case or not + axom::float64* tValues = + new axom::float64[mir::utilities::maxPossibleNumVerts(shapeType)] { + 0}; // Array of t values that denote the percent value of where the edge should be clipped + + // Set up the volume fractions for the current element for the two materials currently being considered + std::vector> vertexVF(2); + if(matOne == NULL_MAT) + { + std::vector nullVF(elementVertices.size(), -1); + vertexVF[0] = nullVF; + } + else + { + vertexVF[0] = originalElementVertexVF[matOne]; + } + if(matTwo == NULL_MAT) + { + std::vector nullVF(elementVertices.size(), -1); + vertexVF[1] = nullVF; + } + else + { + vertexVF[1] = originalElementVertexVF[matTwo]; + } + + // Clip the element + CellClipper clipper; + clipper.computeClippingPoints(shapeType, + vertexVF, + newElements, + newVertices, + tValues); + + // Determine which vertices are present + for(auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + verticesPresent[vID] = 1; + } + + int centerVertexID = mir::utilities::getCenterVertex(shapeType); + if(centerVertexID != -1 && verticesPresent[centerVertexID] == 1) + { + // Set up the cell data struct for the results of the decomposed element being clipped + CellData decomposed_cellData[newElements.size()]; + int decompElementID = 0; + // The clipping is one a tough one, and the element needs to decompose further before splitting with the two materials + for(auto itr = newElements.begin(); itr != newElements.end(); + itr++, decompElementID++) + { + // Determine the shape type of the decomposed element + mir::Shape decomposedShapeType = + mir::utilities::determineElementShapeType(shapeType, itr->second.size()); + + // Extract the data for the current element + std::vector decomposedElementVertices; // = itr->second; + for(int i = 0; i < mir::utilities::numVerts(decomposedShapeType); ++i) + { + decomposedElementVertices.push_back(i); + } + + // Extract the vertex volume fractions of the element to split + std::vector> decomposedElementVertexVF; + for(unsigned long matID = 0; matID < originalElementVertexVF.size(); ++matID) + { + std::vector materialVertexVF; + for(unsigned long vID = 0; vID < decomposedElementVertices.size(); ++vID) + { + int originalVID = itr->second[vID]; + if(mir::utilities::isCenterVertex(shapeType, originalVID)) + { + // Compute the central vertex volume fraction as the average of the other + axom::float64 averageMaterialVF = + mir::utilities::computeAverageFloat(originalElementVertexVF[matID]); + materialVertexVF.push_back(averageMaterialVF); + } + else + { + // Use the values that is at one of the original local vertices + materialVertexVF.push_back( + originalElementVertexVF[matID][originalVID]); + } + } + decomposedElementVertexVF.push_back(materialVertexVF); + } + + // Extract the vertex positions of the element to split + std::vector decomposedElementVertexPositions; + for(unsigned long vID = 0; vID < decomposedElementVertices.size(); ++vID) + { + int originalVID = itr->second[vID]; + if(mir::utilities::isCenterVertex(shapeType, originalVID)) + { + // Compute the central vertex position as the centroid of the other + mir::Point2 centroid = + mir::utilities::computeAveragePoint(originalElementVertexPositions); + decomposedElementVertexPositions.push_back(centroid); + } + else + { + // Use the vertex position that is at one of the original local vertices + decomposedElementVertexPositions.push_back( + originalElementVertexPositions[originalVID]); + } + } + + generateCleanCells(decomposedShapeType, + parentElementID, + matOne, + matTwo, + decomposedElementVertices, + decomposedElementVertexVF, + decomposedElementVertexPositions, + decomposed_cellData[decompElementID]); + } + + // Merge the decomposed cell data with the outermost cellData + out_cellData.m_mapData.m_vertexVolumeFractions.resize( + originalElementVertexVF.size()); + out_cellData.m_topology.m_evBegins.push_back(0); + out_cellData.m_topology.m_veBegins.push_back(0); + for(int i = 0; i < decompElementID; i++) + out_cellData.mergeCell(decomposed_cellData[i]); + } + else + { + // Calculate the total number of elements and vertices that were generated from splitting the current element + out_cellData.m_numElems = (int)newElements.size(); + out_cellData.m_numVerts = (int)newVertices.size(); + + // Generate the topology and connectivity of the newly split element + CellGenerator cellGenerator; + cellGenerator.generateTopologyData(newElements, newVertices, out_cellData); + + // Generate the vertex position values of the newly split element + cellGenerator.generateVertexPositions(shapeType, + newVertices, + originalElementVertexPositions, + tValues, + out_cellData); + + // Generate the vertex volume fractions of the newly split elements + cellGenerator.generateVertexVolumeFractions(shapeType, + newVertices, + originalElementVertexVF, + tValues, + out_cellData); + + // Determine and store the dominant material of this element + for(auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + int dominantMaterial = cellGenerator.determineCleanCellMaterial( + shapeType, + itr->second, + matOne, + matTwo, + out_cellData.m_mapData.m_vertexVolumeFractions); + out_cellData.m_mapData.m_elementDominantMaterials.push_back( + dominantMaterial); + } + + // Determine and store the parent of this element + out_cellData.m_mapData.m_elementParents.resize(newElements.size()); + for(auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + out_cellData.m_mapData.m_elementParents[itr->first] = parentElementID; + } + + // Determine the generated elements' shape types + out_cellData.m_mapData.m_shapeTypes.resize(newElements.size()); + for(auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + out_cellData.m_mapData.m_shapeTypes[itr->first] = + mir::utilities::determineElementShapeType(shapeType, itr->second.size()); + } + + // Check that each element is dominated by a material that is actually present in the original parent cell + for(auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + int parentElementID = out_cellData.m_mapData.m_elementParents[itr->first]; + int currentDominantMaterial = + out_cellData.m_mapData.m_elementDominantMaterials[itr->first]; + + if(m_originalMesh.m_materialVolumeFractionsElement[currentDominantMaterial] + [parentElementID] == 0) + { + // This material is not present in the original element from which the current element comes from + if(currentDominantMaterial == matOne) + out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matTwo; + else + out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matOne; + } + } + + // Modify the evIndex values to account for the fact that perhaps not all possible vertices are present + std::vector evIndexSubtract; + evIndexSubtract.resize(mir::utilities::maxPossibleNumVerts(shapeType), 0); + for(unsigned long i = 0; i < evIndexSubtract.size(); ++i) + { + if(verticesPresent[i] == 1) + evIndexSubtract[i] = 0; + else + evIndexSubtract[i] = 1; + } + for(unsigned long i = 1; i < evIndexSubtract.size(); ++i) + evIndexSubtract[i] += evIndexSubtract[i - 1]; + + for(unsigned long i = 0; i < out_cellData.m_topology.m_evInds.size(); ++i) + { + out_cellData.m_topology.m_evInds[i] -= + evIndexSubtract[out_cellData.m_topology.m_evInds[i]]; + } + } + + // Memory management + delete[] tValues; +} + +//-------------------------------------------------------------------------------- + +} // namespace mir +} // namespace axom diff --git a/src/axom/mir/reference/InterfaceReconstructor.hpp b/src/axom/mir/reference/InterfaceReconstructor.hpp new file mode 100644 index 0000000000..58a6026a25 --- /dev/null +++ b/src/axom/mir/reference/InterfaceReconstructor.hpp @@ -0,0 +1,109 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file InterfaceReconstructor.hpp + * + * \brief Contains the specification for the InterfaceReconstructor class. + * + */ + +#ifndef __INTERFACE_RECONSTRUCTOR_H__ +#define __INTERFACE_RECONSTRUCTOR_H__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" + +#include "axom/mir/reference/MIRMesh.hpp" +#include "axom/mir/reference/CellData.hpp" +#include "axom/mir/reference/ZooClippingTables.hpp" +#include "axom/mir/reference/MIRUtilities.hpp" +#include "axom/mir/reference/CellClipper.hpp" +#include "axom/mir/reference/CellGenerator.hpp" + +#include + +namespace numerics = axom::numerics; +namespace slam = axom::slam; + +namespace axom +{ +namespace mir +{ +/** + * \class InterfaceReconstructor + * + * \brief A class that contains the functionality for taking an input mesh + * with mixed cells and outputs a mesh with clean cells. + * + * \detail This class requires that the user create a mesh of type MIRMesh + * and pass that into one of the reconstruction methods. There are + * currently two reconstructions methods implemented: one is the + * a zoo-based algorithm described in Meredith 2004, and the other + * is the iterative version described in Meredith and Childs 2010. + */ +class InterfaceReconstructor +{ +public: + /** + * \brief Default constructor. + */ + InterfaceReconstructor(); + + /** + * \brief Default destructor. + */ + ~InterfaceReconstructor(); + + /** + * \brief Performs material interface reconstruction using the zoo-based algorithm. + * + * \param inputMesh The mesh composed of mixed cells. + * \param outputMesh The mesh composed of clean cells. + */ + void computeReconstructedInterface(mir::MIRMesh& inputMesh, + mir::MIRMesh& outputMesh); + + /** + * \brief Performs material interface reconstruction using an iterative version of the zoo-based algorithm. + * + * \param inputMesh The mesh made up of mixed cells. + * \param numIterations The number of iterations for which to run the algorithm. + * \param percent The percent of the difference to use when modifying the original element volume fractions. + * \param outputMesh The mesh composed of clean cells. + */ + void computeReconstructedInterfaceIterative(mir::MIRMesh& inputMesh, + const int numIterations, + const axom::float64 percent, + mir::MIRMesh& outputMesh); + + /** + * \brief Generates a set of clean cells by splitting the element with the two given materials. + * + * \param shapeType The shape type of the element to be split. + * \param parentElementID The ID parent element. + * \param matOne The first material to split with. + * \param matTwo The second material to split with. + * \param elementVertices The vertices of the element to be split. + * \param originalElementVertexVF The original vertex volume fractions associated with the vertices of the element to be split. + * \param originalElementVertexPositions The original vertex positions associated with the vertices of the element to be split. + * \param out_cellData Container to store the data of the generated elements. + */ + void generateCleanCells( + mir::Shape shapeType, + const int parentElementID, + const int matOne, + const int matTwo, + const std::vector& elementVertices, + const std::vector>& originalElementVertexVF, + const std::vector& originalElementVertexPositions, + CellData& out_cellData); + +private: + mir::MIRMesh m_originalMesh; +}; +} // namespace mir +} // namespace axom +#endif diff --git a/src/axom/mir/reference/MIRMesh.cpp b/src/axom/mir/reference/MIRMesh.cpp new file mode 100644 index 0000000000..509ccd8c2e --- /dev/null +++ b/src/axom/mir/reference/MIRMesh.cpp @@ -0,0 +1,718 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/mir/reference/MIRMesh.hpp" + +#include "axom/core.hpp" +#include "axom/primal.hpp" + +#include + +namespace axom +{ +namespace mir +{ +MIRMesh::MIRMesh() : m_verts(0), m_elems(0), m_numMaterials(0) { } + +//-------------------------------------------------------------------------------- + +MIRMesh::MIRMesh(const MIRMesh& other) + : m_verts(other.m_verts) + , m_elems(other.m_elems) + , m_materialVolumeFractionsElement(other.m_materialVolumeFractionsElement) + , m_materialVolumeFractionsVertex(other.m_materialVolumeFractionsVertex) + , m_numMaterials(other.m_numMaterials) + , m_meshTopology(other.m_meshTopology) +{ + constructMeshRelations(); + constructVertexPositionMap(other.m_vertexPositions.data()); + constructElementParentMap(other.m_elementParentIDs.data()); + constructElementDominantMaterialMap(other.m_elementDominantMaterials.data()); + + m_shapeTypes = IntMap(&m_elems); + for(auto el : m_elems) + { + m_shapeTypes[el] = other.m_shapeTypes[el]; + } +} + +MIRMesh& MIRMesh::operator=(const MIRMesh& other) +{ + if(this != &other) + { + m_verts = other.m_verts; + m_elems = other.m_elems; + + m_meshTopology = other.m_meshTopology; + + constructMeshRelations(); + constructVertexPositionMap(other.m_vertexPositions.data()); + constructElementParentMap(other.m_elementParentIDs.data()); + constructElementDominantMaterialMap(other.m_elementDominantMaterials.data()); + + m_shapeTypes = IntMap(&m_elems); + for(auto el : m_elems) + { + m_shapeTypes[el] = other.m_shapeTypes[el]; + } + + m_materialVolumeFractionsElement = other.m_materialVolumeFractionsElement; + m_materialVolumeFractionsVertex = other.m_materialVolumeFractionsVertex; + + m_numMaterials = other.m_numMaterials; + } + return *this; +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::initializeMesh(const VertSet _verts, + const ElemSet _elems, + const int _numMaterials, + const CellTopologyData& _topology, + const CellMapData& _mapData, + const std::vector>& _elementVF) +{ + // Initialize the vertex and element sets + m_verts = _verts; + m_elems = _elems; + + m_numMaterials = _numMaterials; + + // Intialize the mesh topology + m_meshTopology.m_evInds = _topology.m_evInds; + m_meshTopology.m_evBegins = _topology.m_evBegins; + m_meshTopology.m_veInds = _topology.m_veInds; + m_meshTopology.m_veBegins = _topology.m_veBegins; + + // Initialize the mesh relations + constructMeshRelations(); + + // Initialize the mesh's data maps + constructVertexPositionMap(_mapData.m_vertexPositions); + constructElementParentMap(_mapData.m_elementParents); + constructElementDominantMaterialMap(_mapData.m_elementDominantMaterials); + constructElementShapeTypesMap(_mapData.m_shapeTypes); + + // Initialize the element and vertex volume fraction maps + if(_elementVF.size() > 0) constructMeshVolumeFractionsMaps(_elementVF); + + // Check validity of the sets + SLIC_ASSERT_MSG(m_verts.isValid(), "Vertex set is not valid."); + SLIC_ASSERT_MSG(m_elems.isValid(), "Element set is not valid."); +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::constructMeshRelations() +{ + // construct boundary relation from elements to vertices + { + using RelationBuilder = ElemToVertRelation::RelationBuilder; + m_bdry = RelationBuilder() + .fromSet(&m_elems) + .toSet(&m_verts) + .begins(RelationBuilder::BeginsSetBuilder() + .size(m_elems.size()) + .data(m_meshTopology.m_evBegins.data())) + .indices(RelationBuilder::IndicesSetBuilder() + .size(m_meshTopology.m_evInds.size()) + .data(m_meshTopology.m_evInds.data())); + } + + { + // construct coboundary relation from vertices to elements + using RelationBuilder = VertToElemRelation::RelationBuilder; + m_cobdry = RelationBuilder() + .fromSet(&m_verts) + .toSet(&m_elems) + .begins(RelationBuilder::BeginsSetBuilder() + .size(m_verts.size()) + .data(m_meshTopology.m_veBegins.data())) + .indices(RelationBuilder::IndicesSetBuilder() + .size(m_meshTopology.m_veInds.size()) + .data(m_meshTopology.m_veInds.data())); + } + + SLIC_ASSERT_MSG(m_bdry.isValid(), "Boundary relation is not valid."); + SLIC_ASSERT_MSG(m_cobdry.isValid(), "Coboundary relation is not valid."); + + // SLIC_DEBUG("Elem-Vert relation has size " << m_bdry.totalSize()); + // SLIC_DEBUG("Vert-Elem relation has size " << m_cobdry.totalSize()); +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::constructMeshVolumeFractionsMaps( + const std::vector>& elementVF) +{ + // Clear the old maps + m_materialVolumeFractionsElement.clear(); + m_materialVolumeFractionsVertex.clear(); + + // Initialize the maps for all of the materials with the input volume fraction data for each material + for(int matID = 0; matID < m_numMaterials; ++matID) + { + // Initialize the map for the current material + m_materialVolumeFractionsElement.push_back(ScalarMap(&m_elems)); + + // Copy the data for the current material + for(int eID = 0; eID < m_elems.size(); ++eID) + { + m_materialVolumeFractionsElement[matID][eID] = elementVF[matID][eID]; + } + + SLIC_ASSERT_MSG(m_materialVolumeFractionsElement[matID].isValid(), + "Element volume fraction map is not valid."); + } + + // Initialize the maps for all of the vertex volume fractions + for(int matID = 0; matID < m_numMaterials; ++matID) + { + // Initialize the new map for the volume fractions + m_materialVolumeFractionsVertex.push_back(ScalarMap(&m_verts)); + + // Calculate the average volume fraction value for the current vertex for the current material + for(int vID = 0; vID < m_verts.size(); ++vID) + { + // Compute the per vertex volume fractions for the green material + axom::float64 sum = 0; + auto vertexElements = m_cobdry[vID]; + + for(int i = 0; i < vertexElements.size(); ++i) + { + auto eID = vertexElements[i]; + sum += m_materialVolumeFractionsElement[matID][eID]; + } + + m_materialVolumeFractionsVertex[matID][vID] = sum / vertexElements.size(); + } + + SLIC_ASSERT_MSG(m_materialVolumeFractionsVertex[matID].isValid(), + "Vertex volume fraction map is not valid."); + } +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::constructMeshVolumeFractionsVertex( + const std::vector>& vertexVF) +{ + m_materialVolumeFractionsVertex.clear(); + + // Initialize the maps for all of the materials with the input volume fraction data for each vertex + for(int matID = 0; matID < m_numMaterials; ++matID) + { + // Initialize the map for the current material + m_materialVolumeFractionsVertex.push_back(ScalarMap(&m_verts)); + + // Copy the data for the current material + for(int vID = 0; vID < m_verts.size(); ++vID) + { + m_materialVolumeFractionsVertex[matID][vID] = vertexVF[matID][vID]; + } + + SLIC_ASSERT_MSG(m_materialVolumeFractionsVertex[matID].isValid(), + "Vertex volume fraction map is not valid."); + } +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::constructVertexPositionMap(const std::vector& data) +{ + // construct the position map on the vertices + m_vertexPositions = PointMap(&m_verts); + + for(int vID = 0; vID < m_verts.size(); ++vID) + m_vertexPositions[vID] = data[vID]; + + SLIC_ASSERT_MSG(m_vertexPositions.isValid(), "Position map is not valid."); +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::constructElementParentMap(const std::vector& elementParents) +{ + // Initialize the map for the elements' parent IDs + m_elementParentIDs = IntMap(&m_elems); + + // Copy the data for the elements + for(int eID = 0; eID < m_elems.size(); ++eID) + m_elementParentIDs[eID] = elementParents[eID]; + + SLIC_ASSERT_MSG(m_elementParentIDs.isValid(), + "Element parent map is not valid."); +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::constructElementDominantMaterialMap( + const std::vector& dominantMaterials) +{ + // Initialize the map for the elements' dominant colors + m_elementDominantMaterials = IntMap(&m_elems); + + // Copy the dat for the elements + for(int eID = 0; eID < m_elems.size(); ++eID) + m_elementDominantMaterials[eID] = dominantMaterials[eID]; + + SLIC_ASSERT_MSG(m_elementDominantMaterials.isValid(), + "Element dominant materials map is not valid."); +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::constructElementShapeTypesMap(const std::vector& shapeTypes) +{ + // Initialize the map for the elements' dominant colors + m_shapeTypes = IntMap(&m_elems); + + // Copy the data for the elements + for(int eID = 0; eID < m_elems.size(); ++eID) + m_shapeTypes[eID] = shapeTypes[eID]; + + SLIC_ASSERT_MSG(m_shapeTypes.isValid(), + "Element dominant materials map is not valid."); +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::print() +{ + printf( + "\n------------------------Printing Mesh " + "Information:------------------------\n"); + printf("number of vertices: %d\n", m_verts.size()); + printf("number of elements: %d\n", m_elems.size()); + printf("number of materials: %d\n", m_numMaterials); + + printf("evInds: { "); + for(unsigned long i = 0; i < m_meshTopology.m_evInds.size(); i++) + { + printf("%d ", m_meshTopology.m_evInds[i]); + } + printf("}\n"); + + printf("evBegins: { "); + for(unsigned long i = 0; i < m_meshTopology.m_evBegins.size(); i++) + { + printf("%d ", m_meshTopology.m_evBegins[i]); + } + printf("}\n"); + + printf("veInds: { "); + for(unsigned long i = 0; i < m_meshTopology.m_veInds.size(); i++) + { + printf("%d ", m_meshTopology.m_veInds[i]); + } + printf("}\n"); + + printf("veBegins: { "); + for(unsigned long i = 0; i < m_meshTopology.m_veBegins.size(); i++) + { + printf("%d ", m_meshTopology.m_veBegins[i]); + } + printf("}\n"); + + printf("vertexPositions: { "); + for(int i = 0; i < m_verts.size(); ++i) + { + std::stringstream sstr; + sstr << m_vertexPositions[i]; + printf("%s", sstr.str().c_str()); + } + printf("}\n"); + + printf("elementParentIDs: { "); + for(int i = 0; i < m_elems.size(); ++i) + { + printf("%d ", m_elementParentIDs[i]); + } + printf("}\n"); + + printf("elementDominantMaterials: { "); + for(int i = 0; i < m_elems.size(); ++i) + { + printf("%d ", m_elementDominantMaterials[i]); + } + printf("}\n"); + + printf("shapeTypes: { "); + for(int i = 0; i < m_elems.size(); ++i) + { + printf("%d ", m_shapeTypes[i]); + } + printf("}\n"); + + printf("elementVolumeFractions: { \n"); + for(unsigned long i = 0; i < m_materialVolumeFractionsElement.size(); ++i) + { + printf(" { "); + for(int j = 0; j < m_elems.size(); ++j) + { + printf("%.3f, ", m_materialVolumeFractionsElement[i][j]); + } + printf("}\n"); + } + printf("}\n"); + + printf("vertexVolumeFractions: { \n"); + for(unsigned long i = 0; i < m_materialVolumeFractionsVertex.size(); ++i) + { + printf(" { "); + for(int j = 0; j < m_verts.size(); ++j) + { + printf("%.3f, ", m_materialVolumeFractionsVertex[i][j]); + } + printf("}\n"); + } + printf("}\n"); + printf( + "--------------------------------------------------------------------------" + "\n"); +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::readMeshFromFile(std::string filename) +{ + printf("Mesh reading functionality not implemented yet. Can't read file: %s", + filename.c_str()); + + // Read in header + + // Read in POINTS + + // Read in CELLS + + // Read in CELL_TYPES + + // Read in CELL_DATA (element volume fractions) +} + +//-------------------------------------------------------------------------------- + +void MIRMesh::writeMeshToFile(const std::string& dirName, + const std::string& fileName, + const std::string& separator) +{ + // Ensure that the directory to write to exists + if(!axom::utilities::filesystem::pathExists(dirName)) + { + axom::utilities::filesystem::makeDirsForPath(dirName); + } + + std::string outputLocation = + axom::utilities::filesystem::joinPath(dirName, fileName, separator); + + std::ofstream meshfile; + meshfile.open(outputLocation); + std::ostream_iterator out_it(meshfile, " "); + + // write header + meshfile << "# vtk DataFile Version 3.0\n" + << "vtk output\n" + << "ASCII\n" + << "DATASET UNSTRUCTURED_GRID\n" + << "POINTS " << m_verts.size() << " double\n"; + + // write positions + for(int vID = 0; vID < m_verts.size(); ++vID) + { + auto& pt = m_vertexPositions[vID]; + for(int i = 0; i < pt.dimension(); ++i) + { + meshfile << pt[i] << " "; + } + meshfile << "\n"; + } + + meshfile << "\nCELLS " << m_elems.size() << " " + << m_meshTopology.m_evInds.size() + m_elems.size(); + for(int i = 0; i < m_elems.size(); ++i) + { + int nVerts = m_meshTopology.m_evBegins[i + 1] - m_meshTopology.m_evBegins[i]; + meshfile << "\n" << nVerts; + for(int j = 0; j < nVerts; ++j) + { + int startIndex = m_meshTopology.m_evBegins[i]; + meshfile << " " << m_meshTopology.m_evInds[startIndex + j]; + } + } + + meshfile << "\n\nCELL_TYPES " << m_elems.size() << "\n"; + for(int i = 0; i < m_elems.size(); ++i) + { + if(m_shapeTypes[i] == mir::Shape::Triangle) + meshfile << "5\n"; + else if(m_shapeTypes[i] == mir::Shape::Quad) + meshfile << "9\n"; + else if(m_shapeTypes[i] == mir::Shape::Tetrahedron) + meshfile << "10\n"; + else if(m_shapeTypes[i] == mir::Shape::Triangular_Prism) + meshfile << "13\n"; + else if(m_shapeTypes[i] == mir::Shape::Pyramid) + meshfile << "14\n"; + else if(m_shapeTypes[i] == mir::Shape::Hexahedron) + meshfile << "12\n"; + } + + // write element materials + meshfile << "\n\nCELL_DATA " << m_elems.size() << "\nSCALARS cellIds int 1" + << "\nLOOKUP_TABLE default \n"; + for(int i = 0; i < m_elems.size(); ++i) + { + meshfile << m_elementDominantMaterials[i] << " "; + } + + meshfile << "\n"; +} + +//-------------------------------------------------------------------------------- + +std::vector> MIRMesh::computeOriginalElementVolumeFractions() +{ + std::map totalAreaOriginalElements; // the total area of the original elements + std::map newElementAreas; // the area of each of the generated child elements + std::map numChildren; // the number of child elements generated from one of the original elements + + // Compute the total area of each element of the original mesh and also the area of each new element + for(int eID = 0; eID < m_elems.size(); ++eID) + { + int numVertices = + m_meshTopology.m_evBegins[eID + 1] - m_meshTopology.m_evBegins[eID]; + if(numVertices == 3) + { + Point2 trianglePoints[3]; + for(int i = 0; i < 3; ++i) + trianglePoints[i] = + m_vertexPositions[m_meshTopology.m_evInds[m_meshTopology.m_evBegins[eID] + i]]; + newElementAreas[eID] = computeTriangleArea(trianglePoints[0], + trianglePoints[1], + trianglePoints[2]); + } + if(numVertices == 4) + { + Point2 trianglePoints[4]; + for(int i = 0; i < 4; ++i) + trianglePoints[i] = + m_vertexPositions[m_meshTopology.m_evInds[m_meshTopology.m_evBegins[eID] + i]]; + newElementAreas[eID] = computeQuadArea(trianglePoints[0], + trianglePoints[1], + trianglePoints[2], + trianglePoints[3]); + } + + totalAreaOriginalElements[m_elementParentIDs[eID]] += newElementAreas[eID]; + numChildren[m_elementParentIDs[eID]] += .4; + } + + // Intialize the element volume fraction vectors + std::vector> + elementVolumeFractions; // indexed as: elementVolumeFractions[material][originalElementID] = volumeFraction + elementVolumeFractions.resize(m_numMaterials); + for(unsigned long i = 0; i < elementVolumeFractions.size(); ++i) + elementVolumeFractions[i].resize(totalAreaOriginalElements.size(), 0.0); + + // Compute the volume fractions for each of the original mesh elements + for(auto itr = newElementAreas.begin(); itr != newElementAreas.end(); itr++) + { + int parentElementID = m_elementParentIDs[itr->first]; + int materialID = m_elementDominantMaterials[itr->first]; + elementVolumeFractions[materialID][parentElementID] += + (itr->second / totalAreaOriginalElements[parentElementID]); + } + + return elementVolumeFractions; +} + +//-------------------------------------------------------------------------------- + +axom::float64 MIRMesh::computeTriangleArea(Point2 p0, Point2 p1, Point2 p2) +{ + return primal::Triangle(p0, p1, p2).area(); +} + +//-------------------------------------------------------------------------------- + +axom::float64 MIRMesh::computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3) +{ + return computeTriangleArea(p0, p1, p2) + computeTriangleArea(p2, p3, p0); +} + +//-------------------------------------------------------------------------------- + +bool MIRMesh::isValid(bool verbose) const +{ + bool bValid = true; + + // Check the topology data + bValid = bValid && isTopologyValid(verbose); + + // Check the volume fraction data + bValid = bValid && areVolumeFractionsValid(verbose); + + // Check the map data + bValid = bValid && areMapsValid(verbose); + + return bValid; +} + +bool MIRMesh::isTopologyValid(bool verbose) const +{ + bool bValid = true; + + // check the vertex and element sets + bValid = bValid && m_verts.isValid(verbose); + bValid = bValid && m_elems.isValid(verbose); + + // check the relations + if(!m_verts.empty() && !m_elems.empty()) + { + bValid = bValid && m_bdry.isValid(verbose); + bValid = bValid && m_cobdry.isValid(verbose); + } + + if(!bValid && verbose) + { + SLIC_WARNING("MIRMesh invalid: Invalid topology"); + } + + return bValid; +} + +bool MIRMesh::areVolumeFractionsValid(bool verbose) const +{ + bool bValid = true; + + int vfElemSize = m_materialVolumeFractionsElement.size(); + if(vfElemSize != m_numMaterials) + { + bValid = false; + if(verbose) + { + SLIC_WARNING( + "MIRMesh invalid: Invalid number of materials in volume fraction " + "array."); + } + return bValid; + } + + // Check the sizes of the volume fraction arrays + for(int i = 0; i < m_numMaterials; ++i) + { + const auto& mats = m_materialVolumeFractionsElement[i]; + if(mats.size() != m_elems.size()) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Material " + << i << " has wrong " + << " number of materials in volume fraction array." + << " Expected " << m_elems.size() << ", got " + << mats.size() << "."); + } + } + } + + if(!bValid) + { + return false; + } + + // Check that the volume fractions sum to 1.0 + for(auto el : m_elems.positions()) + { + double sum = 0; + for(int m = 0; m < m_numMaterials; ++m) + { + sum += m_materialVolumeFractionsElement[m][el]; + } + + if(!axom::utilities::isNearlyEqual(sum, 1.)) + { + bValid = false; + if(verbose) + { + std::stringstream sstr; + + for(int m = 0; m < m_numMaterials; ++m) + { + sstr << m_materialVolumeFractionsElement[m][el] << " "; + } + + SLIC_WARNING("MIRMesh invalid: Sum of materials in element" + << el << " was " << sum << ". Expected 1." + << " Values were: " << sstr.str()); + } + } + } + + return bValid; +} + +bool MIRMesh::areMapsValid(bool verbose) const +{ + bool bValid = true; + + // Check the positions map + if(m_vertexPositions.size() != m_verts.size()) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Incorrect number of vertex positions." + << " Expected " << m_verts.size() << ". Got " + << m_vertexPositions.size()); + } + } + + bValid = bValid && m_vertexPositions.isValid(verbose); + + // Check the element maps + bValid = bValid && m_elementParentIDs.isValid(verbose); + bValid = bValid && m_elementDominantMaterials.isValid(verbose); + bValid = bValid && m_shapeTypes.isValid(verbose); + + if(m_elementParentIDs.size() != m_elems.size()) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Incorrect number of elem parent IDs." + << " Expected " << m_elems.size() << ". Got " + << m_elementParentIDs.size()); + } + } + + if(m_elementDominantMaterials.size() != m_elems.size()) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Incorrect number of elem dominant mats." + << " Expected " << m_elems.size() << ". Got " + << m_elementDominantMaterials.size()); + } + } + + if(m_shapeTypes.size() != m_elems.size()) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Incorrect number of elem shape types." + << " Expected " << m_elems.size() << ". Got " + << m_shapeTypes.size()); + } + } + + return bValid; +} + +} // namespace mir +} // namespace axom diff --git a/src/axom/mir/reference/MIRMesh.hpp b/src/axom/mir/reference/MIRMesh.hpp new file mode 100644 index 0000000000..4060789a79 --- /dev/null +++ b/src/axom/mir/reference/MIRMesh.hpp @@ -0,0 +1,233 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file MIRMesh.hpp + * + * \brief Contains the specification for the MIRMesh class. + * + */ + +#ifndef __MIR_MESH_H__ +#define __MIR_MESH_H__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" // unified header for slam classes and functions + +#include "axom/mir/reference/MIRMeshTypes.hpp" +#include "axom/mir/reference/CellData.hpp" + +// C/C++ includes +#include // for definition of M_PI, exp() +#include +#include +#include + +namespace numerics = axom::numerics; +namespace slam = axom::slam; + +//-------------------------------------------------------------------------------- +namespace axom +{ +namespace mir +{ +const int NULL_MAT = -1; + +//-------------------------------------------------------------------------------- + +/** + * \class MIRMesh + * + * \brief The MIRMesh class represents a finite element mesh containing element volume fractions. + * + * \detail This class is meant to be used in conjunction with the InterfaceReconstructor class + * to process the mesh. + * + */ +class MIRMesh +{ +public: + /** + * \brief Default constructor. + */ + MIRMesh(); + + /** + * \brief Copy constructor. + */ + MIRMesh(const MIRMesh& other); + + /** + * \brief Default destructor. + */ + ~MIRMesh() = default; + + /** + * \brief Copy assignment. + */ + MIRMesh& operator=(const MIRMesh& other); + + /** + * \brief Initializes a mesh with the provided data and topology. + * + * \param _verts The set of vertices of the mesh. + * \param _elems The set of elements of the mesh. + * \param _numMaterials The number of materials present in the mesh. + * \param _topology The topology/connectivity of the mesh. + * \param _mapData The data used to initialized the maps associated with the vertex and element sets. + * \param _elementVF The volume fractions of each element. Note that this is an optional parameter. + */ + void initializeMesh( + const VertSet _verts, + const ElemSet _elems, + const int _numMaterials, + const CellTopologyData& _topology, + const CellMapData& _mapData, + const std::vector>& _elementVF = {}); + + /** + * \brief Constructs the mesh boundary and coboundary relations. + * + * \note The boundary relation is from each element to its vertices. + * \note The coboundary relation is from each vertex to its elements. + */ + void constructMeshRelations(); + + /** + * \brief Constructs the element and vertex volume fraction maps. + * + * \param elementVF The volume fractions of each element. + */ + void constructMeshVolumeFractionsMaps( + const std::vector>& elementVF); + + /** + * \brief Constructs the vertex volume fraction map. + * + * \param vertexVF The volume fractions of each vertex. + */ + void constructMeshVolumeFractionsVertex( + const std::vector>& vertexVF); + + /** + * \brief Prints out the data contained within this mesh in a nice format. + */ + void print(); + + /** + * \brief Reads in a mesh specified by the given file. + * + * \param filename The location where the mesh file will be read from. + */ + void readMeshFromFile(const std::string filename); + + /** + * \brief Writes out the mesh to the given file. + * + * \param filename The location where the mesh file will be written. + * + * \note Currently reads in an ASCII, UNSTRUCTURED_GRID .vtk file. + */ + void writeMeshToFile(const std::string& dirName, + const std::string& fileName, + const std::string& separator = "/"); + + /** + * \brief Computes the volume fractions of the elements of the original mesh. + */ + std::vector> computeOriginalElementVolumeFractions(); + + /** + * \brief Checks that this instance of MIRMesh is valid + * + * \return True if this instance is valid, false otherwise + */ + bool isValid(bool verbose) const; + +private: + /// Utility predicate to check if the mesh's topology is valid + bool isTopologyValid(bool verbose) const; + /// Utility predicate to check if the mesh's volume fractions are valid + bool areVolumeFractionsValid(bool verbose) const; + /// Utility predicate to check if the mesh's maps are valid + bool areMapsValid(bool verbose) const; + + /** + * \brief Constructs the positions map on the vertices. + * + * \param data The vector of position data for each vertex. + */ + void constructVertexPositionMap(const std::vector& data); + + /** + * \brief Constructs the map of elements to their original element parent. + * + * \param cellParents The vector of parent IDs for each element of the mesh. + */ + void constructElementParentMap(const std::vector& elementParents); + + /** + * \brief Constructs the map of elements to their dominant materials. + * + * \param dominantMaterials A vector of material ids that are the dominant material of each element. + */ + void constructElementDominantMaterialMap(const std::vector& dominantMaterials); + + /** + * \brief Constructs the map of elements to their shape types. + * + * \param shapeTypes A vector of shape enumerators that are the shape type of each element. + */ + void constructElementShapeTypesMap(const std::vector& shapeTypes); + + /** + * \brief Computes the area of the triangle defined by the given three vertex positions using Heron's formula. + * + * \param p0 The position of the first vertex. + * \param p1 The position of the second vertex. + * \param p2 The position of the third vertex. + */ + axom::float64 computeTriangleArea(Point2 p0, Point2 p1, Point2 p2); + + /** + * \brief Computes the area of the quad defined by the given four vertex positions. + * + * \param p0 The position of the first vertex. + * \param p1 The position of the second vertex. + * \param p2 The position of the third vertex. + * \param p3 The position of the fourth vertex. + * + * \note It is assumed that the points are given in consecutive, counter-clockwise order. + */ + axom::float64 computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3); + + /**************************************************************** + * VARIABLES + ****************************************************************/ +public: + // Mesh Set Definitions + VertSet m_verts; // the set of vertices in the mesh + ElemSet m_elems; // the set of elements in the mesh + + // Mesh Relation Definitions + ElemToVertRelation m_bdry; // Boundary relation from elements to vertices + VertToElemRelation m_cobdry; // Coboundary relation from vertices to elements + + // Mesh Map Definitions + PointMap m_vertexPositions; // vertex position for each vertex + std::vector m_materialVolumeFractionsElement; // the volume fractions of each material for each element + std::vector m_materialVolumeFractionsVertex; // the volume fractions of each material for each vertex + IntMap m_elementParentIDs; // the ID of the parent element from the original mesh + IntMap m_elementDominantMaterials; // the dominant material of the cell (a processed mesh should have only one material per cell) + IntMap m_shapeTypes; // the int enumerator of what type of shape each element is + + int m_numMaterials; // the number of materials present in the mesh + CellTopologyData m_meshTopology; // the topology/connectivity of the mesh +}; + +//-------------------------------------------------------------------------------- +} // namespace mir +} // namespace axom +#endif diff --git a/src/axom/mir/reference/MIRMeshTypes.hpp b/src/axom/mir/reference/MIRMeshTypes.hpp new file mode 100644 index 0000000000..6529aaaece --- /dev/null +++ b/src/axom/mir/reference/MIRMeshTypes.hpp @@ -0,0 +1,62 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file MIRMeshTypes.hpp + * + * \brief Contains the specifications for types aliases used throughout the MIR component. + */ + +#ifndef __MIR_MESH_TYPES_H__ +#define __MIR_MESH_TYPES_H__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" +#include "axom/primal.hpp" + +namespace axom +{ +namespace mir +{ +enum Shape +{ + Triangle, + Quad, + Tetrahedron, + Triangular_Prism, + Pyramid, + Hexahedron +}; + +using Point2 = primal::Point; + +// SET TYPE ALIASES +using PosType = slam::DefaultPositionType; +using ElemType = slam::DefaultElementType; + +using ArrayIndir = slam::policies::CArrayIndirection; + +using VertSet = slam::PositionSet; +using ElemSet = slam::PositionSet; + +// RELATION TYPE ALIASES +using VarCard = slam::policies::VariableCardinality; + +// Note: This is the actual relation type, which takes in a bunch of policies and data entries to relate to each other. +// Note: It is the relation of the elements to the vertices. + +using ElemToVertRelation = + slam::StaticRelation; +using VertToElemRelation = + slam::StaticRelation; + +// MAP TYPE ALIASES +using BaseSet = slam::Set; +using ScalarMap = slam::Map; +using PointMap = slam::Map; +using IntMap = slam::Map; +} // namespace mir +} // namespace axom +#endif diff --git a/src/axom/mir/reference/MIRUtilities.hpp b/src/axom/mir/reference/MIRUtilities.hpp new file mode 100644 index 0000000000..1a7bc96cc9 --- /dev/null +++ b/src/axom/mir/reference/MIRUtilities.hpp @@ -0,0 +1,660 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file MIRUtilities.hpp + * + * \brief Contains a set of functions that provide general utility + * within Axom's MIR component. + * + */ + +#ifndef __MIR_UTILITIES_HPP__ +#define __MIR_UTILITIES_HPP__ + +#include "axom/mir/reference/ZooClippingTables.hpp" + +//-------------------------------------------------------------------------------- + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +//-------------------------------------------------------------------------------- + +/** + * \brief Determines the number of vertices of the given shape in the finite element zoo. + * + * \param shape The shape type from the finite element zoo. + * + * \return THe number of vertices of the shape. + */ +inline int numVerts(mir::Shape shape) +{ + int numVertices = 0; + switch(shape) + { + case mir::Shape::Triangle: + numVertices = 3; + break; + case mir::Shape::Quad: + numVertices = 4; + break; + case mir::Shape::Tetrahedron: + numVertices = 4; + break; + case mir::Shape::Pyramid: + numVertices = 5; + break; + case mir::Shape::Triangular_Prism: + numVertices = 6; + break; + case mir::Shape::Hexahedron: + numVertices = 8; + break; + default: + printf("Invalid shape. Cannot determine numVerts().\n"); + } + return numVertices; +} + +//-------------------------------------------------------------------------------- + +/** + * \brief Determines the maximum number of possible vertices of the given shape in the finite element zoo. + * This number includes the midpoint vertices between each of the original shape's vertices. + * + * \param shape The shape type from the finite element zoo. + * + * \return THe number of vertices of the shape. + */ +inline int maxPossibleNumVerts(mir::Shape shape) +{ + int numVertices = -1; + switch(shape) + { + case mir::Shape::Triangle: + numVertices = 6 + 1; // add one for the central vertex (not used) + break; + case mir::Shape::Quad: + numVertices = 8 + 1; // add one for the central vertex (not used) + break; + case mir::Shape::Tetrahedron: + numVertices = 10 + 1; // add one for the central vertex (not used) + break; + case mir::Shape::Pyramid: + numVertices = 13 + 1; // add one for the central vertex + break; + case mir::Shape::Triangular_Prism: + numVertices = 15 + 1; // add one for the central vertex + break; + case mir::Shape::Hexahedron: + numVertices = 20 + 1; // add one for the central vertex + break; + default: + printf("Invalid shape. Cannot determine maxPossibleNumVerts().\n"); + } + return numVertices; +} + +//-------------------------------------------------------------------------------- + +/** + * \brief Returns the local vertex ID of the from/to vertex that is one of + * the two endpoints that the edge the given midpoint is on. + * + * \param shapeType The shape type from the finite element zoo. + * \param midpointVertexID The ID of the vertex between the two endpoints. + * \param isFromVertex A flag denoting which of the two edge endpoints to return. + * + * \return The vertex ID of one of the endpoints. + */ +inline int getEdgeEndpoint(const mir::Shape shapeType, + const int midpointVertexID, + const bool isFromVertex) +{ + switch(shapeType) + { + case mir::Shape::Triangle: + if(midpointVertexID == 3 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 3 && !isFromVertex) + { + return 1; + } + if(midpointVertexID == 4 && isFromVertex) + { + return 1; + } + if(midpointVertexID == 4 && !isFromVertex) + { + return 2; + } + if(midpointVertexID == 5 && isFromVertex) + { + return 2; + } + if(midpointVertexID == 5 && !isFromVertex) + { + return 0; + } + break; + case mir::Shape::Quad: + if(midpointVertexID == 4 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 4 && !isFromVertex) + { + return 1; + } + if(midpointVertexID == 5 && isFromVertex) + { + return 1; + } + if(midpointVertexID == 5 && !isFromVertex) + { + return 2; + } + if(midpointVertexID == 6 && isFromVertex) + { + return 2; + } + if(midpointVertexID == 6 && !isFromVertex) + { + return 3; + } + if(midpointVertexID == 7 && isFromVertex) + { + return 3; + } + if(midpointVertexID == 7 && !isFromVertex) + { + return 0; + } + break; + case mir::Shape::Tetrahedron: + if(midpointVertexID == 4 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 4 && !isFromVertex) + { + return 1; + } + if(midpointVertexID == 5 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 5 && !isFromVertex) + { + return 2; + } + if(midpointVertexID == 6 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 6 && !isFromVertex) + { + return 3; + } + if(midpointVertexID == 7 && isFromVertex) + { + return 1; + } + if(midpointVertexID == 7 && !isFromVertex) + { + return 2; + } + if(midpointVertexID == 8 && isFromVertex) + { + return 2; + } + if(midpointVertexID == 8 && !isFromVertex) + { + return 3; + } + if(midpointVertexID == 9 && isFromVertex) + { + return 3; + } + if(midpointVertexID == 9 && !isFromVertex) + { + return 1; + } + break; + case mir::Shape::Pyramid: + if(midpointVertexID == 5 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 5 && !isFromVertex) + { + return 1; + } + if(midpointVertexID == 6 && isFromVertex) + { + return 1; + } + if(midpointVertexID == 6 && !isFromVertex) + { + return 2; + } + if(midpointVertexID == 7 && isFromVertex) + { + return 2; + } + if(midpointVertexID == 7 && !isFromVertex) + { + return 3; + } + if(midpointVertexID == 8 && isFromVertex) + { + return 3; + } + if(midpointVertexID == 8 && !isFromVertex) + { + return 0; + } + + if(midpointVertexID == 9 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 9 && !isFromVertex) + { + return 4; + } + if(midpointVertexID == 10 && isFromVertex) + { + return 1; + } + if(midpointVertexID == 10 && !isFromVertex) + { + return 4; + } + if(midpointVertexID == 11 && isFromVertex) + { + return 2; + } + if(midpointVertexID == 11 && !isFromVertex) + { + return 4; + } + if(midpointVertexID == 12 && isFromVertex) + { + return 3; + } + if(midpointVertexID == 12 && !isFromVertex) + { + return 4; + } + break; + case mir::Shape::Triangular_Prism: + if(midpointVertexID == 6 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 6 && !isFromVertex) + { + return 1; + } + if(midpointVertexID == 7 && isFromVertex) + { + return 1; + } + if(midpointVertexID == 7 && !isFromVertex) + { + return 2; + } + if(midpointVertexID == 8 && isFromVertex) + { + return 2; + } + if(midpointVertexID == 8 && !isFromVertex) + { + return 0; + } + + if(midpointVertexID == 9 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 9 && !isFromVertex) + { + return 3; + } + if(midpointVertexID == 10 && isFromVertex) + { + return 1; + } + if(midpointVertexID == 10 && !isFromVertex) + { + return 4; + } + if(midpointVertexID == 11 && isFromVertex) + { + return 2; + } + if(midpointVertexID == 11 && !isFromVertex) + { + return 5; + } + + if(midpointVertexID == 12 && isFromVertex) + { + return 3; + } + if(midpointVertexID == 12 && !isFromVertex) + { + return 4; + } + if(midpointVertexID == 13 && isFromVertex) + { + return 4; + } + if(midpointVertexID == 13 && !isFromVertex) + { + return 5; + } + if(midpointVertexID == 14 && isFromVertex) + { + return 5; + } + if(midpointVertexID == 14 && !isFromVertex) + { + return 3; + } + break; + case mir::Shape::Hexahedron: + if(midpointVertexID == 8 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 8 && !isFromVertex) + { + return 1; + } + if(midpointVertexID == 9 && isFromVertex) + { + return 1; + } + if(midpointVertexID == 9 && !isFromVertex) + { + return 2; + } + if(midpointVertexID == 10 && isFromVertex) + { + return 2; + } + if(midpointVertexID == 10 && !isFromVertex) + { + return 3; + } + if(midpointVertexID == 11 && isFromVertex) + { + return 3; + } + if(midpointVertexID == 11 && !isFromVertex) + { + return 0; + } + + if(midpointVertexID == 12 && isFromVertex) + { + return 4; + } + if(midpointVertexID == 12 && !isFromVertex) + { + return 5; + } + if(midpointVertexID == 13 && isFromVertex) + { + return 5; + } + if(midpointVertexID == 13 && !isFromVertex) + { + return 6; + } + if(midpointVertexID == 14 && isFromVertex) + { + return 6; + } + if(midpointVertexID == 14 && !isFromVertex) + { + return 7; + } + if(midpointVertexID == 15 && isFromVertex) + { + return 7; + } + if(midpointVertexID == 15 && !isFromVertex) + { + return 4; + } + + if(midpointVertexID == 16 && isFromVertex) + { + return 0; + } + if(midpointVertexID == 16 && !isFromVertex) + { + return 4; + } + if(midpointVertexID == 17 && isFromVertex) + { + return 1; + } + if(midpointVertexID == 17 && !isFromVertex) + { + return 5; + } + if(midpointVertexID == 18 && isFromVertex) + { + return 2; + } + if(midpointVertexID == 18 && !isFromVertex) + { + return 6; + } + if(midpointVertexID == 19 && isFromVertex) + { + return 3; + } + if(midpointVertexID == 19 && !isFromVertex) + { + return 7; + } + break; + default: + printf("Edge endpoint case not implemented.\n"); + return -1; + break; + } + return -1; +} + +//-------------------------------------------------------------------------------- + +/** + * \brief Determines if the shape type is a three dimensional shape or not. + * + * \param shapeType The shape type from the finite element zoo. + * + * \return True, if the shape is a tetrahedron, pyramid, triangular prism, or a hexahedron. + */ +inline bool isShapeThreeDimensional(const mir::Shape shapeType) +{ + return (shapeType == mir::Tetrahedron || shapeType == mir::Pyramid || + shapeType == mir::Triangular_Prism || shapeType == mir::Hexahedron); +} + +//-------------------------------------------------------------------------------- + +/** + * \brief Get the local vertex ID of the central vertex used when decomposing a 3D shape. + * + * \param shapeType The shape type from the finite element zoo. + * + * \return The local vertex ID of the central vertex of a 3D shape. + * + * \note 2D shapes do not have a central vertex because they do not decompose. + */ +inline int getCenterVertex(const mir::Shape shapeType) +{ + switch(shapeType) + { + case mir::Tetrahedron: + return 10; + case mir::Pyramid: + return 13; + case mir::Triangular_Prism: + return 15; + case mir::Hexahedron: + return 20; + default: + return -1; + } +} + +//-------------------------------------------------------------------------------- + +/** + * \brief Determines if the given vertex is the central vertex of a 3D shape. + * + * \param shapeType The shape type from the finite element zoo. + * \param vID The local vertex ID. + * + * \return True, if vertex ID is that of the central vertex of a 3D shape. + * + * \note 2D shapes do not have a central vertex because they do not decompose. + */ +inline bool isCenterVertex(const mir::Shape shapeType, const int vID) +{ + if(shapeType == mir::Tetrahedron && vID == 10) + return true; + else if(shapeType == mir::Pyramid && vID == 13) + return true; + else if(shapeType == mir::Triangular_Prism && vID == 15) + return true; + else if(shapeType == mir::Hexahedron && vID == 20) + return true; + else + return false; +} + +//-------------------------------------------------------------------------------- + +/** + * \brief Computes the average value of the float values given. + * + * \param values A vector of float values. + * + * \return The average value. + */ +inline axom::float64 computeAverageFloat(const std::vector& values) +{ + axom::float64 sum = 0.0; + for(unsigned long i = 0; i < values.size(); ++i) + { + sum += values[i]; + } + return sum / (axom::float64)values.size(); +} + +//-------------------------------------------------------------------------------- + +/** + * \brief Computes the centroid for the points given. + * + * \param A vector of points. + * + * \return The centroid point. + */ +inline mir::Point2 computeAveragePoint(const std::vector& points) +{ + mir::Point2 centroid; + for(auto i = 0u; i < points.size(); ++i) + { + centroid.array() += points[i].array(); + } + centroid.array() /= (axom::float64)points.size(); + + return centroid; +} + +//-------------------------------------------------------------------------------- + +/** + * \brief Determine the shape type of an element. + * + * \param parentShapeType The shape of the element from which the new element is generated. + * \param numVerts The number of vertices of the new element. + * + * \note It is assumed that the given cell is one that results from splitting its parent cell. + */ +inline mir::Shape determineElementShapeType(const Shape parentShapeType, + const int numVerts) +{ + mir::Shape newShapeType; + if(parentShapeType == mir::Shape::Triangle || + parentShapeType == mir::Shape::Quad) + { + // Handle the two-dimensional case + switch(numVerts) + { + case 3: + newShapeType = mir::Shape::Triangle; + break; + case 4: + newShapeType = mir::Shape::Quad; + break; + default: + newShapeType = mir::Shape::Triangle; + printf( + "2D Case: Invalid number of vertices in " + "determineElementShapeType().\n"); + break; + } + } + else + { + // Handle the three-dimensional case + switch(numVerts) + { + case 4: + newShapeType = mir::Shape::Tetrahedron; + break; + case 5: + newShapeType = mir::Shape::Pyramid; + break; + case 6: + newShapeType = mir::Shape::Triangular_Prism; + break; + case 8: + newShapeType = mir::Shape::Hexahedron; + break; + default: + newShapeType = mir::Shape::Tetrahedron; + printf( + "3D Case: Invalid number of vertices in " + "determineElementShapeType().\n"); + break; + } + } + return newShapeType; +} + +//-------------------------------------------------------------------------------- + +} // namespace utilities +} // namespace mir +} // namespace axom + +#endif diff --git a/src/axom/mir/reference/README.md b/src/axom/mir/reference/README.md new file mode 100644 index 0000000000..7c5137ec5c --- /dev/null +++ b/src/axom/mir/reference/README.md @@ -0,0 +1,3 @@ +This is a deprecated serial reference implementation that was developed as +a summer project. The plan is to remove it once the newer CPU/GPU implementation +supports the iteration scheme in EquiZAlgorithm. diff --git a/src/axom/mir/reference/ZooClippingTables.cpp b/src/axom/mir/reference/ZooClippingTables.cpp new file mode 100644 index 0000000000..e28b4a6eb7 --- /dev/null +++ b/src/axom/mir/reference/ZooClippingTables.cpp @@ -0,0 +1,794 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "ZooClippingTables.hpp" + +namespace axom +{ +namespace mir +{ +/* + Quad Vertex Local Indices + + 0 7 3 + @---------@---------@ + | | + | | + | | + 4 @ @ 6 + | | + | | + | | + @---------@---------@ + 1 5 2 + */ +const int quadClipTable[16][19] = { + {4, 0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 7, 6, 4, 7, 0, 2, 6, 3, 0, 1, 2, -1, -1, -1, -1, -1, -1}, + {3, 6, 5, 2, 4, 3, 1, 5, 6, 3, 0, 1, 3, -1, -1, -1, -1, -1, -1}, + {4, 0, 1, 5, 7, 4, 7, 5, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 4, 1, 5, 4, 0, 4, 5, 2, 3, 0, 2, 3, -1, -1, -1, -1, -1, -1}, + {3, 4, 1, 5, 4, 0, 4, 5, 2, 4, 0, 2, 6, 7, 3, 7, 6, 3, -1}, + {4, 0, 4, 6, 3, 4, 4, 1, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 4, 7, 4, 4, 1, 3, 7, 3, 1, 2, 3, -1, -1, -1, -1, -1, -1}, + {3, 0, 4, 7, 4, 4, 1, 3, 7, 3, 1, 2, 3, -1, -1, -1, -1, -1, -1}, + {4, 0, 4, 6, 3, 4, 4, 1, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 4, 1, 5, 4, 0, 4, 5, 2, 4, 0, 2, 6, 7, 3, 7, 6, 3, -1}, + {3, 4, 1, 5, 4, 0, 4, 5, 2, 3, 0, 2, 3, -1, -1, -1, -1, -1, -1}, + {4, 0, 1, 5, 7, 4, 7, 5, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 6, 5, 2, 4, 3, 1, 5, 6, 3, 0, 1, 3, -1, -1, -1, -1, -1, -1}, + {3, 3, 7, 6, 4, 7, 0, 2, 6, 3, 0, 1, 2, -1, -1, -1, -1, -1, -1}, + {4, 0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}; + +const std::vector> quadClipTableVec = { + {4, 0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 7, 6, 4, 7, 0, 2, 6, 3, 0, 1, 2, -1, -1, -1, -1, -1, -1}, + {3, 6, 5, 2, 4, 3, 1, 5, 6, 3, 0, 1, 3, -1, -1, -1, -1, -1, -1}, + {4, 0, 1, 5, 7, 4, 7, 5, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 4, 1, 5, 4, 0, 4, 5, 2, 3, 0, 2, 3, -1, -1, -1, -1, -1, -1}, + {3, 4, 1, 5, 4, 0, 4, 5, 2, 4, 0, 2, 6, 7, 3, 7, 6, 3, -1}, + {4, 0, 4, 6, 3, 4, 4, 1, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 4, 7, 4, 4, 1, 3, 7, 3, 1, 2, 3, -1, -1, -1, -1, -1, -1}, + {3, 0, 4, 7, 4, 4, 1, 3, 7, 3, 1, 2, 3, -1, -1, -1, -1, -1, -1}, + {4, 0, 4, 6, 3, 4, 4, 1, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 4, 1, 5, 4, 0, 4, 5, 2, 4, 0, 2, 6, 7, 3, 7, 6, 3, -1}, + {3, 4, 1, 5, 4, 0, 4, 5, 2, 3, 0, 2, 3, -1, -1, -1, -1, -1, -1}, + {4, 0, 1, 5, 7, 4, 7, 5, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 6, 5, 2, 4, 3, 1, 5, 6, 3, 0, 1, 3, -1, -1, -1, -1, -1, -1}, + {3, 3, 7, 6, 4, 7, 0, 2, 6, 3, 0, 1, 2, -1, -1, -1, -1, -1, -1}, + {4, 0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}; + +/* + Triangle Vertex Local Indices + 0 + @ + / \ + / \ + 3 @ @ 5 + / \ + / \ + 1 @-----@-----@ 2 + 4 + */ +const int triangleClipTable[8][10] = {{3, 0, 1, 2, -1, -1, -1, -1, -1, -1}, + {4, 0, 1, 4, 5, 3, 5, 4, 2, -1}, + {4, 0, 3, 4, 2, 3, 3, 1, 4, -1}, + {4, 3, 1, 2, 5, 3, 0, 3, 5, -1}, + {4, 3, 1, 2, 5, 3, 0, 3, 5, -1}, + {4, 0, 3, 4, 2, 3, 3, 1, 4, -1}, + {4, 0, 1, 4, 5, 3, 5, 4, 2, -1}, + {3, 0, 1, 2, -1, -1, -1, -1, -1, -1}}; + +const std::vector> triangleClipTableVec = { + {3, 0, 1, 2, -1, -1, -1, -1, -1, -1}, + {4, 0, 1, 4, 5, 3, 5, 4, 2, -1}, + {4, 0, 3, 4, 2, 3, 3, 1, 4, -1}, + {4, 3, 1, 2, 5, 3, 0, 3, 5, -1}, + {4, 3, 1, 2, 5, 3, 0, 3, 5, -1}, + {4, 0, 3, 4, 2, 3, 3, 1, 4, -1}, + {4, 0, 1, 4, 5, 3, 5, 4, 2, -1}, + {3, 0, 1, 2, -1, -1, -1, -1, -1, -1}}; + +const std::vector> tetrahedronClipTableVec = { + {4, 0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 3, 6, 9, 8, 6, 0, 1, 2, 6, 9, 8, -1}, + {4, 2, 5, 7, 8, 6, 0, 1, 3, 5, 7, 8, -1}, + {6, 2, 5, 7, 3, 6, 9, 6, 0, 5, 6, 1, 7, 9, -1}, + {4, 1, 4, 7, 9, 6, 0, 2, 3, 4, 7, 9, -1}, + {6, 1, 4, 7, 3, 6, 8, 6, 0, 4, 6, 2, 7, 8, -1}, + {6, 1, 4, 9, 2, 5, 8, 6, 0, 4, 5, 3, 9, 8, -1}, + {4, 0, 4, 5, 6, 6, 1, 2, 3, 4, 5, 6, -1}, + + {4, 0, 4, 5, 6, 6, 1, 2, 3, 4, 5, 6, -1}, + {6, 1, 4, 9, 2, 5, 8, 6, 0, 4, 5, 3, 9, 8, -1}, + {6, 1, 4, 7, 3, 6, 8, 6, 0, 4, 6, 2, 7, 8, -1}, + {4, 1, 4, 7, 9, 6, 0, 2, 3, 4, 7, 9, -1}, + {6, 2, 5, 7, 3, 6, 9, 6, 0, 5, 6, 1, 7, 9, -1}, + {4, 2, 5, 7, 8, 6, 0, 1, 3, 5, 7, 8, -1}, + {4, 3, 6, 9, 8, 6, 0, 1, 2, 6, 9, 8, -1}, + {4, 0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, +}; + +// {5,0,1,2,3,13,4,0,1,4,13,4,1,2,4,13,4,2,3,4,13,4,3,0,4,13,-1}, // correct decomp +// {4,0,1,4,13,4,1,2,4,13,4,2,3,4,13,4,3,0,4,13,4,0,1,3,13,4,1,2,3,13,-1} // incorect decomp (currently used) +const std::vector> pyramidClipTableVec = { + {5, 0, 1, 2, 3, 4, -1}, + {5, 9, 10, 11, 12, 4, 8, 9, 10, 11, 12, 0, 1, 2, 3, -1}, + {4, 3, 8, 7, 12, 6, 8, 7, 12, 0, 2, 4, 4, 0, 1, 2, 4, -1}, + {6, 3, 8, 7, 4, 9, 11, 4, 4, 9, 10, 11, 6, 9, + 8, 0, 11, 7, 2, 6, 9, 10, 11, 0, 1, 2, -1}, + {4, 2, 6, 7, 11, 6, 6, 7, 11, 1, 3, 4, 4, 0, 1, 3, 4, -1}, + {6, 2, 6, 7, 4, 10, 12, 4, 4, 9, 10, 12, 6, 10, + 6, 1, 12, 7, 3, 6, 9, 10, 12, 0, 1, 3, -1}, + {4, 13, 0, 1, 4, 4, 13, 1, 2, 4, 4, 13, 2, 3, 4, 4, + 13, 3, 0, 4, 4, 13, 0, 3, 1, 4, 13, 1, 3, 2, -1}, + {4, 13, 0, 1, 4, 4, 13, 1, 2, 4, 4, 13, 2, 3, 4, 4, + 13, 3, 0, 4, 4, 13, 0, 3, 1, 4, 13, 1, 3, 2, -1}, + {4, 1, 5, 6, 10, 6, 5, 6, 10, 0, 2, 4, 4, 0, 2, 3, 4, -1}, + {6, 1, 5, 6, 4, 9, 11, 4, 4, 9, 11, 12, 6, 9, + 5, 0, 11, 6, 2, 6, 9, 11, 12, 0, 2, 3, -1}, + {4, 1, 10, 5, 6, 6, 10, 5, 6, 4, 0, 2, 6, + 4, 0, 2, 12, 8, 7, 4, 3, 12, 8, 7, -1}, + {4, 0, 9, 5, 8, 6, 9, 5, 8, 4, 1, 3, 6, + 4, 1, 3, 11, 6, 7, 4, 2, 11, 6, 7, -1}, + {4, 13, 0, 1, 4, 4, 13, 1, 2, 4, 4, 13, 2, 3, 4, 4, + 13, 3, 0, 4, 4, 13, 0, 3, 1, 4, 13, 1, 3, 2, -1}, + {4, 13, 0, 1, 4, 4, 13, 1, 2, 4, 4, 13, 2, 3, 4, 4, + 13, 3, 0, 4, 4, 13, 0, 3, 1, 4, 13, 1, 3, 2, -1}, + {6, 0, 5, 8, 4, 10, 12, 4, 4, 10, 11, 12, 6, 10, + 5, 1, 12, 8, 3, 6, 10, 11, 12, 1, 2, 3, -1}, + {4, 0, 5, 8, 9, 6, 5, 8, 9, 1, 3, 4, 4, 1, 2, 3, 4, -1}, + {4, 0, 5, 8, 9, 6, 5, 8, 9, 1, 3, 4, 4, 1, 2, 3, 4, -1}, + {6, 0, 5, 8, 4, 10, 12, 4, 4, 10, 11, 12, 6, 10, + 5, 1, 12, 8, 3, 6, 10, 11, 12, 1, 2, 3, -1}, + {4, 13, 0, 1, 4, 4, 13, 1, 2, 4, 4, 13, 2, 3, 4, 4, + 13, 3, 0, 4, 4, 13, 0, 3, 1, 4, 13, 1, 3, 2, -1}, + {4, 13, 0, 1, 4, 4, 13, 1, 2, 4, 4, 13, 2, 3, 4, 4, + 13, 3, 0, 4, 4, 13, 0, 3, 1, 4, 13, 1, 3, 2, -1}, + {4, 0, 9, 5, 8, 6, 9, 5, 8, 4, 1, 3, 6, + 4, 1, 3, 11, 6, 7, 4, 2, 11, 6, 7, -1}, + {4, 1, 10, 5, 6, 6, 10, 5, 6, 4, 0, 2, 6, + 4, 0, 2, 12, 8, 7, 4, 3, 12, 8, 7, -1}, + {6, 1, 5, 6, 4, 9, 11, 4, 4, 9, 11, 12, 6, 9, + 5, 0, 11, 6, 2, 6, 9, 11, 12, 0, 2, 3, -1}, + {4, 1, 5, 6, 10, 6, 5, 6, 10, 0, 2, 4, 4, 0, 2, 3, 4, -1}, + {4, 13, 0, 1, 4, 4, 13, 1, 2, 4, 4, 13, 2, 3, 4, 4, + 13, 3, 0, 4, 4, 13, 0, 3, 1, 4, 13, 1, 3, 2, -1}, + {4, 13, 0, 1, 4, 4, 13, 1, 2, 4, 4, 13, 2, 3, 4, 4, + 13, 3, 0, 4, 4, 13, 0, 3, 1, 4, 13, 1, 3, 2, -1}, + {6, 2, 6, 7, 4, 10, 12, 4, 4, 9, 10, 12, 6, 10, + 6, 1, 12, 7, 3, 6, 9, 10, 12, 0, 1, 3, -1}, + {4, 2, 6, 7, 11, 6, 6, 7, 11, 1, 3, 4, 4, 0, 1, 3, 4, -1}, + {6, 3, 8, 7, 4, 9, 11, 4, 4, 9, 10, 11, 6, 9, + 8, 0, 11, 7, 2, 6, 9, 10, 11, 0, 1, 2, -1}, + {4, 3, 8, 7, 12, 6, 8, 7, 12, 0, 2, 4, 4, 0, 1, 2, 4, -1}, + {5, 9, 10, 11, 12, 4, 8, 9, 10, 11, 12, 0, 1, 2, 3, -1}, + {5, 0, 1, 2, 3, 4, -1}, +}; + +// currently set to decompose as if every clipping case were considered "tough" (2**6 = 64 cases total) +const std::vector> triangularPrismClipTableVec = { + {6, 0, 1, 2, 3, 4, 5, -1}, + {4, 5, 14, 13, 11, 6, 14, 13, 11, 3, 4, 2, 5, 4, 1, 0, 3, 2, -1}, + {4, 4, 12, 13, 10, 6, 12, 13, 10, 3, 5, 1, 5, 5, 2, 0, 3, 1, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 3, 12, 14, 9, 6, 12, 14, 9, 4, 5, 0, 5, 5, 2, 1, 4, 0, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {6, 0, 1, 2, 9, 10, 11, 6, 9, 10, 11, 3, 4, 5, -1}, + {4, 2, 8, 7, 11, 6, 8, 7, 11, 0, 1, 5, 5, 1, 4, 3, 0, 5, -1}, + {6, 2, 8, 7, 5, 14, 13, 8, 0, 1, 4, 3, 8, 7, 13, 14, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 1, 6, 7, 10, 6, 6, 7, 10, 0, 2, 4, 5, 2, 5, 3, 0, 4, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {6, 1, 6, 7, 4, 12, 13, 8, 0, 2, 5, 3, 6, 7, 13, 12, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {6, 0, 6, 8, 3, 12, 14, 8, 1, 2, 5, 4, 6, 8, 14, 12, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 6, 8, 9, 6, 6, 8, 9, 1, 2, 3, 5, 2, 5, 4, 1, 3, -1}, + {4, 0, 6, 8, 9, 6, 6, 8, 9, 1, 2, 3, 5, 2, 5, 4, 1, 3, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {6, 0, 6, 8, 3, 12, 14, 8, 1, 2, 5, 4, 6, 8, 14, 12, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {6, 1, 6, 7, 4, 12, 13, 8, 0, 2, 5, 3, 6, 7, 13, 12, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 1, 6, 7, 10, 6, 6, 7, 10, 0, 2, 4, 5, 2, 5, 3, 0, 4, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {6, 2, 8, 7, 5, 14, 13, 8, 0, 1, 4, 3, 8, 7, 13, 14, -1}, + {4, 2, 8, 7, 11, 6, 8, 7, 11, 0, 1, 5, 5, 1, 4, 3, 0, 5, -1}, + {6, 3, 4, 5, 9, 10, 11, 6, 9, 10, 11, 0, 1, 2, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 3, 12, 14, 9, 6, 12, 14, 9, 4, 5, 0, 5, 5, 2, 1, 4, 0, -1}, + {4, 0, 1, 2, 15, 4, 3, 5, 4, 15, 5, 0, 2, 5, 3, + 15, 5, 1, 4, 5, 2, 15, 5, 0, 3, 4, 1, 15, -1}, + {4, 4, 12, 13, 10, 6, 12, 13, 10, 3, 5, 1, 5, 5, 2, 0, 3, 1, -1}, + {4, 5, 14, 13, 11, 6, 14, 13, 11, 3, 4, 2, 5, 4, 1, 0, 3, 2, -1}, + {6, 0, 1, 2, 3, 4, 5, -1}}; + +// currently set to decompose as if every clipping case were considered "tough" (2**8 = 256 cases total) +const std::vector> hexahedronClipTableVec = { + {8, 0, 1, 2, 3, 4, 5, 6, 7, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {5, 0, 1, 2, 3, 20, 5, 4, 5, 6, 7, 20, 5, 1, 2, 6, 5, 20, 5, + 0, 4, 7, 3, 20, 5, 0, 1, 5, 4, 20, 5, 2, 6, 7, 3, 20, -1}, + {8, 0, 1, 2, 3, 4, 5, 6, 7, -1}}; + +} // namespace mir +} // namespace axom diff --git a/src/axom/mir/reference/ZooClippingTables.hpp b/src/axom/mir/reference/ZooClippingTables.hpp new file mode 100644 index 0000000000..187146f158 --- /dev/null +++ b/src/axom/mir/reference/ZooClippingTables.hpp @@ -0,0 +1,33 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef __ZOO_CLIPPING_TABLES_H__ +#define __ZOO_CLIPPING_TABLES_H__ + +/** + * \file ZooClippingTables.hpp + * + * \brief Contains the definitions for the clipping cases and enumerator + * for the shape types in the zoo. + */ + +#include + +namespace axom +{ +namespace mir +{ +extern const int quadClipTable[16][19]; +extern const int triangleClipTable[8][10]; + +extern const std::vector> triangleClipTableVec; +extern const std::vector> quadClipTableVec; +extern const std::vector> tetrahedronClipTableVec; +extern const std::vector> pyramidClipTableVec; +extern const std::vector> triangularPrismClipTableVec; +extern const std::vector> hexahedronClipTableVec; +} // namespace mir +} // namespace axom +#endif diff --git a/src/axom/mir/tests/CMakeLists.txt b/src/axom/mir/tests/CMakeLists.txt new file mode 100644 index 0000000000..5c6c3f0f6a --- /dev/null +++ b/src/axom/mir/tests/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +# other Axom Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (BSD-3-Clause) +#------------------------------------------------------------------------------ +# Mir unit tests +#------------------------------------------------------------------------------ + + +#------------------------------------------------------------------------------ +# Specify list of tests +#------------------------------------------------------------------------------ + +set(gtest_mir_tests + mir_clipfield.cpp + mir_blueprint_utilities.cpp + mir_views_indexing.cpp + mir_views.cpp + mir_equiz2d.cpp + mir_equiz3d.cpp + mir_slicers.cpp + mir_mergemeshes.cpp + mir_node_to_zone_relation.cpp + ) + +set(mir_tests_depends_on + slic + mir + gtest) + +#------------------------------------------------------------------------------ +# Add gtest based tests +#------------------------------------------------------------------------------ +foreach(test ${gtest_mir_tests}) + get_filename_component( test_name ${test} NAME_WE ) + axom_add_executable( NAME ${test_name}_test + SOURCES ${test} + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + DEPENDS_ON ${mir_tests_depends_on} + FOLDER axom/mir/tests ) + + blt_add_test( NAME ${test_name} + COMMAND ${test_name}_test ) +endforeach() + diff --git a/src/axom/mir/tests/mir_blueprint_utilities.cpp b/src/axom/mir/tests/mir_blueprint_utilities.cpp new file mode 100644 index 0000000000..f43125231b --- /dev/null +++ b/src/axom/mir/tests/mir_blueprint_utilities.cpp @@ -0,0 +1,733 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/mir.hpp" + +#include "axom/mir/tests/mir_testing_helpers.hpp" +#include "axom/mir/tests/mir_testing_data_helpers.hpp" + +#include +#include + +namespace mir = axom::mir; +namespace bputils = axom::mir::utilities::blueprint; + +//------------------------------------------------------------------------------ +template +struct test_conduit_allocate +{ + static void test() + { + axom::mir::utilities::blueprint::ConduitAllocateThroughAxom c2a; + EXPECT_TRUE(c2a.getConduitAllocatorID() > 0); + + constexpr int nValues = 100; + conduit::Node n; + n.set_allocator(c2a.getConduitAllocatorID()); + n.set(conduit::DataType::int32(nValues)); + + // Make sure we can store some values into the data that were allocated. + auto nview = bputils::make_array_view(n); + axom::for_all( + nValues, + AXOM_LAMBDA(axom::IndexType index) { nview[index] = index; }); + + EXPECT_EQ(n.dtype().number_of_elements(), nValues); + + // Get the values back to the host. + std::vector hostValues(nValues); + axom::copy(hostValues.data(), n.data_ptr(), sizeof(int) * nValues); + + // Check that the values were set. + for(int i = 0; i < nValues; i++) + { + EXPECT_EQ(hostValues[i], i); + } + } +}; + +TEST(mir_blueprint_utilities, allocate_seq) +{ + test_conduit_allocate::test(); +} +#if defined(AXOM_USE_OPENMP) +TEST(mir_blueprint_utilities, allocate_omp) +{ + test_conduit_allocate::test(); +} +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_blueprint_utilities, allocate_cuda) +{ + test_conduit_allocate::test(); +} +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_blueprint_utilities, allocate_hip) +{ + test_conduit_allocate::test(); +} +#endif + +//------------------------------------------------------------------------------ +template +struct test_copy_braid +{ + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host->device + conduit::Node deviceMesh; + axom::mir::utilities::blueprint::copy(deviceMesh, hostMesh); + + // Run some minmax operations on device (proves that the data was in the right place) and check the results. + + constexpr double eps = 1.e-7; + + auto x = axom::mir::utilities::blueprint::minmax::execute( + deviceMesh["coordsets/coords/values/x"]); + //std::cout << std::setw(16) << "x={" << x.first << ", " << x.second << "}\n"; + EXPECT_NEAR(x.first, -10., eps); + EXPECT_NEAR(x.second, 10., eps); + + auto y = axom::mir::utilities::blueprint::minmax::execute( + deviceMesh["coordsets/coords/values/y"]); + //std::cout << std::setw(16) << "y={" << y.first << ", " << y.second << "}\n"; + EXPECT_NEAR(y.first, -10., eps); + EXPECT_NEAR(y.second, 10., eps); + + auto c = axom::mir::utilities::blueprint::minmax::execute( + deviceMesh["topologies/mesh/elements/connectivity"]); + //std::cout << std::setw(16) << "conn={" << c.first << ", " << c.second << "}\n"; + EXPECT_NEAR(c.first, 0., eps); + EXPECT_NEAR(c.second, 999., eps); + + auto r = axom::mir::utilities::blueprint::minmax::execute( + deviceMesh["fields/radial/values"]); + //std::cout << std::setw(16) << "radial={" << r.first << ", " << r.second << "}\n"; + EXPECT_NEAR(r.first, 19.2450089729875, eps); + EXPECT_NEAR(r.second, 173.205080756888, eps); + } + + static void create(conduit::Node &mesh) + { + const int d[3] = {10, 10, 10}; + conduit::blueprint::mesh::examples::braid("hexs", d[0], d[1], d[2], mesh); + } +}; + +TEST(mir_blueprint_utilities, copy_seq) { test_copy_braid::test(); } + +#if defined(AXOM_USE_OPENMP) +TEST(mir_blueprint_utilities, copy_omp) { test_copy_braid::test(); } +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_blueprint_utilities, copy_cuda) { test_copy_braid::test(); } +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_blueprint_utilities, copy_hip) { test_copy_braid::test(); } +#endif + +//------------------------------------------------------------------------------ +template +struct test_make_unstructured +{ + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + // _mir_utilities_makeunstructured_begin + conduit::Node deviceResult; + bputils::MakeUnstructured uns; + uns.execute(deviceMesh["topologies/mesh"], + deviceMesh["coordsets/coords"], + "mesh", + deviceResult); + // _mir_utilities_makeunstructured_end + + // device->host + conduit::Node hostResult; + bputils::copy(hostResult, deviceResult); + + // Result + conduit::Node expectedResult; + result(expectedResult); + + // Compare just the topologies + constexpr double tolerance = 1.e-7; + conduit::Node info; + bool success = compareConduit(expectedResult["topologies/mesh"], + hostResult["topologies/mesh"], + tolerance, + info); + if(!success) + { + info.print(); + } + EXPECT_TRUE(success); + } + + static void create(conduit::Node &mesh) + { + std::vector dims {4, 4}; + axom::mir::testing::data::braid("uniform", dims, mesh); + } + + static void result(conduit::Node &mesh) + { + std::vector dims {4, 4}; + axom::mir::testing::data::braid("quads", dims, mesh); + } +}; + +TEST(mir_blueprint_utilities, make_unstructured_seq) +{ + test_make_unstructured::test(); +} +#if defined(AXOM_USE_OPENMP) +TEST(mir_blueprint_utilities, make_unstructured_omp) +{ + test_make_unstructured::test(); +} +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_blueprint_utilities, make_unstructured_cuda) +{ + test_make_unstructured::test(); +} +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_blueprint_utilities, make_unstructured_hip) +{ + test_make_unstructured::test(); +} +#endif + +//------------------------------------------------------------------------------ +template +struct test_recenter_field +{ + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host -> device + conduit::Node deviceMesh; + axom::mir::utilities::blueprint::copy(deviceMesh, hostMesh); + // _mir_utilities_recenterfield_begin + const conduit::Node &deviceTopo = deviceMesh["topologies/mesh"]; + const conduit::Node &deviceCoordset = deviceMesh["coordsets/coords"]; + + // Make a node to zone relation on the device. + conduit::Node deviceRelation; + axom::mir::utilities::blueprint::NodeToZoneRelationBuilder n2z; + n2z.execute(deviceTopo, deviceCoordset, deviceRelation); + + // Recenter a field zonal->nodal on the device + axom::mir::utilities::blueprint::RecenterField r; + r.execute(deviceMesh["fields/easy_zonal"], + deviceRelation, + deviceMesh["fields/z2n"]); + + // Recenter a field nodal->zonal on the device. (The elements are an o2m relation) + r.execute(deviceMesh["fields/z2n"], + deviceMesh["topologies/mesh/elements"], + deviceMesh["fields/n2z"]); + // _mir_utilities_recenterfield_end + + // device -> host + conduit::Node hostResultMesh; + axom::mir::utilities::blueprint::copy(hostResultMesh, deviceMesh); + + // Print the results. + //printNode(hostResultMesh); + + const float n2z_result[] = {1., 2., 4., 5., 4., 5., 7., 8., 7., 8., 10., 11.}; + for(size_t i = 0; i < (sizeof(n2z_result) / sizeof(float)); i++) + { + EXPECT_EQ(n2z_result[i], + hostResultMesh["fields/z2n/values"].as_float_accessor()[i]); + } + const float z2n_result[] = {3., 4.5, 6., 6., 7.5, 9.}; + for(size_t i = 0; i < (sizeof(z2n_result) / sizeof(float)); i++) + { + EXPECT_EQ(z2n_result[i], + hostResultMesh["fields/n2z/values"].as_float_accessor()[i]); + } + } + + static void create(conduit::Node &mesh) + { + /* + 8---9--10--11 + | | | | + 4---5---6---7 + | | | | + 0---1---2---3 + */ + axom::StackArray dims {{4, 3}}; + axom::mir::testing::data::braid("quads", dims, mesh); + mesh["topologies/mesh/elements/sizes"].set( + std::vector {{4, 4, 4, 4, 4, 4}}); + mesh["topologies/mesh/elements/offsets"].set( + std::vector {{0, 4, 8, 12, 16, 20}}); + mesh["fields/easy_zonal/topology"] = "mesh"; + mesh["fields/easy_zonal/association"] = "element"; + mesh["fields/easy_zonal/values"].set(std::vector {{1, 3, 5, 7, 9, 11}}); + } +}; + +TEST(mir_blueprint_utilities, recenterfield_seq) +{ + test_recenter_field::test(); +} + +#if defined(AXOM_USE_OPENMP) +TEST(mir_blueprint_utilities, recenterfield_omp) +{ + test_recenter_field::test(); +} +#endif + +#if defined(AXOM_USE_CUDA) +TEST(mir_blueprint_utilities, recenterfield_cuda) +{ + test_recenter_field::test(); +} +#endif + +#if defined(AXOM_USE_HIP) +TEST(mir_blueprint_utilities, recenterfield_hip) +{ + test_recenter_field::test(); +} +#endif + +//------------------------------------------------------------------------------ +template +struct test_extractzones +{ + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + axom::Array ids {{1, 3, 5}}; + const auto nzones = ids.size(); + axom::Array selectedZones( + nzones, + nzones, + axom::execution_space::allocatorID()); + axom::copy(selectedZones.data(), ids.data(), nzones * sizeof(axom::IndexType)); + + // Wrap the data in views. + auto coordsetView = + axom::mir::views::make_explicit_coordset::view( + deviceMesh["coordsets/coords"]); + using CoordsetView = decltype(coordsetView); + + using TopologyView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::QuadShape>; + TopologyView topoView( + bputils::make_array_view( + deviceMesh["topologies/mesh/elements/connectivity"]), + bputils::make_array_view( + deviceMesh["topologies/mesh/elements/sizes"]), + bputils::make_array_view( + deviceMesh["topologies/mesh/elements/offsets"])); + + // Pull out selected zones + bputils::ExtractZones extract( + topoView, + coordsetView); + conduit::Node options, newDeviceMesh; + options["topology"] = "mesh"; + extract.execute(selectedZones.view(), deviceMesh, options, newDeviceMesh); + + // device->host + conduit::Node newHostMesh; + bputils::copy(newHostMesh, newDeviceMesh); + + //printNode(newHostMesh); + + // Check some of the key arrays + const axom::Array connectivity { + {0, 1, 4, 3, 2, 3, 7, 6, 4, 5, 9, 8}}; + const axom::Array sizes {{4, 4, 4}}; + const axom::Array offsets {{0, 4, 8}}; + const axom::Array x { + {1.0, 2.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0}}; + const axom::Array y { + {0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0}}; + const axom::Array zonal {{1.0, 3.0, 5.0}}; + const axom::Array nodal { + {1.0, 2.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0}}; + + EXPECT_TRUE( + compare_views(connectivity.view(), + bputils::make_array_view( + newHostMesh["topologies/mesh/elements/connectivity"]))); + EXPECT_TRUE( + compare_views(sizes.view(), + bputils::make_array_view( + newHostMesh["topologies/mesh/elements/sizes"]))); + EXPECT_TRUE( + compare_views(offsets.view(), + bputils::make_array_view( + newHostMesh["topologies/mesh/elements/offsets"]))); + EXPECT_TRUE(compare_views(x.view(), + bputils::make_array_view( + newHostMesh["coordsets/coords/values/x"]))); + EXPECT_TRUE(compare_views(y.view(), + bputils::make_array_view( + newHostMesh["coordsets/coords/values/y"]))); + EXPECT_TRUE(compare_views(zonal.view(), + bputils::make_array_view( + newHostMesh["fields/zonal/values"]))); + EXPECT_TRUE(compare_views(nodal.view(), + bputils::make_array_view( + newHostMesh["fields/nodal/values"]))); + + // Do the material too. + using MatsetView = + axom::mir::views::UnibufferMaterialView; + MatsetView matsetView; + matsetView.set(bputils::make_array_view( + deviceMesh["matsets/mat1/material_ids"]), + bputils::make_array_view( + deviceMesh["matsets/mat1/volume_fractions"]), + bputils::make_array_view( + deviceMesh["matsets/mat1/sizes"]), + bputils::make_array_view( + deviceMesh["matsets/mat1/offsets"]), + bputils::make_array_view( + deviceMesh["matsets/mat1/indices"])); + + // Pull out selected zones + bputils::ExtractZonesAndMatset + extractM(topoView, coordsetView, matsetView); + newDeviceMesh.reset(); + extractM.execute(selectedZones.view(), deviceMesh, options, newDeviceMesh); + + // device->host + newHostMesh.reset(); + bputils::copy(newHostMesh, newDeviceMesh); + + // Check some of the key arrays in the sliced material + const axom::Array mat_sizes {{2, 1, 2}}; + const axom::Array mat_offsets {{0, 2, 3}}; + const axom::Array mat_indices {{0, 1, 2, 3, 4}}; + const axom::Array mat_material_ids {{1, 2, 2, 2, 3}}; + const axom::Array mat_volume_fractions { + {0.5, 0.5, 1.0, 0.8, 0.2}}; + + EXPECT_TRUE(compare_views(mat_sizes.view(), + bputils::make_array_view( + newHostMesh["matsets/mat1/sizes"]))); + EXPECT_TRUE(compare_views(mat_offsets.view(), + bputils::make_array_view( + newHostMesh["matsets/mat1/offsets"]))); + EXPECT_TRUE(compare_views(mat_indices.view(), + bputils::make_array_view( + newHostMesh["matsets/mat1/indices"]))); + EXPECT_TRUE(compare_views(mat_material_ids.view(), + bputils::make_array_view( + newHostMesh["matsets/mat1/material_ids"]))); + EXPECT_TRUE(compare_views(mat_volume_fractions.view(), + bputils::make_array_view( + newHostMesh["matsets/mat1/volume_fractions"]))); + } + + static void create(conduit::Node &hostMesh) + { + /* + 8-------9------10------11 + | 2/1 | 1/0.1 | 2/0.8 | + | | 2/0.5 | 3/0.2 | + | | 3/0.4 | | + 4-------5-------6-------7 + | | 1/0.5 | 1/0.2 | + | 1/1 | 2/0.5 | 2/0.8 | + | | | | + 0-------1-------2-------3 + */ + const char *yaml = R"xx( +coordsets: + coords: + type: explicit + values: + x: [0., 1., 2., 3., 0., 1., 2., 3., 0., 1., 2., 3.] + y: [0., 0., 0., 0., 1., 1., 1., 1., 2., 2., 2., 2.] +topologies: + mesh: + coordset: coords + type: unstructured + elements: + shape: quad + connectivity: [0,1,5,4, 1,2,6,5, 2,3,7,6, 4,5,9,8, 5,6,10,9, 6,7,11,10] + sizes: [4,4,4,4,4,4] + offsets: [0,4,8,12,16,20] +fields: + zonal: + topology: mesh + association: element + values: [0.,1.,2.,3.,4.,5.] + nodal: + topology: mesh + association: vertex + values: [0.,1.,2.,3.,4.,5.,6.,7.,8.,9.,10.,11.] +matsets: + mat1: + topology: mesh + material_map: + a: 1 + b: 2 + c: 3 + material_ids: [1, 1,2, 1,2, 2, 1,2,3, 2,3] + volume_fractions: [1., 0.5,0.5, 0.2,0.8, 1., 0.1,0.5,0.4, 0.8,0.2] + indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + sizes: [1, 2, 2, 1, 3, 2] + offsets: [0, 1, 3, 5, 6, 9] +)xx"; + + hostMesh.parse(yaml); + } +}; + +TEST(mir_blueprint_utilities, extractzones_seq) +{ + test_extractzones::test(); +} + +#if defined(AXOM_USE_OPENMP) +TEST(mir_blueprint_utilities, extractzones_omp) +{ + test_extractzones::test(); +} +#endif + +#if defined(AXOM_USE_CUDA) +TEST(mir_blueprint_utilities, extractzones_cuda) +{ + test_extractzones::test(); +} +#endif + +#if defined(AXOM_USE_HIP) +TEST(mir_blueprint_utilities, extractzones_hip) +{ + test_extractzones::test(); +} +#endif + +//------------------------------------------------------------------------------ +template +struct test_zonelistbuilder +{ + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + // Wrap the data in views. + auto coordsetView = + axom::mir::views::make_rectilinear_coordset::view( + deviceMesh["coordsets/coords"]); + + auto topologyView = axom::mir::views::make_rectilinear<2>::view( + deviceMesh["topologies/mesh"]); + using TopologyView = decltype(topologyView); + + // Do the material too. + using MatsetView = + axom::mir::views::UnibufferMaterialView; + MatsetView matsetView; + matsetView.set(bputils::make_array_view( + deviceMesh["matsets/mat1/material_ids"]), + bputils::make_array_view( + deviceMesh["matsets/mat1/volume_fractions"]), + bputils::make_array_view( + deviceMesh["matsets/mat1/sizes"]), + bputils::make_array_view( + deviceMesh["matsets/mat1/offsets"]), + bputils::make_array_view( + deviceMesh["matsets/mat1/indices"])); + + // Determine the list of clean and mixed zones (taking into account #mats at the nodes) + bputils::ZoneListBuilder zlb( + topologyView, + matsetView); + axom::Array clean, mixed; + zlb.execute(coordsetView.numberOfNodes(), clean, mixed); + + conduit::Node deviceData; + deviceData["clean"].set_external(clean.data(), clean.size()); + deviceData["mixed"].set_external(mixed.data(), mixed.size()); + + // device->host + conduit::Node hostData; + bputils::copy(hostData, deviceData); + + // Compare expected + const axom::Array cleanResult {{0, 1, 2, 3, 4, 8, 12}}; + const axom::Array mixedResult { + {5, 6, 7, 9, 10, 11, 13, 14, 15}}; + EXPECT_TRUE(compare_views( + cleanResult.view(), + bputils::make_array_view(hostData["clean"]))); + EXPECT_TRUE(compare_views( + mixedResult.view(), + bputils::make_array_view(hostData["mixed"]))); + + // Try selecting a subset of the zones. + axom::Array ids {{2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14}}; + axom::Array selectedZones( + ids.size(), + ids.size(), + axom::execution_space::allocatorID()); + axom::copy(selectedZones.data(), + ids.data(), + ids.size() * sizeof(axom::IndexType)); + zlb.execute(coordsetView.numberOfNodes(), selectedZones.view(), clean, mixed); + + deviceData["clean"].set_external(clean.data(), clean.size()); + deviceData["mixed"].set_external(mixed.data(), mixed.size()); + + // device->host + bputils::copy(hostData, deviceData); + + // Compare expected + const axom::Array cleanResult2 {{2, 3, 8, 12}}; + const axom::Array mixedResult2 {{6, 7, 9, 10, 11, 13, 14}}; + EXPECT_TRUE(compare_views( + cleanResult2.view(), + bputils::make_array_view(hostData["clean"]))); + EXPECT_TRUE(compare_views( + mixedResult2.view(), + bputils::make_array_view(hostData["mixed"]))); + } + + static void create(conduit::Node &hostMesh) + { + /* + 20------21-------22-------23-------24 + | 1/1 | 1/1 | 1/.5 | 2/1. | 1/1 = mat#1, vf=1.0 + | | | 2/.5 | | + |z12 |z13 |z14 |z15 | + 15------16-------17-------18-------19 + | 1/1 | 1/1 | 1/0.7 | 1/.5 | + | | | 2/0.3 | 2/.5 | + |z8 |z9 |z10 |z11 | + 10------11-------12-------13-------14 + | 1/1 | 1/1 | 1/1 | 1/1 | + | | | | | + |z4 |z5 |z6 |z7 | + 5-------6--------7--------8--------9 + | 1/1 | 1/1 | 1/1 | 1/1 | + | | | | | + |z0 |z1 |z2 |z3 | + 0-------1--------2--------3--------4 + */ + const char *yaml = R"xx( +coordsets: + coords: + type: rectilinear + values: + x: [0., 1., 2., 3., 4.] + y: [0., 1., 2., 3., 4.] +topologies: + mesh: + type: rectilinear + coordset: coords +matsets: + mat1: + topology: mesh + material_map: + a: 1 + b: 2 + material_ids: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 2, 2] + volume_fractions: [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 0.7, 0.3, .5, 0.5, 1., 1., 0.5, 0.5, 1.] + indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] + sizes: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 1] + offsets: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 18] +)xx"; + + hostMesh.parse(yaml); + } +}; + +TEST(mir_blueprint_utilities, zonelistbuilder_seq) +{ + AXOM_ANNOTATE_SCOPE("zonelistbuilder_seq"); + test_zonelistbuilder::test(); +} +#if defined(AXOM_USE_OPENMP) +TEST(mir_blueprint_utilities, zonelistbuilder_omp) +{ + AXOM_ANNOTATE_SCOPE("zonelistbuilder_omp"); + test_zonelistbuilder::test(); +} +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_blueprint_utilities, zonelistbuilder_cuda) +{ + AXOM_ANNOTATE_SCOPE("zonelistbuilder_cuda"); + test_zonelistbuilder::test(); +} +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_blueprint_utilities, zonelistbuilder_hip) +{ + AXOM_ANNOTATE_SCOPE("zonelistbuilder_hip"); + test_zonelistbuilder::test(); +} +#endif + +//------------------------------------------------------------------------------ +int main(int argc, char *argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; // create & initialize test logger, +#if defined(AXOM_USE_CALIPER) + axom::CLI::App app; + std::string annotationMode("none"); + app.add_option("--caliper", annotationMode) + ->description( + "caliper annotation mode. Valid options include 'none' and 'report'. " + "Use 'help' to see full list.") + ->capture_default_str() + ->check(axom::utilities::ValidCaliperMode); + + // Parse command line options. + app.parse(argc, argv); + + axom::utilities::raii::AnnotationsWrapper annotations_raii_wrapper( + annotationMode); +#endif + result = RUN_ALL_TESTS(); + return result; +} diff --git a/src/axom/mir/tests/mir_clipfield.cpp b/src/axom/mir/tests/mir_clipfield.cpp new file mode 100644 index 0000000000..d6abefd41e --- /dev/null +++ b/src/axom/mir/tests/mir_clipfield.cpp @@ -0,0 +1,1339 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/mir.hpp" +#include "axom/mir/tests/mir_testing_data_helpers.hpp" + +#include +#include +#include + +namespace bputils = axom::mir::utilities::blueprint; + +//------------------------------------------------------------------------------ + +// Uncomment to make Conduit errors hang. +//#define DEBUGGING_TEST_CASES + +// Uncomment to generate baselines +//#define AXOM_TESTING_GENERATE_BASELINES + +// Uncomment to save visualization files for debugging (when making baselines) +//#define AXOM_TESTING_SAVE_VISUALIZATION + +#include "axom/mir/tests/mir_testing_helpers.hpp" + +std::string baselineDirectory() +{ + return pjoin(pjoin(pjoin(dataDirectory(), "mir"), "regression"), + "mir_clipfield"); +} +//------------------------------------------------------------------------------ +TEST(mir_clipfield, options) +{ + int nzones = 6; + + conduit::Node options; + axom::mir::clipping::ClipOptions opts(options); + + options["clipField"] = "distance"; + EXPECT_EQ(opts.clipField(), options["clipField"].as_string()); + + EXPECT_EQ(opts.clipValue(), 0.); + options["clipValue"] = 2.5f; + EXPECT_EQ(opts.clipValue(), 2.5f); + + EXPECT_EQ(opts.topologyName("default"), "default"); + options["topologyName"] = "topo"; + EXPECT_EQ(opts.topologyName("default"), "topo"); + + EXPECT_EQ(opts.coordsetName("default"), "default"); + options["coordsetName"] = "coords"; + EXPECT_EQ(opts.coordsetName("default"), "coords"); + + EXPECT_EQ(opts.colorField(), "color"); + options["colorField"] = "custom_color"; + EXPECT_EQ(opts.colorField(), "custom_color"); + + EXPECT_TRUE(opts.inside()); + options["inside"] = 1; + EXPECT_TRUE(opts.inside()); + options["inside"] = 0; + EXPECT_FALSE(opts.inside()); + + EXPECT_FALSE(opts.outside()); + options["outside"] = 1; + EXPECT_TRUE(opts.outside()); + options["outside"] = 0; + EXPECT_FALSE(opts.outside()); + + // The clip field has to be present + conduit::Node n_fields; + n_fields["distance/topology"] = "topo"; + n_fields["distance/association"] = "vertex"; + n_fields["distance/values"].set(std::vector {0., 1., 2., 3.}); + + // There are currently no fields in the options. fields should just return the clip field. + std::map fields; + auto have_fields = opts.fields(fields); + EXPECT_FALSE(have_fields); + EXPECT_EQ(fields.size(), 0); + + // Add an empty fields node so we select NO fields. + (void)options["fields"]; + have_fields = opts.fields(fields); + EXPECT_TRUE(have_fields); + EXPECT_EQ(fields.size(), 0); + + // Add some fields + options["fields/distance"] = "distance"; + options["fields/source"] = "destination"; + options["fields/same"] = 1; + have_fields = opts.fields(fields); + EXPECT_TRUE(have_fields); + EXPECT_EQ(fields.size(), 3); + int i = 0; + for(auto it = fields.begin(); it != fields.end(); it++, i++) + { + if(i == 0) + { + EXPECT_EQ(it->first, "distance"); + EXPECT_EQ(it->second, "distance"); + } + else if(i == 1) + { + EXPECT_EQ(it->first, "same"); + EXPECT_EQ(it->second, "same"); + } + else if(i == 2) + { + EXPECT_EQ(it->first, "source"); + EXPECT_EQ(it->second, "destination"); + } + } + + // There are no "selectedZones" in the options. We should get nzones values from 0 onward. + axom::mir::SelectedZones selectedZones(nzones, options); + auto selectedZonesView = selectedZones.view(); + EXPECT_EQ(selectedZonesView.size(), 6); + EXPECT_EQ(selectedZonesView[0], 0); + EXPECT_EQ(selectedZonesView[1], 1); + EXPECT_EQ(selectedZonesView[2], 2); + EXPECT_EQ(selectedZonesView[3], 3); + EXPECT_EQ(selectedZonesView[4], 4); + EXPECT_EQ(selectedZonesView[5], 5); + + // Put some "selectedZones" in the options. + options["selectedZones"].set(std::vector {5, 4, 3}); + axom::mir::SelectedZones selectedZones2(nzones, options); + selectedZonesView = selectedZones2.view(); + EXPECT_EQ(selectedZonesView.size(), 3); + EXPECT_EQ(selectedZonesView[0], 3); + EXPECT_EQ(selectedZonesView[1], 4); + EXPECT_EQ(selectedZonesView[2], 5); +} + +TEST(mir_clipfield, blend_group_builder) +{ + using IndexType = axom::IndexType; + using KeyType = std::uint64_t; + + /* + + We'll make 2 quads + + 3 4 5 + *--8---*------* + | | | + | 6 9 | + | | | + *--7---*------* + 0 1 2 + + */ + axom::Array blendGroups {{8, 5}}; + axom::Array blendGroupsLen { + {/*zone 0*/ 4 + 1 + 1 + 1 + 1 + 2 + 2 + 2, /*zone 1*/ 1 + 1 + 1 + 1 + 2}}; + axom::Array blendGroupOffsets {{0, 8}}; + axom::Array blendOffsets {{0, blendGroupsLen[0]}}; + + axom::Array blendNames { + {/*zone 0*/ 6, 0, 1, 3, 4, 7, 8, 9, /*zone 1*/ 1, 2, 4, 5, 9}}; + axom::Array blendGroupSizes { + {/*zone 0*/ 4, 1, 1, 1, 1, 2, 2, 2, /*zone 1*/ 1, 1, 1, 1, 2}}; + axom::Array blendGroupStart { + {/*zone 0*/ 0, 4, 5, 6, 7, 8, 10, 12, /*zone 1*/ 13, 14, 15, 16, 18}}; + axom::Array blendIds {{ + /*zone 0*/ + 0, + 1, + 2, + 3, // 6 (bgname) // 0 (bgindex) + 0, // 0 // 1 + 1, // 1 // 2 + 3, // 3 // 3 + 4, // 4 // 4 + 0, + 1, // 7 // 5 + 3, + 4, // 8 // 6 + 1, + 4, // 9 // 7 + /*zone 1*/ + 1, // 1 // 8 + 2, // 2 // 9 + 4, // 4 // 10 + 5, // 5 // 11 + 1, + 4 // 9 // 12 + }}; + axom::Array blendCoeff {{/*zone 0*/ + 0.25, + 0.25, + 0.25, + 0.25, + 1., + 1., + 1., + 1., + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + /*zone 1*/ + 1., + 1., + 1., + 1., + 0.5, + 0.5}}; + axom::Array blendUniqueNames {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}; + axom::Array blendUniqueIndices {{1, 2, 9, 3, 4, 11, 0, 5, 6, 7}}; + + using NamingPolicyView = + typename axom::mir::utilities::HashNaming::View; + + axom::mir::clipping::BlendGroupBuilder builder; + builder.setBlendGroupSizes(blendGroups.view(), blendGroupsLen.view()); + builder.setBlendGroupOffsets(blendOffsets.view(), blendGroupOffsets.view()); + builder.setBlendViews(blendNames.view(), + blendGroupSizes.view(), + blendGroupStart.view(), + blendIds.view(), + blendCoeff.view()); + + //std::cout << "-------- zone 0 --------" << std::endl; + auto z0 = builder.blendGroupsForZone(0); + EXPECT_EQ(z0.numGroups(), 8); + IndexType index = 0; + for(IndexType i = 0; i < z0.numGroups(); i++, index++) + { + //z0.print(std::cout); + EXPECT_EQ(z0.ids().size(), blendGroupSizes[index]); + + z0++; + } + + //std::cout << "-------- zone 1 --------" << std::endl; + auto z1 = builder.blendGroupsForZone(1); + EXPECT_EQ(z1.numGroups(), 5); + for(IndexType i = 0; i < z1.numGroups(); i++, index++) + { + //z1.print(std::cout); + EXPECT_EQ(z1.ids().size(), blendGroupSizes[index]); + z1++; + } +} + +//------------------------------------------------------------------------------ +template +bool increasing(const ArrayType &arr) +{ + bool retval = true; + for(size_t i = 1; i < arr.size(); i++) retval &= (arr[i] >= arr[i - 1]); + return retval; +} + +std::vector permute(const std::vector &input) +{ + std::vector values, indices; + std::vector order; + + values.resize(input.size()); + indices.resize(input.size()); + order.resize(input.size()); + + std::iota(indices.begin(), indices.end(), 0); + for(size_t i = 0; i < input.size(); i++) + { + order[i] = drand48(); + } + std::sort(indices.begin(), indices.end(), [&](int a, int b) { + return order[a] < order[b]; + }); + for(size_t i = 0; i < input.size(); i++) values[i] = input[indices[i]]; + return values; +} + +std::vector makeUnsortedArray(int n) +{ + std::vector values; + values.resize(n); + std::iota(values.begin(), values.end(), 0); + return permute(values); +} + +std::vector makeRandomArray(int n) +{ + constexpr int largestId = 1 << 28; + std::vector values; + values.resize(n); + for(int i = 0; i < n; i++) + { + values[i] = static_cast(largestId * drand48()); + } + return permute(values); +} + +//------------------------------------------------------------------------------ +TEST(mir_clipfield, sort_values) +{ + for(int n = 1; n < 15; n++) + { + for(int trial = 1; trial <= n; trial++) + { + auto values = makeUnsortedArray(n); + axom::utilities::Sorting::sort(values.data(), values.size()); + EXPECT_TRUE(increasing(values)); + } + } +} +//------------------------------------------------------------------------------ +template +struct test_unique +{ + static void test() + { + /* + 8---9---10--11 + | | | | + 4---5---6---7 + | | | | + 0---1---2---3 + */ + // _mir_utilities_unique_begin + const int allocatorID = axom::execution_space::allocatorID(); + axom::Array ids {{0, 1, 5, 4, 1, 2, 6, 5, 2, 3, 7, 6, + 4, 5, 9, 8, 5, 6, 10, 9, 6, 7, 11, 10}}; + // host->device + axom::Array deviceIds(ids.size(), ids.size(), allocatorID); + axom::copy(deviceIds.data(), ids.data(), sizeof(int) * ids.size()); + + // Make unique ids. + axom::Array uIds; + axom::Array uIndices; + axom::mir::utilities::Unique::execute(ids.view(), + uIds, + uIndices); + // _mir_utilities_unique_end + + // device->host + axom::Array hostuIds(uIds.size()); + axom::Array hostuIndices(uIndices.size()); + axom::copy(hostuIds.data(), uIds.data(), sizeof(int) * uIds.size()); + axom::copy(hostuIndices.data(), + uIndices.data(), + sizeof(axom::IndexType) * uIndices.size()); + + // compare results + EXPECT_EQ(hostuIds.size(), 12); + EXPECT_EQ(hostuIndices.size(), 12); + for(axom::IndexType i = 0; i < hostuIds.size(); i++) + { + EXPECT_EQ(hostuIds[i], i); + EXPECT_EQ(hostuIds[i], ids[hostuIndices[i]]); + } + } +}; + +TEST(mir_clipfield, unique_seq) { test_unique::test(); } +#if defined(AXOM_USE_OPENMP) +TEST(mir_clipfield, unique_omp) { test_unique::test(); } +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_clipfield, unique_cuda) { test_unique::test(); } +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_clipfield, unique_hip) { test_unique::test(); } +#endif + +//------------------------------------------------------------------------------ +TEST(mir_clipfield, make_name) +{ + axom::mir::utilities::HashNaming naming; + + for(int n = 1; n < 14; n++) + { + // Make a set of scrambled ids. + auto values = makeRandomArray(n); + // Compute the name for that list of ids. + auto name = naming.makeName(values.data(), n); + + for(int trial = 0; trial < 1000; trial++) + { + // Scramble the id list. + auto values2 = permute(values); + // Compute the name for that list of ids. + auto name2 = naming.makeName(values2.data(), n); + + // The names for the 2 scrambled lists of numbers should be the same. + EXPECT_EQ(name, name2); + } + } +} + +//------------------------------------------------------------------------------ +template +void test_one_shape(const conduit::Node &hostMesh, const std::string &name) +{ + using TopoView = + axom::mir::views::UnstructuredTopologySingleShapeView; + using CoordsetView = axom::mir::views::ExplicitCoordsetView; + + // Copy mesh to device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + // _mir_utilities_clipfield_start + // Make views for the device mesh. + conduit::Node &n_x = deviceMesh.fetch_existing("coordsets/coords/values/x"); + conduit::Node &n_y = deviceMesh.fetch_existing("coordsets/coords/values/y"); + conduit::Node &n_z = deviceMesh.fetch_existing("coordsets/coords/values/z"); + axom::ArrayView xView(static_cast(n_x.data_ptr()), + n_x.dtype().number_of_elements()); + axom::ArrayView yView(static_cast(n_y.data_ptr()), + n_y.dtype().number_of_elements()); + axom::ArrayView zView(static_cast(n_z.data_ptr()), + n_z.dtype().number_of_elements()); + CoordsetView coordsetView(xView, yView, zView); + + conduit::Node &n_conn = + deviceMesh.fetch_existing("topologies/topo/elements/connectivity"); + axom::ArrayView connView(static_cast(n_conn.data_ptr()), + n_conn.dtype().number_of_elements()); + TopoView topoView(connView); + + // Clip the data + conduit::Node deviceClipMesh, options; + axom::mir::clipping::ClipField clipper( + topoView, + coordsetView); + options["clipField"] = "distance"; + options["clipValue"] = 0.; + options["inside"] = 1; + options["outside"] = 1; + clipper.execute(deviceMesh, options, deviceClipMesh); + // _mir_utilities_clipfield_end + + // Copy device->host + conduit::Node hostClipMesh; + bputils::copy(hostClipMesh, deviceClipMesh); + + // Handle baseline comparison. + std::string baselineName(yamlRoot(name)); + const auto paths = baselinePaths(); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostClipMesh); +#else + EXPECT_TRUE(compareBaseline(paths, baselineName, hostClipMesh)); +#endif +} + +template +void test_one_shape_exec(const conduit::Node &hostMesh, const std::string &name) +{ + test_one_shape(hostMesh, name); + +#if defined(AXOM_USE_OPENMP) + test_one_shape(hostMesh, name); +#endif + +#if defined(AXOM_USE_CUDA) + test_one_shape(hostMesh, name); +#endif + +#if defined(AXOM_USE_HIP) + test_one_shape(hostMesh, name); +#endif +} + +TEST(mir_clipfield, onetet) +{ + conduit::Node hostMesh; + axom::mir::testing::data::make_one_tet(hostMesh); + test_one_shape_exec>(hostMesh, "one_tet"); +} + +TEST(mir_clipfield, onepyr) +{ + conduit::Node hostMesh; + axom::mir::testing::data::make_one_pyr(hostMesh); + test_one_shape_exec>(hostMesh, "one_pyr"); +} + +TEST(mir_clipfield, onewdg) +{ + conduit::Node hostMesh; + axom::mir::testing::data::make_one_wdg(hostMesh); + test_one_shape_exec>(hostMesh, "one_wdg"); +} + +TEST(mir_clipfield, onehex) +{ + conduit::Node hostMesh; + axom::mir::testing::data::make_one_hex(hostMesh); + test_one_shape_exec>(hostMesh, "one_hex"); +} + +//------------------------------------------------------------------------------ +template +void braid2d_clip_test(const std::string &type, const std::string &name) +{ + using Indexing = axom::mir::views::StructuredIndexing; + using TopoView = axom::mir::views::StructuredTopologyView; + using CoordsetView = axom::mir::views::UniformCoordsetView; + + axom::StackArray dims {10, 10}; + axom::StackArray zoneDims {dims[0] - 1, dims[1] - 1}; + + // Create the data + conduit::Node hostMesh, deviceMesh; + axom::mir::testing::data::braid(type, dims, hostMesh); + bputils::copy(deviceMesh, hostMesh); +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMesh, name + "_orig", "hdf5"); +#endif +#if defined(DEBUGGING_TEST_CASES) + std::cout << "---------------------------------- input mesh " + "-------------------------------------" + << std::endl; + printNode(hostMesh); + std::cout << "---------------------------------------------------------------" + "--------------------" + << std::endl; +#endif + + // Create views + axom::StackArray origin {0., 0.}, spacing {1., 1.}; + CoordsetView coordsetView(dims, origin, spacing); + TopoView topoView(Indexing {zoneDims}); + + // Create options to control the clipping. + const std::string clipTopoName("cliptopo"); + conduit::Node options; + options["clipField"] = "distance"; + options["inside"] = 1; + options["outside"] = 1; + options["topologyName"] = clipTopoName; + options["coordsetName"] = "clipcoords"; + options["fields/braid"] = "new_braid"; + options["fields/radial"] = "new_radial"; + + // Clip the data + conduit::Node deviceClipMesh; + axom::mir::clipping::ClipField clipper( + topoView, + coordsetView); + clipper.execute(deviceMesh, options, deviceClipMesh); + + // Copy device->host + conduit::Node hostClipMesh; + bputils::copy(hostClipMesh, deviceClipMesh); + +#if defined(DEBUGGING_TEST_CASES) + std::cout << "---------------------------------- clipped uniform " + "----------------------------------" + << std::endl; + printNode(hostClipMesh); + std::cout << "---------------------------------------------------------------" + "----------------------" + << std::endl; +#endif + + // Handle baseline comparison. + { + std::string baselineName(yamlRoot(name)); + const auto paths = baselinePaths(); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostClipMesh); +#else + EXPECT_TRUE(compareBaseline(paths, baselineName, hostClipMesh)); +#endif + } + + // Now, take the clipped mesh and clip it again using a mixed topology view. + using ExpCoordsetView = axom::mir::views::ExplicitCoordsetView; + const auto xView = bputils::make_array_view( + deviceClipMesh.fetch_existing("coordsets/clipcoords/values/x")); + const auto yView = bputils::make_array_view( + deviceClipMesh.fetch_existing("coordsets/clipcoords/values/y")); + ExpCoordsetView expCoordsetView(xView, yView); + + conduit::Node &n_device_topo = + deviceClipMesh.fetch_existing("topologies/" + clipTopoName); + const auto connView = bputils::make_array_view( + n_device_topo.fetch_existing("elements/connectivity")); + + options["clipField"] = "new_braid"; + options["clipValue"] = 1.; + options["fields"].reset(); + options["fields/new_braid"] = "new_braid2"; + options["fields/color"] = "new_color"; + options["fields/new_radial"] = "new_radial2"; + + conduit::Node deviceClipMixedMesh; + if(n_device_topo.has_path("elements/shape") && + n_device_topo.fetch_existing("elements/shape").as_string() == "mixed") + { + auto shapesView = bputils::make_array_view( + n_device_topo.fetch_existing("elements/shapes")); + const auto sizesView = bputils::make_array_view( + n_device_topo.fetch_existing("elements/sizes")); + const auto offsetsView = bputils::make_array_view( + n_device_topo.fetch_existing("elements/offsets")); + + // Make the shape map. + volatile int allocatorID = axom::execution_space::allocatorID(); + axom::Array values, ids; + auto shapeMap = + axom::mir::views::buildShapeMap(n_device_topo, values, ids, allocatorID); + + using MixedTopoView = + axom::mir::views::UnstructuredTopologyMixedShapeView; + MixedTopoView mixedTopoView(connView, + shapesView, + sizesView, + offsetsView, + shapeMap); + + // Clip the data + axom::mir::clipping::ClipField + mixedClipper(mixedTopoView, expCoordsetView); + mixedClipper.execute(deviceClipMesh, options, deviceClipMixedMesh); + } + else + { + // Depending on optimizations, we might get a mesh with just quads. + using QuadTopoView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::QuadShape>; + QuadTopoView quadTopoView(connView); + + // Clip the data + axom::mir::clipping::ClipField + quadClipper(quadTopoView, expCoordsetView); + quadClipper.execute(deviceClipMesh, options, deviceClipMixedMesh); + } + + // Copy device->host + conduit::Node hostClipMixedMesh; + bputils::copy(hostClipMixedMesh, deviceClipMixedMesh); +#if defined(DEBUGGING_TEST_CASES) + std::cout << "---------------------------------- clipped mixed " + "----------------------------------" + << std::endl; + printNode(hostClipMixedMesh); + std::cout << "---------------------------------------------------------------" + "--------------------" + << std::endl; +#endif + + // Handle baseline comparison. + { + std::string baselineName(yamlRoot(name + "_mixed")); + const auto paths = baselinePaths(); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostClipMixedMesh); +#else + EXPECT_TRUE(compareBaseline(paths, baselineName, hostClipMixedMesh)); +#endif + } +} + +TEST(mir_clipfield, uniform2d) +{ + braid2d_clip_test("uniform", "uniform2d"); + +#if defined(AXOM_USE_OPENMP) + braid2d_clip_test("uniform", "uniform2d"); +#endif + +#if defined(AXOM_USE_CUDA) + braid2d_clip_test("uniform", "uniform2d"); +#endif + +#if defined(AXOM_USE_HIP) + braid2d_clip_test("uniform", "uniform2d"); +#endif +} + +//------------------------------------------------------------------------------ +template +void braid_rectilinear_clip_test(const std::string &name) +{ + using Indexing = axom::mir::views::StructuredIndexing; + using TopoView = axom::mir::views::StructuredTopologyView; + + axom::StackArray dims, zoneDims; + for(int i = 0; i < NDIMS; i++) + { + dims[i] = 10; + zoneDims[i] = dims[i] - 1; + } + + // Create the data + conduit::Node hostMesh, deviceMesh; + axom::mir::testing::data::braid("rectilinear", dims, hostMesh); +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMesh, name + "_orig", "hdf5"); +#endif + + // host->device + bputils::copy(deviceMesh, hostMesh); + + // Create views + auto coordsetView = + axom::mir::views::make_rectilinear_coordset::view( + deviceMesh["coordsets/coords"]); + using CoordsetView = decltype(coordsetView); + TopoView topoView(Indexing {zoneDims}); + + // Create options to control the clipping. + conduit::Node options; + options["clipField"] = "distance"; + options["inside"] = 1; + options["outside"] = 1; + + // Clip the data + conduit::Node deviceClipMesh; + axom::mir::clipping::ClipField clipper( + topoView, + coordsetView); + clipper.execute(deviceMesh, options, deviceClipMesh); + + // Copy device->host + conduit::Node hostClipMesh; + bputils::copy(hostClipMesh, deviceClipMesh); + + // Handle baseline comparison. + { + std::string baselineName(yamlRoot(name)); + const auto paths = baselinePaths(); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostClipMesh); +#else + EXPECT_TRUE(compareBaseline(paths, baselineName, hostClipMesh)); +#endif + } +} + +TEST(mir_clipfield, rectilinear2d) +{ + braid_rectilinear_clip_test("rectilinear2d"); + +#if defined(AXOM_USE_OPENMP) + braid_rectilinear_clip_test("rectilinear2d"); +#endif + +#if defined(AXOM_USE_CUDA) + braid_rectilinear_clip_test("rectilinear2d"); +#endif + +#if defined(AXOM_USE_HIP) + braid_rectilinear_clip_test("rectilinear2d"); +#endif +} + +TEST(mir_clipfield, rectilinear3d) +{ + braid_rectilinear_clip_test("rectilinear3d"); + +#if defined(AXOM_USE_OPENMP) + braid_rectilinear_clip_test("rectilinear3d"); +#endif + +#if defined(AXOM_USE_CUDA) + braid_rectilinear_clip_test("rectilinear3d"); +#endif + +#if defined(AXOM_USE_HIP) + braid_rectilinear_clip_test("rectilinear3d"); +#endif +} + +//------------------------------------------------------------------------------ +template +void strided_structured_clip_test(const std::string &name, + const conduit::Node &options) +{ + // Create the data + conduit::Node hostMesh, deviceMesh; + axom::mir::testing::data::strided_structured(hostMesh); + //hostMesh.print(); +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMesh, name + "_orig", "hdf5"); + conduit::relay::io::blueprint::save_mesh(hostMesh, + name + "_orig_yaml", + "yaml"); +#endif + + conduit::Node deviceOptions, deviceClipMesh, hostClipMesh; + + // host->device + bputils::copy(deviceMesh, hostMesh); + bputils::copy(deviceOptions, options); + + // Create views + auto coordsetView = axom::mir::views::make_explicit_coordset::view( + deviceMesh["coordsets/coords"]); + auto topoView = axom::mir::views::make_strided_structured<2>::view( + deviceMesh["topologies/mesh"]); + + using CoordsetView = decltype(coordsetView); + using TopoView = decltype(topoView); + + // Clip the data + axom::mir::clipping::ClipField clipper( + topoView, + coordsetView); + clipper.execute(deviceMesh, deviceOptions, deviceClipMesh); + + // device->host + bputils::copy(hostClipMesh, deviceClipMesh); + + // Handle baseline comparison. + { + std::string baselineName(yamlRoot(name)); + const auto paths = baselinePaths(); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostClipMesh); +#else + EXPECT_TRUE(compareBaseline(paths, baselineName, hostClipMesh)); +#endif + } +} + +void strided_structured_clip_test_exec(const std::string &name, + const conduit::Node &options) +{ + strided_structured_clip_test(name, options); + +#if defined(AXOM_USE_OPENMP) + strided_structured_clip_test(name, options); +#endif + +#if defined(AXOM_USE_CUDA) + strided_structured_clip_test(name, options); +#endif + +#if defined(AXOM_USE_HIP) + strided_structured_clip_test(name, options); +#endif +} + +TEST(mir_clipfield, strided_structured_2d) +{ + // Create options to control the clipping. + conduit::Node options; + options["clipField"] = "vert_vals"; + options["clipValue"] = 6.5; + options["inside"] = 1; + options["outside"] = 1; + strided_structured_clip_test_exec("strided_structured_2d", options); + + // Clip strided structure on some selected zones. + options["selectedZones"].set(std::vector {{0, 2, 3, 5}}); + strided_structured_clip_test_exec("strided_structured_2d_sel", options); +} + +//------------------------------------------------------------------------------ +template +void braid3d_clip_test(const std::string &type, const std::string &name) +{ + using TopoView = + axom::mir::views::UnstructuredTopologySingleShapeView; + using CoordsetView = axom::mir::views::ExplicitCoordsetView; + + // Create the data + const axom::StackArray dims {10, 10, 10}; + conduit::Node hostMesh, deviceMesh; + axom::mir::testing::data::braid(type, dims, hostMesh); + bputils::copy(deviceMesh, hostMesh); +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMesh, name + "_orig", "hdf5"); +#endif + + // Create views + conduit::Node &n_x = deviceMesh.fetch_existing("coordsets/coords/values/x"); + conduit::Node &n_y = deviceMesh.fetch_existing("coordsets/coords/values/y"); + conduit::Node &n_z = deviceMesh.fetch_existing("coordsets/coords/values/z"); + const axom::ArrayView x(static_cast(n_x.data_ptr()), + n_x.dtype().number_of_elements()); + const axom::ArrayView y(static_cast(n_y.data_ptr()), + n_y.dtype().number_of_elements()); + const axom::ArrayView z(static_cast(n_z.data_ptr()), + n_z.dtype().number_of_elements()); + CoordsetView coordsetView(x, y, z); + + conduit::Node &n_conn = + deviceMesh.fetch_existing("topologies/mesh/elements/connectivity"); + const axom::ArrayView conn(static_cast(n_conn.data_ptr()), + n_conn.dtype().number_of_elements()); + TopoView topoView(conn); + + // Create options to control the clipping. + conduit::Node options; + options["clipField"] = "distance"; + options["inside"] = 1; + options["outside"] = 0; + + // Clip the data + conduit::Node deviceClipMesh; + axom::mir::clipping::ClipField clipper( + topoView, + coordsetView); + clipper.execute(deviceMesh, options, deviceClipMesh); + + // Copy device->host + conduit::Node hostClipMesh; + bputils::copy(hostClipMesh, deviceClipMesh); + + // Handle baseline comparison. + std::string baselineName(yamlRoot(name)); + const auto paths = baselinePaths(); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostClipMesh); +#else + EXPECT_TRUE(compareBaseline(paths, baselineName, hostClipMesh)); +#endif +} + +/// Execute the braid3d test for a single shape on multiple ExecSpaces +template +void braid3d_clip_test_exec(const std::string &type, const std::string &name) +{ + braid3d_clip_test(type, name); + +#if defined(AXOM_USE_OPENMP) + braid3d_clip_test(type, name); +#endif + +#if defined(AXOM_USE_CUDA) + braid3d_clip_test(type, name); +#endif + +#if defined(AXOM_USE_HIP) + braid3d_clip_test(type, name); +#endif +} + +TEST(mir_clipfield, tet) +{ + braid3d_clip_test_exec>("tets", "tet"); +} + +TEST(mir_clipfield, pyramid) +{ + braid3d_clip_test_exec>("pyramids", "pyr"); +} + +TEST(mir_clipfield, wedge) +{ + braid3d_clip_test_exec>("wedges", "wdg"); +} + +TEST(mir_clipfield, hex) +{ + braid3d_clip_test_exec>("hexs", "hex"); +} + +//------------------------------------------------------------------------------ +template +void braid3d_mixed_clip_test(const std::string &name) +{ + using CoordType = float; + using ConnType = int; + using TopoView = axom::mir::views::UnstructuredTopologyMixedShapeView; + using CoordsetView = axom::mir::views::ExplicitCoordsetView; + + // Create the data + conduit::Node hostMesh, deviceMesh; + axom::mir::testing::data::mixed3d(hostMesh); + bputils::copy(deviceMesh, hostMesh); +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMesh, name + "_orig", "hdf5"); +#endif + + // Create views + conduit::Node &n_x = deviceMesh.fetch_existing("coordsets/coords/values/x"); + conduit::Node &n_y = deviceMesh.fetch_existing("coordsets/coords/values/y"); + conduit::Node &n_z = deviceMesh.fetch_existing("coordsets/coords/values/z"); + const axom::ArrayView x(static_cast(n_x.data_ptr()), + n_x.dtype().number_of_elements()); + const axom::ArrayView y(static_cast(n_y.data_ptr()), + n_y.dtype().number_of_elements()); + const axom::ArrayView z(static_cast(n_z.data_ptr()), + n_z.dtype().number_of_elements()); + CoordsetView coordsetView(x, y, z); + + conduit::Node &n_device_topo = deviceMesh.fetch_existing("topologies/mesh"); + conduit::Node &n_conn = n_device_topo.fetch_existing("elements/connectivity"); + conduit::Node &n_shapes = n_device_topo.fetch_existing("elements/shapes"); + conduit::Node &n_sizes = n_device_topo.fetch_existing("elements/sizes"); + conduit::Node &n_offsets = n_device_topo.fetch_existing("elements/offsets"); + axom::ArrayView connView(static_cast(n_conn.data_ptr()), + n_conn.dtype().number_of_elements()); + axom::ArrayView shapesView( + static_cast(n_shapes.data_ptr()), + n_shapes.dtype().number_of_elements()); + axom::ArrayView sizesView(static_cast(n_sizes.data_ptr()), + n_sizes.dtype().number_of_elements()); + axom::ArrayView offsetsView( + static_cast(n_offsets.data_ptr()), + n_offsets.dtype().number_of_elements()); + + // Make the shape map. + axom::Array values, ids; + auto shapeMap = axom::mir::views::buildShapeMap( + n_device_topo, + values, + ids, + axom::execution_space::allocatorID()); + + TopoView topoView(connView, shapesView, sizesView, offsetsView, shapeMap); + + // Create options to control the clipping. + conduit::Node options; + options["clipField"] = "distance"; + options["clipValue"] = 12.f; + options["inside"] = 1; + options["outside"] = 0; + + // Clip the data + conduit::Node deviceClipMesh; + axom::mir::clipping::ClipField clipper( + topoView, + coordsetView); + clipper.execute(deviceMesh, options, deviceClipMesh); + + // Copy device->host + conduit::Node hostClipMesh; + bputils::copy(hostClipMesh, deviceClipMesh); + + // Handle baseline comparison. + std::string baselineName(yamlRoot(name)); + const auto paths = baselinePaths(); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostClipMesh); +#else + EXPECT_TRUE(compareBaseline(paths, baselineName, hostClipMesh)); +#endif +} + +TEST(mir_clipfield, mixed_seq) { braid3d_mixed_clip_test("mixed"); } +#if defined(AXOM_USE_OPENMP) +TEST(mir_clipfield, mixed_omp) { braid3d_mixed_clip_test("mixed"); } +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_clipfield, mixed_cuda) { braid3d_mixed_clip_test("mixed"); } +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_clipfield, mixed_hip) { braid3d_mixed_clip_test("mixed"); } +#endif + +//------------------------------------------------------------------------------ +template +void compare_values(const Container1 &c1, const Container2 &c2) +{ + EXPECT_EQ(c1.size(), c2.number_of_elements()); + for(size_t i = 0; i < c1.size(); i++) + { + EXPECT_EQ(c1[i], c2[i]); + } +} + +template +struct point_merge_test +{ + static void create(conduit::Node &hostMesh) + { + hostMesh["coordsets/coords/type"] = "explicit"; + hostMesh["coordsets/coords/values/x"].set( + std::vector {{0., 1., 2., 0., 1., 2., 0., 1., 2.}}); + hostMesh["coordsets/coords/values/y"].set( + std::vector {{0., 0., 0., 1., 1., 1., 2., 2., 2.}}); + hostMesh["topologies/mesh/type"] = "unstructured"; + hostMesh["topologies/mesh/coordset"] = "coords"; + hostMesh["topologies/mesh/elements/shape"] = "quad"; + hostMesh["topologies/mesh/elements/connectivity"].set( + std::vector {{0, 1, 4, 3, 1, 2, 5, 4, 3, 4, 7, 6, 4, 5, 8, 7}}); + hostMesh["topologies/mesh/elements/sizes"].set( + std::vector {{4, 4, 4, 4}}); + hostMesh["topologies/mesh/elements/offsets"].set( + std::vector {{0, 4, 8, 12}}); + hostMesh["fields/clip/topology"] = "mesh"; + hostMesh["fields/clip/association"] = "vertex"; + hostMesh["fields/clip/values"].set( + std::vector {{1., 1., 0.5, 1., 1., 0., 0.5, 0., 0.5}}); + } + + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + // Set up views for the mesh. + using CoordsetView = axom::mir::views::ExplicitCoordsetView; + CoordsetView coordsetView( + bputils::make_array_view( + deviceMesh.fetch_existing("coordsets/coords/values/x")), + bputils::make_array_view( + deviceMesh.fetch_existing("coordsets/coords/values/y"))); + + using TopologyView = axom::mir::views::UnstructuredTopologySingleShapeView< + axom::mir::views::QuadShape>; + TopologyView topologyView(bputils::make_array_view( + deviceMesh.fetch_existing("topologies/mesh/elements/connectivity"))); + + // Clip + conduit::Node options, deviceClipMesh; + options["clipField"] = "clip"; + options["clipValue"] = 0.5; + using Clip = + axom::mir::clipping::ClipField; + Clip clip(topologyView, coordsetView); + clip.execute(deviceMesh, options, deviceClipMesh); + + // device->host + conduit::Node hostClipMesh; + bputils::copy(hostClipMesh, deviceClipMesh); + //printNode(hostClipMesh); + + // Check that the points were merged when making the new mesh. + std::vector x {{2.0, 2.0, 0.0, 1.0, 2.0, 1.5, 1.0}}; + std::vector y {{0.0, 1.0, 2.0, 2.0, 2.0, 1.0, 1.5}}; + EXPECT_EQ(x.size(), 7); + EXPECT_EQ(y.size(), 7); + for(size_t i = 0; i < x.size(); i++) + { + EXPECT_FLOAT_EQ( + hostClipMesh["coordsets/coords/values/x"].as_float_accessor()[i], + x[i]); + EXPECT_FLOAT_EQ( + hostClipMesh["coordsets/coords/values/y"].as_float_accessor()[i], + y[i]); + } + + // Check that the degenerate quads were turned into triangles. + std::vector shapes {{2, 2, 3, 2}}; + std::vector sizes {{3, 3, 4, 3}}; + std::vector offsets {{0, 4, 8, 12}}; + compare_values( + shapes, + hostClipMesh["topologies/mesh/elements/shapes"].as_int_accessor()); + compare_values( + sizes, + hostClipMesh["topologies/mesh/elements/sizes"].as_int_accessor()); + compare_values( + offsets, + hostClipMesh["topologies/mesh/elements/offsets"].as_int_accessor()); + } +}; + +TEST(mir_clipfield, pointmerging_seq) { point_merge_test::test(); } +#if defined(AXOM_USE_OPENMP) +TEST(mir_clipfield, pointmerging_omp) { point_merge_test::test(); } +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_clipfield, pointmerging_cuda) { point_merge_test::test(); } +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_clipfield, pointmerging_hip) { point_merge_test::test(); } +#endif + +//------------------------------------------------------------------------------ + +template +struct test_selectedzones +{ + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + // Wrap the data in views. + auto coordsetView = + axom::mir::views::make_rectilinear_coordset::view( + deviceMesh["coordsets/coords"]); + using CoordsetView = decltype(coordsetView); + + auto topologyView = axom::mir::views::make_rectilinear<2>::view( + deviceMesh["topologies/mesh"]); + using TopologyView = decltype(topologyView); + + conduit::Node hostOptions; + hostOptions["selectedZones"].set( + std::vector {{1, 3, 4, 5, 7}}); + hostOptions["inside"] = 1; + hostOptions["outside"] = 1; + hostOptions["clipField"] = "zero"; + + conduit::Node deviceOptions, deviceResult; + bputils::copy(deviceOptions, hostOptions); + + axom::mir::clipping::ClipField clip( + topologyView, + coordsetView); + clip.execute(deviceMesh, deviceOptions, deviceResult); + + // device->host + conduit::Node hostResult; + bputils::copy(hostResult, deviceResult); + //printNode(hostResult); + + // Handle baseline comparison. + const auto paths = baselinePaths(); + std::string baselineName(yamlRoot("selectedzones1")); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostResult); +#else + EXPECT_TRUE(compareBaseline(paths, baselineName, hostResult)); +#endif + + //--------------------- + // Try a different clip + hostOptions["outside"] = 0; + hostOptions["clipField"] = "radial"; + hostOptions["clipValue"] = 3.2; + bputils::copy(deviceOptions, hostOptions); + deviceResult.reset(); + clip.execute(deviceMesh, deviceOptions, deviceResult); + + // device->host + bputils::copy(hostResult, deviceResult); + //printNode(hostResult); + + baselineName = yamlRoot("selectedzones2"); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostResult); +#else + EXPECT_TRUE(compareBaseline(paths, baselineName, hostResult)); +#endif + } + + static void create(conduit::Node &mesh) + { + /* + 12--13--14--15 + | | x | | + 8---9--10---11 + | x | x | x | x=selected zones + 4---5---6---7 + | | x | | + 0---1---2---3 + */ + const char *yaml = R"xx( +coordsets: + coords: + type: rectilinear + values: + x: [0., 1., 2., 3.] + y: [0., 1., 2., 3.] +topologies: + mesh: + type: rectilinear + coordset: coords +fields: + zero: + topology: mesh + association: vertex + values: [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.] + radial: + topology: mesh + association: vertex + values: [0.0, 1.0, 2.0, 3.0, 1.0, 1.414, 2.236, 3.162, 2.0, 2.236, 2.828, 3.605, 3.0, 3.162, 3.605, 4.242] +)xx"; + + mesh.parse(yaml); + } +}; + +TEST(mir_clipfield, selectedzones_seq) { test_selectedzones::test(); } +#if defined(AXOM_USE_OPENMP) +TEST(mir_clipfield, selectedzones_omp) { test_selectedzones::test(); } +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_clipfield, selectedzones_cuda) +{ + test_selectedzones::test(); +} +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_clipfield, selectedzones_hip) { test_selectedzones::test(); } +#endif + +//------------------------------------------------------------------------------ +#if defined(DEBUGGING_TEST_CASES) +void conduit_debug_err_handler(const std::string &s1, const std::string &s2, int i1) +{ + std::cout << "s1=" << s1 << ", s2=" << s2 << ", i1=" << i1 << std::endl; + // This is on purpose. + while(1) + ; +} +#endif + +//------------------------------------------------------------------------------ +int main(int argc, char *argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; // create & initialize test logger, +#if defined(DEBUGGING_TEST_CASES) + conduit::utils::set_error_handler(conduit_debug_err_handler); +#endif +#if defined(AXOM_USE_CALIPER) + axom::CLI::App app; + std::string annotationMode("none"); + app.add_option("--caliper", annotationMode) + ->description( + "caliper annotation mode. Valid options include 'none' and 'report'. " + "Use 'help' to see full list.") + ->capture_default_str() + ->check(axom::utilities::ValidCaliperMode); + + // Parse command line options. + app.parse(argc, argv); + + axom::utilities::raii::AnnotationsWrapper annotations_raii_wrapper( + annotationMode); +#endif + + result = RUN_ALL_TESTS(); + return result; +} diff --git a/src/axom/mir/tests/mir_equiz2d.cpp b/src/axom/mir/tests/mir_equiz2d.cpp new file mode 100644 index 0000000000..905e4c7c21 --- /dev/null +++ b/src/axom/mir/tests/mir_equiz2d.cpp @@ -0,0 +1,201 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/mir.hpp" +#include "axom/primal.hpp" +#include "axom/mir/tests/mir_testing_data_helpers.hpp" + +//------------------------------------------------------------------------------ + +// Uncomment to generate baselines +//#define AXOM_TESTING_GENERATE_BASELINES + +// Uncomment to save visualization files for debugging (when making baselines) +//#define AXOM_TESTING_SAVE_VISUALIZATION + +#include "axom/mir/tests/mir_testing_helpers.hpp" + +std::string baselineDirectory() +{ + return pjoin(pjoin(pjoin(dataDirectory(), "mir"), "regression"), "mir_equiz"); +} + +//------------------------------------------------------------------------------ +TEST(mir_equiz, miralgorithm) +{ + axom::mir::MIRAlgorithm *m = nullptr; + EXPECT_EQ(m, nullptr); +} + +//------------------------------------------------------------------------------ +TEST(mir_equiz, materialinformation) +{ + conduit::Node matset; + matset["material_map/a"] = 1; + matset["material_map/b"] = 2; + matset["material_map/c"] = 0; + + auto mi = axom::mir::views::materials(matset); + EXPECT_EQ(mi.size(), 3); + EXPECT_EQ(mi[0].number, 1); + EXPECT_EQ(mi[0].name, "a"); + + EXPECT_EQ(mi[1].number, 2); + EXPECT_EQ(mi[1].name, "b"); + + EXPECT_EQ(mi[2].number, 0); + EXPECT_EQ(mi[2].name, "c"); +} + +//------------------------------------------------------------------------------ +template +void braid2d_mat_test(const std::string &type, + const std::string &mattype, + const std::string &name) +{ + namespace bputils = axom::mir::utilities::blueprint; + + axom::StackArray dims {10, 10}; + axom::StackArray zoneDims {dims[0] - 1, dims[1] - 1}; + + // Create the data + conduit::Node hostMesh, deviceMesh; + axom::mir::testing::data::braid(type, dims, hostMesh); + axom::mir::testing::data::make_matset(mattype, "mesh", zoneDims, hostMesh); + axom::mir::utilities::blueprint::copy(deviceMesh, hostMesh); +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMesh, name + "_orig", "hdf5"); +#endif + + // Make views. + auto coordsetView = axom::mir::views::make_uniform_coordset<2>::view( + deviceMesh["coordsets/coords"]); + auto topologyView = + axom::mir::views::make_uniform<2>::view(deviceMesh["topologies/mesh"]); + using CoordsetView = decltype(coordsetView); + using TopologyView = decltype(topologyView); + + conduit::Node deviceMIRMesh; + if(mattype == "unibuffer") + { + // clang-format off + using MatsetView = axom::mir::views::UnibufferMaterialView; + MatsetView matsetView; + matsetView.set(bputils::make_array_view(deviceMesh["matsets/mat/material_ids"]), + bputils::make_array_view(deviceMesh["matsets/mat/volume_fractions"]), + bputils::make_array_view(deviceMesh["matsets/mat/sizes"]), + bputils::make_array_view(deviceMesh["matsets/mat/offsets"]), + bputils::make_array_view(deviceMesh["matsets/mat/indices"])); + // clang-format on + + using MIR = + axom::mir::EquiZAlgorithm; + MIR m(topologyView, coordsetView, matsetView); + conduit::Node options; + options["matset"] = "mat"; + m.execute(deviceMesh, options, deviceMIRMesh); + } + + // device->host + conduit::Node hostMIRMesh; + axom::mir::utilities::blueprint::copy(hostMIRMesh, deviceMIRMesh); + +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMIRMesh, name, "hdf5"); +#endif + // Handle baseline comparison. + { + std::string baselineName(yamlRoot(name)); + const auto paths = baselinePaths(); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostMIRMesh); +#else + constexpr double tolerance = 2.6e-06; + EXPECT_TRUE(compareBaseline(paths, baselineName, hostMIRMesh, tolerance)); +#endif + } +} + +//------------------------------------------------------------------------------ +TEST(mir_equiz, equiz_uniform_unibuffer_seq) +{ + AXOM_ANNOTATE_SCOPE("equiz_uniform_unibuffer_seq"); + braid2d_mat_test("uniform", "unibuffer", "equiz_uniform_unibuffer"); +} + +#if defined(AXOM_USE_OPENMP) +TEST(mir_equiz, equiz_uniform_unibuffer_omp) +{ + AXOM_ANNOTATE_SCOPE("equiz_uniform_unibuffer_omp"); + braid2d_mat_test("uniform", "unibuffer", "equiz_uniform_unibuffer"); +} +#endif + +#if defined(AXOM_USE_CUDA) +TEST(mir_equiz, equiz_uniform_unibuffer_cuda) +{ + AXOM_ANNOTATE_SCOPE("equiz_uniform_unibuffer_cuda"); + braid2d_mat_test("uniform", "unibuffer", "equiz_uniform_unibuffer"); +} +#endif + +#if defined(AXOM_USE_HIP) +TEST(mir_equiz, equiz_uniform_unibuffer_hip) +{ + AXOM_ANNOTATE_SCOPE("equiz_uniform_unibuffer_hip"); + braid2d_mat_test("uniform", "unibuffer", "equiz_uniform_unibuffer"); +} +#endif + +//------------------------------------------------------------------------------ +void conduit_debug_err_handler(const std::string &s1, const std::string &s2, int i1) +{ + std::cout << "s1=" << s1 << ", s2=" << s2 << ", i1=" << i1 << std::endl; + // This is on purpose. + while(1) + ; +} + +//------------------------------------------------------------------------------ + +int main(int argc, char *argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + // Define command line options. + bool handler = true; + axom::CLI::App app; + app.add_option("--handler", handler) + ->description("Install a custom error handler that loops forever."); +#if defined(AXOM_USE_CALIPER) + std::string annotationMode("none"); + app.add_option("--caliper", annotationMode) + ->description( + "caliper annotation mode. Valid options include 'none' and 'report'. " + "Use 'help' to see full list.") + ->capture_default_str() + ->check(axom::utilities::ValidCaliperMode); +#endif + // Parse command line options. + app.parse(argc, argv); + +#if defined(AXOM_USE_CALIPER) + axom::utilities::raii::AnnotationsWrapper annotations_raii_wrapper( + annotationMode); +#endif + + axom::slic::SimpleLogger logger; // create & initialize test logger, + if(handler) + { + conduit::utils::set_error_handler(conduit_debug_err_handler); + } + + result = RUN_ALL_TESTS(); + return result; +} diff --git a/src/axom/mir/tests/mir_equiz3d.cpp b/src/axom/mir/tests/mir_equiz3d.cpp new file mode 100644 index 0000000000..11b95a9a26 --- /dev/null +++ b/src/axom/mir/tests/mir_equiz3d.cpp @@ -0,0 +1,180 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/mir.hpp" +#include "axom/primal.hpp" +#include "axom/mir/tests/mir_testing_data_helpers.hpp" + +//------------------------------------------------------------------------------ + +// Uncomment to generate baselines +//#define AXOM_TESTING_GENERATE_BASELINES + +// Uncomment to save visualization files for debugging (when making baselines) +//#define AXOM_TESTING_SAVE_VISUALIZATION + +#include "axom/mir/tests/mir_testing_helpers.hpp" + +std::string baselineDirectory() +{ + return pjoin(pjoin(pjoin(dataDirectory(), "mir"), "regression"), "mir_equiz"); +} + +//------------------------------------------------------------------------------ +template +void braid3d_mat_test(const std::string &type, + const std::string &mattype, + const std::string &name) +{ + namespace bputils = axom::mir::utilities::blueprint; + + axom::StackArray dims {11, 11, 11}; + axom::StackArray zoneDims {dims[0] - 1, + dims[1] - 1, + dims[2] - 1}; + + // Create the data + conduit::Node hostMesh, deviceMesh; + axom::mir::testing::data::braid(type, dims, hostMesh); + axom::mir::testing::data::make_matset(mattype, "mesh", zoneDims, hostMesh); + axom::mir::utilities::blueprint::copy(deviceMesh, hostMesh); +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMesh, name + "_orig", "hdf5"); +#endif + + // Make views. + auto coordsetView = axom::mir::views::make_explicit_coordset::view( + deviceMesh["coordsets/coords"]); + using CoordsetView = decltype(coordsetView); + + using ShapeType = axom::mir::views::HexShape; + using TopologyView = + axom::mir::views::UnstructuredTopologySingleShapeView; + auto connView = bputils::make_array_view( + deviceMesh["topologies/mesh/elements/connectivity"]); + TopologyView topologyView(connView); + + conduit::Node deviceMIRMesh; + if(mattype == "unibuffer") + { + // clang-format off + using MatsetView = axom::mir::views::UnibufferMaterialView; + MatsetView matsetView; + matsetView.set(bputils::make_array_view(deviceMesh["matsets/mat/material_ids"]), + bputils::make_array_view(deviceMesh["matsets/mat/volume_fractions"]), + bputils::make_array_view(deviceMesh["matsets/mat/sizes"]), + bputils::make_array_view(deviceMesh["matsets/mat/offsets"]), + bputils::make_array_view(deviceMesh["matsets/mat/indices"])); + // clang-format on + + using MIR = + axom::mir::EquiZAlgorithm; + MIR m(topologyView, coordsetView, matsetView); + conduit::Node options; + options["matset"] = "mat"; + m.execute(deviceMesh, options, deviceMIRMesh); + } + + // device->host + conduit::Node hostMIRMesh; + axom::mir::utilities::blueprint::copy(hostMIRMesh, deviceMIRMesh); + +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMIRMesh, name, "hdf5"); +#endif + // Handle baseline comparison. + { + std::string baselineName(yamlRoot(name)); + const auto paths = baselinePaths(); +#if defined(AXOM_TESTING_GENERATE_BASELINES) + saveBaseline(paths, baselineName, hostMIRMesh); +#else + constexpr double tolerance = 1.7e-6; + EXPECT_TRUE(compareBaseline(paths, baselineName, hostMIRMesh, tolerance)); +#endif + } +} + +//------------------------------------------------------------------------------ +TEST(mir_equiz, equiz_hex_unibuffer_seq) +{ + AXOM_ANNOTATE_SCOPE("equiz_explicit_hex_seq"); + braid3d_mat_test("hexs", "unibuffer", "equiz_hex_unibuffer"); +} + +#if defined(AXOM_USE_OPENMP) +TEST(mir_equiz, equiz_hex_unibuffer_omp) +{ + AXOM_ANNOTATE_SCOPE("equiz_hex_unibuffer_omp"); + braid3d_mat_test("hexs", "unibuffer", "equiz_hex_unibuffer"); +} +#endif + +#if defined(AXOM_USE_CUDA) +TEST(mir_equiz, equiz_hex_unibuffer_cuda) +{ + AXOM_ANNOTATE_SCOPE("equiz_hex_unibuffer_cuda"); + braid3d_mat_test("hexs", "unibuffer", "equiz_hex_unibuffer"); +} +#endif + +#if defined(AXOM_USE_HIP) +TEST(mir_equiz, equiz_hex_unibuffer_hip) +{ + AXOM_ANNOTATE_SCOPE("equiz_hex_unibuffer_hip"); + braid3d_mat_test("hexs", "unibuffer", "equiz_hex_unibuffer"); +} +#endif + +//------------------------------------------------------------------------------ +void conduit_debug_err_handler(const std::string &s1, const std::string &s2, int i1) +{ + std::cout << "s1=" << s1 << ", s2=" << s2 << ", i1=" << i1 << std::endl; + // This is on purpose. + while(1) + ; +} + +//------------------------------------------------------------------------------ + +int main(int argc, char *argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + // Define command line options. + bool handler = true; + axom::CLI::App app; + app.add_option("--handler", handler) + ->description("Install a custom error handler that loops forever."); +#if defined(AXOM_USE_CALIPER) + std::string annotationMode("none"); + app.add_option("--caliper", annotationMode) + ->description( + "caliper annotation mode. Valid options include 'none' and 'report'. " + "Use 'help' to see full list.") + ->capture_default_str() + ->check(axom::utilities::ValidCaliperMode); +#endif + // Parse command line options. + app.parse(argc, argv); + +#if defined(AXOM_USE_CALIPER) + axom::utilities::raii::AnnotationsWrapper annotations_raii_wrapper( + annotationMode); +#endif + + axom::slic::SimpleLogger logger; // create & initialize test logger, + if(handler) + { + conduit::utils::set_error_handler(conduit_debug_err_handler); + } + + result = RUN_ALL_TESTS(); + return result; +} diff --git a/src/axom/mir/tests/mir_mergemeshes.cpp b/src/axom/mir/tests/mir_mergemeshes.cpp new file mode 100644 index 0000000000..da61338de3 --- /dev/null +++ b/src/axom/mir/tests/mir_mergemeshes.cpp @@ -0,0 +1,184 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/mir.hpp" + +#include "axom/mir/tests/mir_testing_helpers.hpp" +#include "axom/mir/tests/mir_testing_data_helpers.hpp" + +#include +#include + +namespace mir = axom::mir; +namespace bputils = axom::mir::utilities::blueprint; + +//------------------------------------------------------------------------------ +template +struct test_mergemeshes +{ + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + // Set up inputs. + std::vector inputs(2); + inputs[0].m_input = deviceMesh.fetch_ptr("domain0000"); + + inputs[1].m_input = deviceMesh.fetch_ptr("domain0001"); + // The node names for input 1 in the final merged mesh. + const axom::IndexType nodeMap[] = {1, 2, 5, 6, 9, 10, 13, 14, 16, 17}; + // The 2 nodes in input 1 that do not appear in input 0 + const axom::IndexType nodeSlice[] = {8, 9}; + const int allocatorID = axom::execution_space::allocatorID(); + axom::Array deviceNodeMap(10, 10, allocatorID); + axom::Array deviceNodeSlice(2, 2, allocatorID); + axom::copy(deviceNodeMap.data(), nodeMap, 10 * sizeof(axom::IndexType)); + axom::copy(deviceNodeSlice.data(), nodeSlice, 2 * sizeof(axom::IndexType)); + inputs[1].m_nodeMapView = deviceNodeMap.view(); + inputs[1].m_nodeSliceView = deviceNodeSlice.view(); + + // Execute + conduit::Node opts, deviceResult; + opts["topology"] = "mesh"; + bputils::MergeMeshes mm; + mm.execute(inputs, opts, deviceResult); + + // device->host + conduit::Node hostResult; + bputils::copy(hostResult, deviceResult); + + //printNode(hostResult); + //conduit::relay::io::blueprint::save_mesh(hostResult, "mergemeshes", "hdf5"); + + constexpr double tolerance = 1.e-7; + conduit::Node expectedResult, info; + result(expectedResult); + bool success = compareConduit(expectedResult, hostResult, tolerance, info); + if(!success) + { + info.print(); + } + EXPECT_TRUE(success); + } + + static void create(conduit::Node &mesh) + { + const char *yaml = R"xx( +domain0000: + coordsets: + coords: + type: explicit + values: + x: [0., 1., 2., 3., 0., 1., 2., 3., 0., 1., 2., 3., 0., 1., 2., 3.] + y: [0., 0., 0., 0., 1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.] + topologies: + mesh: + type: unstructured + coordset: coords + elements: + shape: quad + connectivity: [0,1,5,4, 4,5,9,8, 8,9,13,12, 2,3,7,6, 6,7,11,10, 10,11,15,14] + sizes: [4,4,4, 4,4,4] + offsets: [0,4,8,12,16,20] + fields: + nodal: + topology: mesh + association: vertex + values: [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0] + zonal: + topology: mesh + association: element + values: [0,1,2, 3,4,5] +domain0001: + coordsets: + coords: + type: explicit + values: + x: [1., 2., 1., 2., 1., 2., 1., 2., 1.5, 1.5] + y: [0., 0., 1., 1., 2., 2., 3., 3., 0.5, 1.5] + topologies: + mesh: + type: unstructured + coordset: coords + elements: + shape: mixed + shape_map: + quad: 3 + tri: 2 + connectivity: [0,8,2, 0,1,8, 1,3,8, 8,3,2, 2,9,4, 2,3,9, 3,5,9, 5,4,9, 4,5,7,6] + sizes: [3,3,3,3, 3,3,3,3, 4] + offsets: [0,3,6,9, 12,15,18,21, 24] + shapes: [2,2,2,2, 2,2,2,2, 3] + fields: + nodal: + topology: mesh + association: vertex + values: [1,1,1,1,1,1,1,1, 2,2] + zonal: + topology: mesh + association: element + values: [0,1,2,3, 4,5,6,7, 8] +)xx"; + mesh.parse(yaml); + } + + static void result(conduit::Node &mesh) + { + const char *yaml = R"xx( +coordsets: + coords: + type: "explicit" + values: + x: [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 1.5, 1.5] + y: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 0.5, 1.5] +topologies: + mesh: + type: "unstructured" + coordset: "coords" + elements: + connectivity: [0, 1, 5, 4, 4, 5, 9, 8, 8, 9, 13, 12, 2, 3, 7, 6, 6, 7, 11, 10, 10, 11, 15, 14, 1, 16, 5, 1, 2, 16, 2, 6, 16, 16, 6, 5, 5, 17, 9, 5, 6, 17, 6, 10, 17, 10, 9, 17, 9, 10, 14, 13] + sizes: [4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4] + offsets: [0, 4, 8, 12, 16, 20, 24, 27, 30, 33, 36, 39, 42, 45, 48] + shape: "mixed" + shape_map: + quad: 3 + tri: 2 + shapes: [3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3] +)xx"; + mesh.parse(yaml); + } +}; + +TEST(mir_mergemeshes, mergemeshes_seq) { test_mergemeshes::test(); } +#if defined(AXOM_USE_OPENMP) +TEST(mir_mergemeshes, mergemeshes_omp) { test_mergemeshes::test(); } +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_mergemeshes, mergemeshes_cuda) { test_mergemeshes::test(); } +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_mergemeshes, mergemeshes_hip) { test_mergemeshes::test(); } +#endif + +//------------------------------------------------------------------------------ +int main(int argc, char *argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} diff --git a/src/axom/mir/tests/mir_node_to_zone_relation.cpp b/src/axom/mir/tests/mir_node_to_zone_relation.cpp new file mode 100644 index 0000000000..c9fda27e66 --- /dev/null +++ b/src/axom/mir/tests/mir_node_to_zone_relation.cpp @@ -0,0 +1,330 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/mir.hpp" + +#include "axom/mir/tests/mir_testing_helpers.hpp" +#include "axom/mir/tests/mir_testing_data_helpers.hpp" + +#include +#include + +namespace mir = axom::mir; +namespace bputils = axom::mir::utilities::blueprint; + +//------------------------------------------------------------------------------ +template +struct test_node_to_zone_relation_builder +{ + static void test(const conduit::Node &hostMesh) + { + // host -> device + conduit::Node deviceMesh; + axom::mir::utilities::blueprint::copy(deviceMesh, hostMesh); + // _mir_utilities_n2zrel_begin + const conduit::Node &deviceTopo = deviceMesh["topologies/mesh"]; + const conduit::Node &deviceCoordset = deviceMesh["coordsets/coords"]; + + // Run the algorithm on the device + conduit::Node deviceRelation; + axom::mir::utilities::blueprint::NodeToZoneRelationBuilder n2z; + n2z.execute(deviceTopo, deviceCoordset, deviceRelation); + // _mir_utilities_n2zrel_end + + // device -> host + conduit::Node hostRelation; + axom::mir::utilities::blueprint::copy(hostRelation, deviceRelation); + + // Expected answers + // clang-format off + const int zones[] = { + 0, + 0, 1, + 1, 2, + 2, + 0, 3, // NOTE: these are sorted here + 0, 1, 3, 4, + 1, 2, 4, 5, + 2, 5, + 3, + 3, 4, + 4, 5, + 5 + }; + const int sizes[] = {1, 2, 2, 1, 2, 4, 4, 2, 1, 2, 2, 1}; + const int offsets[] = {0, 1, 3, 5, 6, 8, 12, 16, 18, 19, 21, 23}; + // clang-format on + + // Compare answers. + compareRelation( + hostRelation, + axom::ArrayView(zones, sizeof(zones) / sizeof(int)), + axom::ArrayView(sizes, sizeof(sizes) / sizeof(int)), + axom::ArrayView(offsets, sizeof(offsets) / sizeof(int))); + } + + static void compareRelation(const conduit::Node &hostRelation, + const axom::ArrayView &zones, + const axom::ArrayView &sizes, + const axom::ArrayView &offsets) + { + const auto zonesView = + bputils::make_array_view(hostRelation["zones"]); + const auto sizesView = + bputils::make_array_view(hostRelation["sizes"]); + const auto offsetsView = + bputils::make_array_view(hostRelation["offsets"]); + EXPECT_EQ(sizesView.size(), sizes.size()); + EXPECT_EQ(offsetsView.size(), offsets.size()); + for(axom::IndexType i = 0; i < sizesView.size(); i++) + { + EXPECT_EQ(sizes[i], sizesView[i]); + EXPECT_EQ(offsets[i], offsetsView[i]); + } + for(axom::IndexType i = 0; i < sizesView.size(); i++) + { + // Sort the result so we can compare to the expected answer. + IndexT *begin = zonesView.data() + offsetsView[i]; + IndexT *end = zonesView.data() + offsetsView[i] + sizesView[i]; + std::sort(begin, end); + + for(int j = 0; j < sizesView[i]; j++) + { + EXPECT_EQ(zones[offsets[i] + j], zonesView[offsetsView[i] + j]); + } + } + } +}; + +TEST(mir_node_to_zone_relation, n2zrel_unstructured_seq) +{ + /* + 8---9--10--11 + | | | | + 4---5---6---7 + | | | | + 0---1---2---3 + */ + conduit::Node mesh; + axom::StackArray dims {{4, 3}}; + axom::mir::testing::data::braid("quads", dims, mesh); + test_node_to_zone_relation_builder::test(mesh); +} + +#if defined(AXOM_USE_OPENMP) +TEST(mir_node_to_zone_relation, n2zrel_unstructured_omp) +{ + conduit::Node mesh; + axom::StackArray dims {{4, 3}}; + axom::mir::testing::data::braid("quads", dims, mesh); + test_node_to_zone_relation_builder::test(mesh); +} +#endif + +#if defined(AXOM_USE_CUDA) +TEST(mir_node_to_zone_relation, n2zrel_unstructured_cuda) +{ + conduit::Node mesh; + axom::StackArray dims {{4, 3}}; + axom::mir::testing::data::braid("quads", dims, mesh); + test_node_to_zone_relation_builder::test(mesh); +} +#endif + +#if defined(AXOM_USE_HIP) +TEST(mir_node_to_zone_relation, n2zrel_unstructured_hip) +{ + conduit::Node mesh; + axom::StackArray dims {{4, 3}}; + axom::mir::testing::data::braid("quads", dims, mesh); + test_node_to_zone_relation_builder::test(mesh); +} +#endif + +TEST(mir_node_to_zone_relation, n2zrel_rectilinear_seq) +{ + /* + 8---9--10--11 + | | | | + 4---5---6---7 + | | | | + 0---1---2---3 + */ + conduit::Node mesh; + axom::StackArray dims {{4, 3}}; + axom::mir::testing::data::braid("rectilinear", dims, mesh); + test_node_to_zone_relation_builder::test(mesh); +} +#if defined(AXOM_USE_OPENMP) +TEST(mir_node_to_zone_relation, n2zrel_rectilinear_omp) +{ + conduit::Node mesh; + axom::StackArray dims {{4, 3}}; + axom::mir::testing::data::braid("rectilinear", dims, mesh); + test_node_to_zone_relation_builder::test(mesh); +} +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_node_to_zone_relation, n2zrel_rectilinear_cuda) +{ + conduit::Node mesh; + axom::StackArray dims {{4, 3}}; + axom::mir::testing::data::braid("rectilinear", dims, mesh); + test_node_to_zone_relation_builder::test(mesh); +} +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_node_to_zone_relation, n2zrel_rectilinear_hip) +{ + conduit::Node mesh; + axom::StackArray dims {{4, 3}}; + axom::mir::testing::data::braid("rectilinear", dims, mesh); + test_node_to_zone_relation_builder::test(mesh); +} +#endif + +template +struct test_node_to_zone_relation_builder_polyhedral + : public test_node_to_zone_relation_builder +{ + using SuperClass = test_node_to_zone_relation_builder; + + static void test(const conduit::Node &hostMesh) + { + // host -> device + conduit::Node deviceMesh; + axom::mir::utilities::blueprint::copy(deviceMesh, hostMesh); + const conduit::Node &deviceTopo = deviceMesh["topologies/mesh"]; + const conduit::Node &deviceCoordset = deviceMesh["coordsets/coords"]; + + // Run the algorithm on the device + conduit::Node deviceRelation; + axom::mir::utilities::blueprint::NodeToZoneRelationBuilder n2z; + n2z.execute(deviceTopo, deviceCoordset, deviceRelation); + + // device -> host + conduit::Node hostRelation; + axom::mir::utilities::blueprint::copy(hostRelation, deviceRelation); + + // Expected answers + // clang-format off + const int zones[] = { + /*node 0*/ 0, + /*node 1*/ 0, 1, + /*node 2*/ 1, + /*node 3*/ 0, 2, + /*node 4*/ 0, 1, 2, 3, + /*node 5*/ 1, 3, + /*node 6*/ 2, + /*node 7*/ 2, 3, + /*node 8*/ 3, + + /*node 9*/ 0, 4, + /*node 10*/ 0, 1, 4, 5, + /*node 11*/ 1, 5, + /*node 12*/ 0, 2, 4, 6, + /*node 13*/ 0, 1, 2, 3, 4, 5, 6, 7, + /*node 14*/ 1, 3, 5, 7, + /*node 15*/ 2, 6, + /*node 16*/ 2, 3, 6, 7, + /*node 17*/ 3, 7, + + /*node 18*/ 4, + /*node 19*/ 4, 5, + /*node 20*/ 5, + /*node 21*/ 4, 6, + /*node 22*/ 4, 5, 6, 7, + /*node 23*/ 5, 7, + /*node 24*/ 6, + /*node 25*/ 6, 7, + /*node 26*/ 7 + }; + const int sizes[] = {1, 2, 1, 2, 4, 2, 1, 2, 1, + 2, 4, 2, 4, 8, 4, 2, 4, 2, + 1, 2, 1, 2, 4, 2, 1, 2, 1 + }; + const int offsets[] = {0, 1, 3, 4, 6, 10, 12, 13, 15, + 16, 18, 22, 24, 28, 36, 40, 42, 46, + 48, 49, 51, 52, 54, 58, 60, 61, 63}; + // clang-format on + + // Compare answers. + SuperClass::compareRelation( + hostRelation, + axom::ArrayView(zones, sizeof(zones) / sizeof(int)), + axom::ArrayView(sizes, sizeof(sizes) / sizeof(int)), + axom::ArrayView(offsets, sizeof(offsets) / sizeof(int))); + } + + static void create(conduit::Node &mesh) + { + conduit::blueprint::mesh::examples::basic("polyhedra", 3, 3, 3, mesh); + // Make sure all the types are the same. + conduit::blueprint::mesh::utils::convert( + mesh, + conduit::DataType::int32(), + std::vector {{"topologies/mesh/elements/connectivity", + "topologies/mesh/elements/sizes", + "topologies/mesh/elements/offsets", + "topologies/mesh/subelements/connectivity", + "topologies/mesh/subelements/sizes", + "topologies/mesh/subelements/offsets"}}); + } +}; + +TEST(mir_node_to_zone_relation, n2zrel_polyhedral_seq) +{ + conduit::Node mesh; + test_node_to_zone_relation_builder_polyhedral::create( + mesh); + test_node_to_zone_relation_builder_polyhedral::test( + mesh); +} +#if defined(AXOM_USE_OPENMP) +TEST(mir_node_to_zone_relation, n2zrel_polyhedral_omp) +{ + conduit::Node mesh; + test_node_to_zone_relation_builder_polyhedral::create( + mesh); + test_node_to_zone_relation_builder_polyhedral::test( + mesh); +} +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_node_to_zone_relation, n2zrel_polyhedral_cuda) +{ + conduit::Node mesh; + test_node_to_zone_relation_builder_polyhedral::create( + mesh); + test_node_to_zone_relation_builder_polyhedral::test( + mesh); +} +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_node_to_zone_relation, n2zrel_polyhedral_hip) +{ + conduit::Node mesh; + test_node_to_zone_relation_builder_polyhedral::create( + mesh); + test_node_to_zone_relation_builder_polyhedral::test( + mesh); +} +#endif + +//------------------------------------------------------------------------------ +int main(int argc, char *argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} diff --git a/src/axom/mir/tests/mir_slicers.cpp b/src/axom/mir/tests/mir_slicers.cpp new file mode 100644 index 0000000000..aa91a6d133 --- /dev/null +++ b/src/axom/mir/tests/mir_slicers.cpp @@ -0,0 +1,463 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/mir.hpp" + +#include "axom/mir/tests/mir_testing_helpers.hpp" +#include "axom/mir/tests/mir_testing_data_helpers.hpp" + +#include +#include + +namespace mir = axom::mir; +namespace bputils = axom::mir::utilities::blueprint; + +//------------------------------------------------------------------------------ +template +struct test_matset_slice +{ + static void test() + { + conduit::Node hostMatset; + create(hostMatset); + + // host->device + conduit::Node deviceMatset; + bputils::copy(deviceMatset, hostMatset); + + axom::Array ids {{1, 3, 5}}; + axom::Array selectedZones( + 3, + 3, + axom::execution_space::allocatorID()); + axom::copy(selectedZones.data(), ids.data(), 3 * sizeof(axom::IndexType)); + + using MatsetView = + axom::mir::views::UnibufferMaterialView; + MatsetView matsetView; + matsetView.set( + bputils::make_array_view(deviceMatset["material_ids"]), + bputils::make_array_view( + deviceMatset["volume_fractions"]), + bputils::make_array_view(deviceMatset["sizes"]), + bputils::make_array_view(deviceMatset["offsets"]), + bputils::make_array_view(deviceMatset["indices"])); + + // Slice it. + bputils::MatsetSlicer slicer(matsetView); + conduit::Node newDeviceMatset; + bputils::SliceData slice; + slice.m_indicesView = selectedZones.view(); + slicer.execute(slice, deviceMatset, newDeviceMatset); + + // device->host + conduit::Node newHostMatset; + bputils::copy(newHostMatset, newDeviceMatset); + + // Expected answers. + const axom::Array sizes {{2, 1, 2}}; + const axom::Array offsets {{0, 2, 3}}; + const axom::Array indices {{0, 1, 2, 3, 4}}; + const axom::Array material_ids {{1, 2, 2, 2, 3}}; + const axom::Array volume_fractions { + {0.5, 0.5, 1.0, 0.8, 0.2}}; + + EXPECT_EQ(conduit::DataType::INT64_ID, + newHostMatset["material_ids"].dtype().id()); + EXPECT_EQ(conduit::DataType::FLOAT64_ID, + newHostMatset["volume_fractions"].dtype().id()); + + EXPECT_TRUE(compare_views( + sizes.view(), + bputils::make_array_view(newHostMatset["sizes"]))); + EXPECT_TRUE(compare_views( + offsets.view(), + bputils::make_array_view(newHostMatset["offsets"]))); + EXPECT_TRUE(compare_views( + indices.view(), + bputils::make_array_view(newHostMatset["indices"]))); + EXPECT_TRUE(compare_views( + material_ids.view(), + bputils::make_array_view(newHostMatset["material_ids"]))); + EXPECT_TRUE(compare_views(volume_fractions.view(), + bputils::make_array_view( + newHostMatset["volume_fractions"]))); + } + + static void create(conduit::Node &matset) + { + /* + 8-------9------10------11 + | 2/1 | 1/0.1 | 2/0.8 | + | | 2/0.5 | 3/0.2 | + | | 3/0.4 | | + 4-------5-------6-------7 + | | 1/0.5 | 1/0.2 | + | 1/1 | 2/0.5 | 2/0.8 | + | | | | + 0-------1-------2-------3 + */ + const char *yaml = R"xx( +topology: mesh +material_map: + a: 1 + b: 2 + c: 3 +material_ids: [1, 1,2, 1,2, 2, 1,2,3, 2,3] +volume_fractions: [1., 0.5,0.5, 0.2,0.8, 1., 0.1,0.5,0.4, 0.8,0.2] +indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +sizes: [1, 2, 2, 1, 3, 2] +offsets: [0, 1, 3, 5, 6, 9] +)xx"; + + matset.parse(yaml); + } +}; + +TEST(mir_slicers, matsetslice_seq) { test_matset_slice::test(); } + +#if defined(AXOM_USE_OPENMP) +TEST(mir_slicers, matsetslice_omp) { test_matset_slice::test(); } +#endif + +#if defined(AXOM_USE_CUDA) +TEST(mir_slicers, matsetslice_cuda) { test_matset_slice::test(); } +#endif + +#if defined(AXOM_USE_HIP) +TEST(mir_slicers, matsetslice_hip) { test_matset_slice::test(); } +#endif + +//------------------------------------------------------------------------------ +template +void test_coordsetslicer(const conduit::Node &hostCoordset, Func &&makeView) +{ + axom::Array ids {{0, 1, 2, 4, 5, 6}}; + + const auto nnodes = ids.size(); + axom::Array selectedNodes( + nnodes, + nnodes, + axom::execution_space::allocatorID()); + axom::copy(selectedNodes.data(), ids.data(), nnodes * sizeof(axom::IndexType)); + + bputils::SliceData slice; + slice.m_indicesView = selectedNodes.view(); + + // host->device + conduit::Node deviceCoordset; + bputils::copy(deviceCoordset, hostCoordset); + + // Make a view. + auto coordsetView = makeView(deviceCoordset); + using CoordsetView = decltype(coordsetView); + + // Pull out selected nodes + bputils::CoordsetSlicer slicer(coordsetView); + conduit::Node newDeviceCoordset; + slicer.execute(slice, deviceCoordset, newDeviceCoordset); + + // device->host + conduit::Node newHostCoordset; + bputils::copy(newHostCoordset, newDeviceCoordset); + + // We get an explicit coordset out of the slicer. + const axom::Array x {{0., 1., 2., 0., 1., 2.}}; + const axom::Array y {{0., 0., 0., 1., 1., 1.}}; + EXPECT_TRUE(compare_views( + x.view(), + bputils::make_array_view(newHostCoordset["values/x"]))); + EXPECT_TRUE(compare_views( + y.view(), + bputils::make_array_view(newHostCoordset["values/y"]))); +} + +//------------------------------------------------------------------------------ +template +struct coordsetslicer_explicit +{ + static void test() + { + /* + 8---9--10--11 + | | | | + 4---5---6---7 + | | | | + 0---1---2---3 + */ + const char *yaml = R"xx( +type: explicit +values: + x: [0., 1., 2., 3., 0., 1., 2., 3., 0., 1., 2., 3.] + y: [0., 0., 0., 0., 1., 1., 1., 1., 2., 2., 2., 2.] +)xx"; + + conduit::Node coordset; + coordset.parse(yaml); + + auto makeView = [](const conduit::Node &deviceCoordset) { + return axom::mir::views::make_explicit_coordset::view( + deviceCoordset); + }; + + test_coordsetslicer(coordset, makeView); + } +}; + +TEST(mir_slicers, coordsetslicer_explicit_seq) +{ + coordsetslicer_explicit::test(); +} + +#if defined(AXOM_USE_OPENMP) +TEST(mir_slicers, coordsetslicer_explicit_omp) +{ + coordsetslicer_explicit::test(); +} +#endif + +#if defined(AXOM_USE_CUDA) +TEST(mir_slicers, coordsetslicer_explicit_cuda) +{ + coordsetslicer_explicit::test(); +} +#endif + +#if defined(AXOM_USE_HIP) +TEST(mir_slicers, coordsetslicer_explicit_hip) +{ + coordsetslicer_explicit::test(); +} +#endif + +//------------------------------------------------------------------------------ +template +struct coordsetslicer_rectilinear +{ + static void test() + { + /* + 8---9--10--11 + | | | | + 4---5---6---7 + | | | | + 0---1---2---3 + */ + const char *yaml = R"xx( +type: rectilinear +values: + x: [0., 1., 2., 3.] + y: [0., 1., 2.] +)xx"; + + conduit::Node coordset; + coordset.parse(yaml); + + auto makeView = [](const conduit::Node &deviceCoordset) { + return axom::mir::views::make_rectilinear_coordset::view( + deviceCoordset); + }; + test_coordsetslicer(coordset, makeView); + } +}; + +TEST(mir_slicers, coordsetslicer_rectilinear_seq) +{ + coordsetslicer_rectilinear::test(); +} + +#if defined(AXOM_USE_OPENMP) +TEST(mir_slicers, coordsetslicer_rectilinear_omp) +{ + coordsetslicer_rectilinear::test(); +} +#endif + +#if defined(AXOM_USE_CUDA) +TEST(mir_slicers, coordsetslicer_rectilinear_cuda) +{ + coordsetslicer_rectilinear::test(); +} +#endif + +#if defined(AXOM_USE_HIP) +TEST(mir_slicers, coordsetslicer_rectilinear_hip) +{ + coordsetslicer_rectilinear::test(); +} +#endif + +//------------------------------------------------------------------------------ +template +struct coordsetslicer_uniform +{ + static void test() + { + /* + 8---9--10--11 + | | | | + 4---5---6---7 + | | | | + 0---1---2---3 + */ + const char *yaml = R"xx( +type: uniform +dims: + i: 4 + j: 3 +)xx"; + + conduit::Node coordset; + coordset.parse(yaml); + + auto makeView = [](const conduit::Node &deviceCoordset) { + return axom::mir::views::make_uniform_coordset<2>::view(deviceCoordset); + }; + test_coordsetslicer(coordset, makeView); + } +}; + +TEST(mir_slicers, coordsetslicer_uniform_seq) +{ + coordsetslicer_uniform::test(); +} + +#if defined(AXOM_USE_OPENMP) +TEST(mir_slicers, coordsetslicer_uniform_omp) +{ + coordsetslicer_uniform::test(); +} +#endif + +#if defined(AXOM_USE_CUDA) +TEST(mir_slicers, coordsetslicer_uniform_cuda) +{ + coordsetslicer_uniform::test(); +} +#endif + +#if defined(AXOM_USE_HIP) +TEST(mir_slicers, coordsetslicer_uniform_hip) +{ + coordsetslicer_uniform::test(); +} +#endif + +//------------------------------------------------------------------------------ +template +struct test_fieldslicer +{ + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + // _mir_utilities_fieldslicer_begin + std::vector indices {0, 1, 2, 7, 8, 9}; + axom::Array sliceIndices( + indices.size(), + indices.size(), + axom::execution_space::allocatorID()); + axom::copy(sliceIndices.data(), + indices.data(), + sizeof(axom::IndexType) * indices.size()); + + bputils::SliceData slice; + slice.m_indicesView = sliceIndices.view(); + + conduit::Node slicedMesh; + bputils::FieldSlicer fs; + fs.execute(slice, deviceMesh["fields/scalar"], slicedMesh["fields/scalar"]); + fs.execute(slice, deviceMesh["fields/vector"], slicedMesh["fields/vector"]); + // _mir_utilities_fieldslicer_end + + // device->host + conduit::Node hostSlicedMesh; + bputils::copy(hostSlicedMesh, slicedMesh); + + std::vector resultX {0., 1., 2., 7., 8., 9.}; + std::vector resultY {0., 10., 20., 70., 80., 90.}; + + EXPECT_EQ(hostSlicedMesh["fields/scalar/topology"].as_string(), "mesh"); + EXPECT_EQ(hostSlicedMesh["fields/scalar/association"].as_string(), + "element"); + EXPECT_EQ(hostSlicedMesh["fields/scalar/values"].dtype().number_of_elements(), + indices.size()); + for(size_t i = 0; i < indices.size(); i++) + { + const auto acc = + hostSlicedMesh["fields/scalar/values"].as_double_accessor(); + EXPECT_EQ(acc[i], resultX[i]); + } + + EXPECT_EQ(hostSlicedMesh["fields/vector/topology"].as_string(), "mesh"); + EXPECT_EQ(hostSlicedMesh["fields/vector/association"].as_string(), + "element"); + EXPECT_EQ( + hostSlicedMesh["fields/vector/values/x"].dtype().number_of_elements(), + indices.size()); + EXPECT_EQ( + hostSlicedMesh["fields/vector/values/y"].dtype().number_of_elements(), + indices.size()); + for(size_t i = 0; i < indices.size(); i++) + { + const auto x = + hostSlicedMesh["fields/vector/values/x"].as_double_accessor(); + const auto y = + hostSlicedMesh["fields/vector/values/y"].as_double_accessor(); + EXPECT_EQ(x[i], resultX[i]); + EXPECT_EQ(y[i], resultY[i]); + } + } + + static void create(conduit::Node &fields) + { + const char *yaml = R"xx( +fields: + scalar: + topology: mesh + association: element + values: [0., 1., 2., 3., 4., 5., 6., 7., 8., 9.] + vector: + topology: mesh + association: element + values: + x: [0., 1., 2., 3., 4., 5., 6., 7., 8., 9.] + y: [0., 10., 20., 30., 40., 50., 60., 70., 80., 90.] +)xx"; + fields.parse(yaml); + } +}; + +TEST(mir_slicers, fieldslicer_seq) { test_fieldslicer::test(); } +#if defined(AXOM_USE_OPENMP) +TEST(mir_slicers, fieldslicer_omp) { test_fieldslicer::test(); } +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_slicers, fieldslicer_cuda) { test_fieldslicer::test(); } +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_slicers, fieldslicer_hip) { test_fieldslicer::test(); } +#endif + +//------------------------------------------------------------------------------ +int main(int argc, char *argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} diff --git a/src/axom/mir/tests/mir_testing_data_helpers.hpp b/src/axom/mir/tests/mir_testing_data_helpers.hpp new file mode 100644 index 0000000000..97338bba79 --- /dev/null +++ b/src/axom/mir/tests/mir_testing_data_helpers.hpp @@ -0,0 +1,406 @@ +// 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) +#ifndef AXOM_MIR_TESTING_DATA_HELPERS_HPP_ +#define AXOM_MIR_TESTING_DATA_HELPERS_HPP_ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include +#include +#include + +namespace axom +{ +namespace mir +{ +namespace testing +{ +namespace data +{ +//------------------------------------------------------------------------------ + +void add_distance(conduit::Node &mesh, float dist = 6.5f) +{ + // Make a new distance field. + const conduit::Node &n_coordset = mesh["coordsets"][0]; + axom::mir::views::dispatch_coordset(n_coordset, [&](auto coordsetView) { + mesh["fields/distance/topology"] = "mesh"; + mesh["fields/distance/association"] = "vertex"; + conduit::Node &n_values = mesh["fields/distance/values"]; + const auto nnodes = coordsetView.size(); + n_values.set(conduit::DataType::float32(nnodes)); + float *valuesPtr = static_cast(n_values.data_ptr()); + for(int index = 0; index < nnodes; index++) + { + const auto pt = coordsetView[index]; + float norm2 = 0.f; + for(int i = 0; i < pt.DIMENSION; i++) norm2 += pt[i] * pt[i]; + valuesPtr[index] = sqrt(norm2) - dist; + } + }); +} + +template +void braid(const std::string &type, const Dimensions &dims, conduit::Node &mesh) +{ + int d[3] = {0, 0, 0}; + auto n = dims.size(); + for(decltype(n) i = 0; i < n; i++) + { + d[i] = dims[i]; + } + conduit::blueprint::mesh::examples::braid(type, d[0], d[1], d[2], mesh); + + add_distance(mesh); +} + +void make_unibuffer(const std::vector &vfA, + const std::vector &vfB, + const std::vector &vfC, + const std::vector &matnos, + conduit::Node &matset) +{ + std::vector material_ids; + std::vector volume_fractions; + std::vector sizes(vfA.size(), 0); + std::vector offsets(vfA.size(), 0); + std::vector indices; + + int offset = 0; + for(size_t i = 0; i < vfA.size(); i++) + { + if(vfA[i] > 0.) + { + indices.push_back(static_cast(indices.size())); + material_ids.push_back(matnos[0]); + volume_fractions.push_back(vfA[i]); + sizes[i]++; + } + if(vfB[i] > 0.) + { + indices.push_back(static_cast(indices.size())); + material_ids.push_back(matnos[1]); + volume_fractions.push_back(vfB[i]); + sizes[i]++; + } + if(vfC[i] > 0.) + { + indices.push_back(static_cast(indices.size())); + material_ids.push_back(matnos[2]); + volume_fractions.push_back(vfC[i]); + sizes[i]++; + } + + offsets[i] = offset; + offset += sizes[i]; + } + + matset["material_ids"].set(material_ids); + matset["volume_fractions"].set(volume_fractions); + matset["sizes"].set(sizes); + matset["offsets"].set(offsets); + matset["indices"].set(indices); +} + +template +void make_matset(const std::string &type, + const std::string &topoName, + const Dimensions &dims, + conduit::Node &mesh) +{ + constexpr int sampling = 10; + int midx = sampling * dims[0] / 2; + int midy = sampling * dims[1] / 2; + + int ksize = 0; + int nelements = 1; + for(int i = 0; i < dims.size(); i++) + { + nelements *= dims[i]; + ksize = (i == 0) ? 1 : (ksize * dims[i]); + } + std::vector matA(nelements); + std::vector matB(nelements); + std::vector matC(nelements); + + int nk = (dims.size() == 3) ? dims[2] : 1; + for(int k = 0; k < nk; k++) + { + for(int j = 0; j < sampling * dims[1]; j++) + { + const int jele = j / sampling; + for(int i = 0; i < sampling * dims[0]; i++) + { + const int iele = i / sampling; + + bool gt1 = j >= midy; + bool gt2 = j >= ((3. / 2.) * (i - midx) + midy); + bool gt3 = j >= ((-2. / 5.) * (i - midx) + midy); + + int index = k * ksize + jele * dims[0] + iele; + + if(gt1 && gt2) + matA[index]++; + else if(!gt1 && !gt3) + matB[index]++; + else + matC[index]++; + } + } + } + + std::vector vfA(nelements); + std::vector vfB(nelements); + std::vector vfC(nelements); + constexpr float s2 = static_cast(sampling * sampling); + for(int k = 0; k < nk; k++) + { + for(int j = 0; j < sampling * dims[1]; j++) + { + const int jele = j / sampling; + for(int i = 0; i < sampling * dims[0]; i++) + { + const int iele = i / sampling; + int index = k * ksize + jele * dims[0] + iele; + + vfA[index] = static_cast(matA[index]) / s2; + vfB[index] = static_cast(matB[index]) / s2; + vfC[index] = static_cast(matC[index]) / s2; + } + } + } + matA.clear(); + matB.clear(); + matC.clear(); + +#if 1 + // Debugging. Add the vfs as fields. + mesh["fields/vfA/topology"] = topoName; + mesh["fields/vfA/association"] = "element"; + mesh["fields/vfA/values"].set(vfA); + + mesh["fields/vfB/topology"] = topoName; + mesh["fields/vfB/association"] = "element"; + mesh["fields/vfB/values"].set(vfB); + + mesh["fields/vfC/topology"] = topoName; + mesh["fields/vfC/association"] = "element"; + mesh["fields/vfC/values"].set(vfC); +#endif + + const std::vector matnos {{22, 66, 33}}; + conduit::Node &matset = mesh["matsets/mat"]; + matset["topology"] = topoName; + matset["material_map/A"] = matnos[0]; + matset["material_map/B"] = matnos[1]; + matset["material_map/C"] = matnos[2]; + + // produce different material types. + if(type == "unibuffer") + { + make_unibuffer(vfA, vfB, vfC, matnos, matset); + } + // TODO: write these other cases. + else if(type == "multibuffer") + { } + else if(type == "element_dominant") + { } + else if(type == "material_dominant") + { } +} + +void mixed3d(conduit::Node &mesh) +{ + // clang-format off + const std::vector conn{{ + // tets + 0,6,1,3, + 3,6,1,9, + 6,7,1,9, + 3,9,1,4, + 9,7,1,4, + 9,7,4,10, + // pyramids + 1,7,8,2,4, + 11,8,7,10,4, + 2,8,11,5,4, + // wedges + 6,7,9,12,13,15, + 9,7,10,15,13,16, + // hex + 7,13,14,8,10,16,17,11 + }}; + const std::vector shapes{{ + 0,0,0,0,0,0, + 1,1,1, + 2,2, + 3 + }}; + const std::vector sizes{{ + 4,4,4,4,4,4, + 5,5,5, + 6,6, + 8 + }}; + const std::vector offsets{{ + 0,4,8,12,16,20, + 24,29,34, + 39,45, + 51 + }}; + constexpr float LOW = -10.f; + constexpr float MID = 0.f; + constexpr float HIGH = 10.f; + const std::vector x{{ + LOW, MID, HIGH, LOW, MID, HIGH, LOW, MID, HIGH, LOW, MID, HIGH, LOW, MID, HIGH, LOW, MID, HIGH + }}; + const std::vector y{{ + LOW, LOW, LOW, MID, MID, MID, LOW, LOW, LOW, MID, MID, MID, LOW, LOW, LOW, MID, MID, MID + }}; + const std::vector z{{ + LOW, LOW, LOW, LOW, LOW, LOW, MID, MID, MID, MID, MID, MID, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH + }}; + // clang-format off + + mesh["coordsets/coords/type"] = "explicit"; + mesh["coordsets/coords/values/x"].set(x); + mesh["coordsets/coords/values/y"].set(y); + mesh["coordsets/coords/values/z"].set(z); + mesh["topologies/mesh/type"] = "unstructured"; + mesh["topologies/mesh/coordset"] = "coords"; + mesh["topologies/mesh/elements/shape"] = "mixed"; + mesh["topologies/mesh/elements/connectivity"].set(conn); + mesh["topologies/mesh/elements/shapes"].set(shapes); + mesh["topologies/mesh/elements/sizes"].set(sizes); + mesh["topologies/mesh/elements/offsets"].set(offsets); + mesh["topologies/mesh/elements/shape_map/tet"] = 0; + mesh["topologies/mesh/elements/shape_map/pyramid"] = 1; + mesh["topologies/mesh/elements/shape_map/wedge"] = 2; + mesh["topologies/mesh/elements/shape_map/hex"] = 3; + + add_distance(mesh, 0.f); +} + +void make_one_hex(conduit::Node &hostMesh) +{ + hostMesh["coordsets/coords/type"] = "explicit"; + hostMesh["coordsets/coords/values/x"].set( + std::vector {{0., 1., 1., 0., 0., 1., 1., 0.}}); + hostMesh["coordsets/coords/values/y"].set( + std::vector {{0., 0., 1., 1., 0., 0., 1., 1.}}); + hostMesh["coordsets/coords/values/z"].set( + std::vector {{0., 0., 0., 0., 1., 1., 1., 1.}}); + hostMesh["topologies/topo/type"] = "unstructured"; + hostMesh["topologies/topo/coordset"] = "coords"; + hostMesh["topologies/topo/elements/shape"] = "hex"; + hostMesh["topologies/topo/elements/connectivity"].set( + std::vector {{0, 1, 2, 3, 4, 5, 6, 7}}); + hostMesh["topologies/topo/elements/sizes"].set(std::vector {8}); + hostMesh["topologies/topo/elements/offsets"].set(std::vector {0}); + hostMesh["fields/distance/topology"] = "topo"; + hostMesh["fields/distance/association"] = "vertex"; + hostMesh["fields/distance/values"].set( + std::vector {{1., -1., -1., -1., -1., -1., -1., -1.}}); +} + +void make_one_tet(conduit::Node &hostMesh) +{ + hostMesh["coordsets/coords/type"] = "explicit"; + hostMesh["coordsets/coords/values/x"].set(std::vector {{0., 0., 1., 0.}}); + hostMesh["coordsets/coords/values/y"].set(std::vector {{0., 0., 0., 1.}}); + hostMesh["coordsets/coords/values/z"].set(std::vector {{0., 1., 0., 0.}}); + hostMesh["topologies/topo/type"] = "unstructured"; + hostMesh["topologies/topo/coordset"] = "coords"; + hostMesh["topologies/topo/elements/shape"] = "tet"; + hostMesh["topologies/topo/elements/connectivity"].set( + std::vector {{0, 1, 2, 3}}); + hostMesh["topologies/topo/elements/sizes"].set(std::vector {4}); + hostMesh["topologies/topo/elements/offsets"].set(std::vector {0}); + hostMesh["fields/distance/topology"] = "topo"; + hostMesh["fields/distance/association"] = "vertex"; + hostMesh["fields/distance/values"].set(std::vector {{-1., -1., -1., 1.}}); +} + +void make_one_pyr(conduit::Node &hostMesh) +{ + hostMesh["coordsets/coords/type"] = "explicit"; + hostMesh["coordsets/coords/values/x"].set( + std::vector {{0., 0., 1., 1., 0.5}}); + hostMesh["coordsets/coords/values/y"].set( + std::vector {{0., 0., 0., 0., 1.}}); + hostMesh["coordsets/coords/values/z"].set( + std::vector {{0., 1., 1., 0., 0.5}}); + hostMesh["topologies/topo/type"] = "unstructured"; + hostMesh["topologies/topo/coordset"] = "coords"; + hostMesh["topologies/topo/elements/shape"] = "pyramid"; + hostMesh["topologies/topo/elements/connectivity"].set( + std::vector {{0, 1, 2, 3, 4}}); + hostMesh["topologies/topo/elements/sizes"].set(std::vector {5}); + hostMesh["topologies/topo/elements/offsets"].set(std::vector {0}); + hostMesh["fields/distance/topology"] = "topo"; + hostMesh["fields/distance/association"] = "vertex"; + hostMesh["fields/distance/values"].set( + std::vector {{1., 1., -1., -1., -1.}}); +} + +void make_one_wdg(conduit::Node &hostMesh) +{ + hostMesh["coordsets/coords/type"] = "explicit"; + hostMesh["coordsets/coords/values/x"].set( + std::vector {{0., 0., 1., 0., 0., 1}}); + hostMesh["coordsets/coords/values/y"].set( + std::vector {{0., 0., 0., 1., 1., 1.}}); + hostMesh["coordsets/coords/values/z"].set( + std::vector {{0., 1., 0., 0., 1., 0.}}); + hostMesh["topologies/topo/type"] = "unstructured"; + hostMesh["topologies/topo/coordset"] = "coords"; + hostMesh["topologies/topo/elements/shape"] = "wedge"; + hostMesh["topologies/topo/elements/connectivity"].set( + std::vector {{0, 1, 2, 3, 4, 5}}); + hostMesh["topologies/topo/elements/sizes"].set(std::vector {6}); + hostMesh["topologies/topo/elements/offsets"].set(std::vector {0}); + hostMesh["fields/distance/topology"] = "topo"; + hostMesh["fields/distance/association"] = "vertex"; + hostMesh["fields/distance/values"].set( + std::vector {{1., 1., -1., -1., -1., -1.}}); +} + +template +void strided_structured(conduit::Node &hostMesh) +{ + // Total padding in each dimension + const conduit::index_t total_elt_pad = 4; // two on each end + const conduit::index_t total_pt_pad = 3; // two on the low end, one on the high end + + // Size of the window in the larger mesh + const conduit::index_t npts_x = 4; + const conduit::index_t npts_y = 3; + const conduit::index_t npts_z = (NDIMS == 3) ? 3 : 0; + const conduit::index_t spz = (NDIMS == 3) ? (npts_z + total_pt_pad) : 0; + + const conduit::index_t nelts_x = npts_x - 1; + const conduit::index_t nelts_y = npts_y - 1; + const conduit::index_t nelts_z = (NDIMS == 3) ? (npts_z - 1) : 0; + const conduit::index_t sez = (NDIMS == 3) ? (nelts_z + total_elt_pad) : 0; + + // Origin: where the data starts in the arrays + const conduit::index_t origin_x = 2; + const conduit::index_t origin_y = 2; + const conduit::index_t origin_z = (NDIMS == 3) ? 2 : 0; + + conduit::Node desc; + desc["vertex_data/shape"].set(std::vector{{npts_x + total_pt_pad, npts_y + total_pt_pad, spz}}); + desc["vertex_data/origin"].set(std::vector{{origin_x, origin_y, origin_z}}); + desc["element_data/shape"].set(std::vector{{nelts_x + total_elt_pad, nelts_y + total_elt_pad, sez}}); + desc["element_data/origin"].set(std::vector{{origin_x, origin_y, origin_z}}); + conduit::blueprint::mesh::examples::strided_structured(desc, npts_x, npts_y, npts_z, hostMesh); +} + +} // end namespace data +} // end namespace testing +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/tests/mir_testing_helpers.hpp b/src/axom/mir/tests/mir_testing_helpers.hpp new file mode 100644 index 0000000000..b05a4aaaef --- /dev/null +++ b/src/axom/mir/tests/mir_testing_helpers.hpp @@ -0,0 +1,285 @@ +// 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) +#ifndef AXOM_MIR_TESTING_HELPERS_HPP_ +#define AXOM_MIR_TESTING_HELPERS_HPP_ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------------ +// clang-format off +using seq_exec = axom::SEQ_EXEC; + +#if defined(AXOM_USE_OPENMP) && defined(AXOM_USE_RAJA) + using omp_exec = axom::OMP_EXEC; +#else + using omp_exec = seq_exec; +#endif + +#if defined (AXOM_USE_RAJA) && defined (AXOM_USE_UMPIRE) + #if defined(AXOM_USE_CUDA) + constexpr int CUDA_BLOCK_SIZE = 256; + using cuda_exec = axom::CUDA_EXEC; + #else + using cuda_exec = seq_exec; + #endif + + #if defined(AXOM_USE_HIP) + constexpr int HIP_BLOCK_SIZE = 64; + using hip_exec = axom::HIP_EXEC; + #else + using hip_exec = seq_exec; + #endif +#endif +// clang-format on + +//------------------------------------------------------------------------------ +// Define better names for the execution spaces. This header needs to be included +// after the ExecSpace types are defined. +template +struct execution_name +{ + static std::string name() { return "seq"; } +}; + +#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #if defined(AXOM_USE_OPENMP) +template <> +struct execution_name +{ + static std::string name() { return "omp"; } +}; + #endif + #if defined(AXOM_USE_CUDA) +template <> +struct execution_name +{ + static std::string name() { return "cuda"; } +}; + #endif + #if defined(AXOM_USE_HIP) +template <> +struct execution_name +{ + static std::string name() { return "hip"; } +}; + #endif +#endif + +//------------------------------------------------------------------------------ +std::string pjoin(const std::string &path, const std::string &filename) +{ + return axom::utilities::filesystem::joinPath(path, filename); +} + +void psplit(const std::string &filepath, std::string &path, std::string &filename) +{ + axom::Path p(filepath); + path = p.dirName(); + filename = p.baseName(); +} + +std::string dataDirectory() { return AXOM_DATA_DIR; } + +std::string testData(const std::string &filename) +{ + return pjoin(dataDirectory(), filename); +} + +std::string baselineDirectory(); + +std::string yamlRoot(const std::string &filepath) +{ + std::string retval, path, filename; + psplit(filepath, path, filename); + auto idx = filename.rfind("."); + if(idx != std::string::npos) + { + retval = filename.substr(0, idx); + } + else + { + retval = filename; + } + return retval; +} + +void printNode(const conduit::Node &n) +{ + conduit::Node options; + options["num_children_threshold"] = 10000; + options["num_elements_threshold"] = 10000; + n.to_summary_string_stream(std::cout, options); +} + +bool compareConduit(const conduit::Node &n1, + const conduit::Node &n2, + double tolerance, + conduit::Node &info) +{ + bool same = true; + if(n1.dtype().id() == n2.dtype().id() && n1.dtype().is_floating_point()) + { + const auto a1 = n1.as_double_accessor(); + const auto a2 = n2.as_double_accessor(); + double maxdiff = 0.; + for(int i = 0; i < a1.number_of_elements() && same; i++) + { + double diff = fabs(a1[i] - a2[i]); + maxdiff = std::max(diff, maxdiff); + same &= diff <= tolerance; + if(!same) + { + info.append().set( + axom::fmt::format("\"{}\" fields differ at index {}.", n1.name(), i)); + } + } + info["maxdiff"][n1.name()] = maxdiff; + } + else + { + for(int i = 0; i < n1.number_of_children() && same; i++) + { + const auto &n1c = n1.child(i); + const auto &n2c = n2.fetch_existing(n1c.name()); + same &= compareConduit(n1c, n2c, tolerance, info); + } + } + return same; +} + +void saveBaseline(const std::string &filename, const conduit::Node &n) +{ + std::string file_with_ext(filename + ".yaml"); + try + { + SLIC_INFO(axom::fmt::format("Save baseline {}", file_with_ext)); + conduit::relay::io::save(n, file_with_ext, "yaml"); + +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + SLIC_INFO(axom::fmt::format("Save visualization files...")); + conduit::relay::io::blueprint::save_mesh(n, filename + "_hdf5", "hdf5"); + axom::mir::utilities::blueprint::save_vtk(n, filename + "_vtk.vtk"); +#endif + } + catch(...) + { + SLIC_INFO(axom::fmt::format("Could not save baseline to {}!", file_with_ext)); + + printNode(n); + + // Check the data for errors. + conduit::Node info; + if(!conduit::blueprint::mesh::verify(n, info)) + { + printNode(info); + } + } +} + +void saveBaseline(const std::vector &baselinePaths, + const std::string &baselineName, + const conduit::Node &n) +{ + for(const auto &path : baselinePaths) + { + axom::utilities::filesystem::makeDirsForPath(path); + std::string filename(pjoin(path, baselineName)); + saveBaseline(filename, n); + } +} + +bool loadBaseline(const std::string &filename, conduit::Node &n) +{ + bool loaded = false; + std::string file_with_ext(filename + ".yaml"); + //SLIC_INFO(axom::fmt::format("Load baseline {}", file_with_ext)); + if(axom::utilities::filesystem::pathExists(file_with_ext)) + { + conduit::relay::io::load(file_with_ext, "yaml", n); + loaded = true; + } + return loaded; +} + +template +std::vector baselinePaths() +{ + std::vector paths; + paths.push_back(pjoin(baselineDirectory(), execution_name::name())); + paths.push_back(baselineDirectory()); + return paths; +} + +bool compareBaseline(const std::vector &baselinePaths, + const std::string &baselineName, + const conduit::Node ¤t, + double tolerance = 1.e-6) +{ + bool success = false; + int count = 0; + for(const auto &path : baselinePaths) + { + try + { + // Load the baseline file. + conduit::Node info, baselineNode; + std::string filename(pjoin(path, baselineName)); + if(loadBaseline(filename, baselineNode)) + { + // Compare the baseline to the current DC. + SLIC_INFO(axom::fmt::format("Comparing to baseline {}", filename)); + success = compareConduit(baselineNode, current, tolerance, info); + count++; + if(!success) + { + info.print(); + + std::string errFile(filename + "_err"); + conduit::relay::io::blueprint::save_mesh(current, errFile, "hdf5"); + conduit::relay::io::blueprint::save_mesh(current, + errFile + "_yaml", + "yaml"); + } + // We found a baseline so we can exit + break; + } + } + catch(...) + { + SLIC_INFO( + axom::fmt::format("Could not load {} from {}!", baselineName, path)); + } + } + if(!success && count == 0) + { + SLIC_INFO(axom::fmt::format("No baselines found for {}", baselineName)); + } + return success; +} + +//------------------------------------------------------------------------------ +template +bool compare_views(const Container1 &a, const Container2 &b) +{ + bool eq = a.size() == b.size(); + for(axom::IndexType i = 0; i < a.size() && eq; i++) + { + eq &= a[i] == b[i]; + } + if(!eq) + { + axom::fmt::format("a={{{}}}\nb={{{}}}", + axom::fmt::join(a, ","), + axom::fmt::join(b, ",")); + } + return eq; +} +#endif diff --git a/src/axom/mir/tests/mir_views.cpp b/src/axom/mir/tests/mir_views.cpp new file mode 100644 index 0000000000..1d619b81e0 --- /dev/null +++ b/src/axom/mir/tests/mir_views.cpp @@ -0,0 +1,516 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/mir.hpp" +#include "axom/primal.hpp" +#include "axom/mir/tests/mir_testing_data_helpers.hpp" + +#include + +namespace bputils = axom::mir::utilities::blueprint; + +//------------------------------------------------------------------------------ + +//#define DEBUGGING_TEST_CASES + +// Uncomment to generate baselines +//#define AXOM_TESTING_GENERATE_BASELINES + +// Uncomment to save visualization files for debugging (when making baselines) +//#define AXOM_TESTING_SAVE_VISUALIZATION + +#include "axom/mir/tests/mir_testing_helpers.hpp" + +std::string baselineDirectory() +{ + return pjoin(pjoin(pjoin(dataDirectory(), "mir"), "regression"), "mir_views"); +} +//------------------------------------------------------------------------------ + +TEST(mir_views, shape2conduitName) +{ + EXPECT_EQ(axom::mir::views::LineShape::name(), "line"); + EXPECT_EQ(axom::mir::views::LineShape::name(), "line"); + + EXPECT_EQ(axom::mir::views::TriShape::name(), "tri"); + EXPECT_EQ(axom::mir::views::TriShape::name(), "tri"); + + EXPECT_EQ(axom::mir::views::QuadShape::name(), "quad"); + EXPECT_EQ(axom::mir::views::QuadShape::name(), "quad"); + + EXPECT_EQ(axom::mir::views::TetShape::name(), "tet"); + EXPECT_EQ(axom::mir::views::TetShape::name(), "tet"); + + EXPECT_EQ(axom::mir::views::PyramidShape::name(), "pyramid"); + EXPECT_EQ(axom::mir::views::PyramidShape::name(), "pyramid"); + + EXPECT_EQ(axom::mir::views::WedgeShape::name(), "wedge"); + EXPECT_EQ(axom::mir::views::WedgeShape::name(), "wedge"); + + EXPECT_EQ(axom::mir::views::HexShape::name(), "hex"); + EXPECT_EQ(axom::mir::views::HexShape::name(), "hex"); +} + +//------------------------------------------------------------------------------ +template +struct test_node_to_arrayview +{ + static int constexpr sum(int n) + { + int s = 0; + for(int i = 0; i < n; i++) s += i; + return s; + } + + static void test() + { + std::vector dtypes {conduit::DataType::INT8_ID, + conduit::DataType::INT16_ID, + conduit::DataType::INT32_ID, + conduit::DataType::INT64_ID, + conduit::DataType::UINT8_ID, + conduit::DataType::UINT16_ID, + conduit::DataType::UINT32_ID, + conduit::DataType::UINT64_ID, + conduit::DataType::FLOAT32_ID, + conduit::DataType::FLOAT64_ID}; + constexpr int n = 16; + axom::mir::utilities::blueprint::ConduitAllocateThroughAxom c2a; + for(int dtype : dtypes) + { + // Make a node and fill it with data. + conduit::Node n_data; + n_data.set_allocator(c2a.getConduitAllocatorID()); + n_data.set(conduit::DataType(dtype, n)); + + int sumValues = 0; + axom::mir::views::Node_to_ArrayView(n_data, [&](auto dataView) { + sumValues = testBody(dataView, n); + }); + + EXPECT_EQ(sumValues, sum(n)); + } + } + + template + static int testBody(DataView dataView, int n) + { + using reduce_policy = + typename axom::execution_space::reduce_policy; + using value_type = typename DataView::value_type; + + std::cout << axom::mir::views::array_view_traits::name() + << std::endl; + + // Make sure we can store values in dataView + axom::for_all( + n, + AXOM_LAMBDA(axom::IndexType index) { + dataView[index] = static_cast(index); + }); + + // Read the values and sum them. + RAJA::ReduceSum sumValues_reduce(0); + axom::for_all( + n, + AXOM_LAMBDA(axom::IndexType index) { sumValues_reduce += dataView[index]; }); + return static_cast(sumValues_reduce.get()); + } +}; + +TEST(mir_views, node_to_arrayview_seq) +{ + test_node_to_arrayview::test(); +} +#if defined(AXOM_USE_OPENMP) +TEST(mir_views, node_to_arrayview_omp) +{ + test_node_to_arrayview::test(); +} +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_views, node_to_arrayview_cuda) +{ + test_node_to_arrayview::test(); +} +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_views, node_to_arrayview_hip) +{ + test_node_to_arrayview::test(); +} +#endif + +//------------------------------------------------------------------------------ +TEST(mir_views, explicit_coordsetview) +{ + axom::Array x {{0., 1., 2., 3., 4., 5.}}; + axom::Array y {{10., 11., 12., 13., 14., 15.}}; + axom::Array z {{20., 21., 22., 23., 24., 25.}}; + + axom::mir::views::ExplicitCoordsetView view2d(x.view(), y.view()); + EXPECT_EQ(view2d.size(), 6); + for(axom::IndexType i = 0; i < view2d.size(); i++) + { + axom::primal::Point P({x[i], y[i]}); + EXPECT_EQ(view2d.getPoint(i), P); + EXPECT_EQ(view2d[i], P); + } + + axom::mir::views::ExplicitCoordsetView view3d(x.view(), + y.view(), + z.view()); + EXPECT_EQ(view3d.size(), 6); + for(axom::IndexType i = 0; i < view3d.size(); i++) + { + axom::primal::Point P({x[i], y[i], z[i]}); + EXPECT_EQ(view3d.getPoint(i), P); + EXPECT_EQ(view3d[i], P); + } +} + +//------------------------------------------------------------------------------ +template +struct test_structured_topology_view_rectilinear +{ + static void test() + { + conduit::Node hostMesh; + create(hostMesh); + + // host->device + conduit::Node deviceMesh; + bputils::copy(deviceMesh, hostMesh); + + // Make results view on device. + constexpr int nzones = 9; + axom::Array results( + nzones, + nzones, + axom::execution_space::allocatorID()); + auto resultsView = results.view(); + + // Execute the kernel for each zone (find max node number in zone). + auto topoView = axom::mir::views::make_rectilinear<2>::view( + deviceMesh["topologies/mesh"]); + axom::for_all( + topoView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zone = topoView.zone(zoneIndex); + axom::IndexType m = -1; + for(const auto &id : zone.getIds()) + { + m = axom::utilities::max(static_cast(id), m); + } + resultsView[zoneIndex] = m; + }); + + // device->host + axom::Array hostResults( + nzones, + nzones, + axom::execution_space::allocatorID()); + axom::copy(hostResults.data(), + results.data(), + nzones * sizeof(axom::IndexType)); + + // Compare. + const axom::IndexType expected[] = {5, 6, 7, 9, 10, 11, 13, 14, 15}; + for(int i = 0; i < nzones; i++) + { + EXPECT_EQ(hostResults[i], expected[i]); + } + } + + static void create(conduit::Node &mesh) + { + std::vector dims {4, 4}; + axom::mir::testing::data::braid("rectilinear", dims, mesh); + } +}; + +TEST(mir_views, stopo_rectilinear_2d_seq) +{ + test_structured_topology_view_rectilinear::test(); +} +#if defined(AXOM_USE_OPENMP) +TEST(mir_views, stopo_rectilinear_2d_omp) +{ + test_structured_topology_view_rectilinear::test(); +} +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_views, stopo_rectilinear_2d_cuda) +{ + test_structured_topology_view_rectilinear::test(); +} +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_views, stopo_rectilinear_2d_hip) +{ + test_structured_topology_view_rectilinear::test(); +} +#endif + +//------------------------------------------------------------------------------ +struct test_strided_structured +{ + static void test() + { + conduit::Node hostMesh; + axom::mir::testing::data::strided_structured<2>(hostMesh); + // hostMesh.print(); + + axom::mir::views::dispatch_explicit_coordset( + hostMesh["coordsets/coords"], + [&](auto coordsetView) { + axom::mir::views::dispatch_structured_topology< + axom::mir::views::select_dimensions(2)>( + hostMesh["topologies/mesh"], + [&](const std::string &AXOM_UNUSED_PARAM(shape), auto topoView) { + execute(coordsetView, topoView); + }); + }); + } + + template + static void execute(CoordsetView coordsetView, TopologyView topoView) + { + using ExecSpace = seq_exec; + + // These are the expected node ids for this strided structured mesh. + // clang-format off + const axom::Array expectedNodes {{16, 17, 24, 23, + 17, 18, 25, 24, + 18, 19, 26, 25, + 23, 24, 31, 30, + 24, 25, 32, 31, + 25, 26, 33, 32}}; + // clang-format on + auto expectedNodesView = expectedNodes.view(); + axom::IndexType n4 = expectedNodesView.size(); + + const int allocatorID = axom::execution_space::allocatorID(); + axom::Array actualNodes(n4, n4, allocatorID); + axom::Array logicalNodes(n4 * 2, n4 * 2, allocatorID); + auto actualNodesView = actualNodes.view(); + auto logicalNodesView = logicalNodes.view(); + + // Traverse the zones in the mesh and gather node ids + axom::for_all( + topoView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zone = topoView.zone(zoneIndex); + const auto nodeIndexing = topoView.indexing().expand(); + + // Get node ids for zone. + const auto ids = zone.getIds(); + for(axom::IndexType i = 0; i < ids.size(); i++) + { + actualNodesView[zoneIndex * 4 + i] = ids[i]; + + // Get the logical local id for the id. + const auto index = nodeIndexing.GlobalToLocal(ids[i]); + const auto logical = nodeIndexing.IndexToLogicalIndex(index); + logicalNodesView[(zoneIndex * 4 + i) * 2 + 0] = logical[0]; + logicalNodesView[(zoneIndex * 4 + i) * 2 + 1] = logical[1]; + } + }); + + for(axom::IndexType i = 0; i < n4; i++) + { + EXPECT_EQ(expectedNodesView[i], actualNodesView[i]); + } + + // Check coordinates + for(axom::IndexType i = 0; i < n4; i++) + { + const auto id = actualNodesView[i]; + + // Get coordinate from coordsetView. + const auto pt = coordsetView[id]; + + // Get the logical local id for the id. + const auto logicalI = logicalNodesView[i * 2 + 0]; + const auto logicalJ = logicalNodesView[i * 2 + 1]; + + // Expected coordinate + double x = (3. + 1. / 3.) * static_cast(logicalI - 1); + const double yvals[] = {-2, 2, 6}; + double y = yvals[logicalJ]; + + const double dx = pt[0] - x; + const double dy = pt[1] - y; + double d = sqrt(dx * dx + dy * dy); + + EXPECT_TRUE(d < 1.e-10); + } + } +}; + +TEST(mir_views, strided_structured_seq) { test_strided_structured::test(); } + +//------------------------------------------------------------------------------ +template +struct test_braid2d_mat +{ + static void test(const std::string &type, + const std::string &mattype, +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + const std::string &name +#else + const std::string &AXOM_UNUSED_PARAM(name) +#endif + ) + { + namespace bputils = axom::mir::utilities::blueprint; + const int allocatorID = axom::execution_space::allocatorID(); + + axom::StackArray dims {10, 10}; + axom::StackArray zoneDims {dims[0] - 1, dims[1] - 1}; + + // Create the data + conduit::Node hostMesh, deviceMesh; + axom::mir::testing::data::braid(type, dims, hostMesh); + axom::mir::testing::data::make_matset(mattype, "mesh", zoneDims, hostMesh); + axom::mir::utilities::blueprint::copy(deviceMesh, hostMesh); +#if defined(AXOM_TESTING_SAVE_VISUALIZATION) + conduit::relay::io::blueprint::save_mesh(hostMesh, name + "_orig", "hdf5"); +#endif + + if(type == "unibuffer") + { + // clang-format off + // _mir_views_matsetview_begin + using MatsetView = axom::mir::views::UnibufferMaterialView; + MatsetView matsetView; + matsetView.set(bputils::make_array_view(deviceMesh["matsets/mat/material_ids"]), + bputils::make_array_view(deviceMesh["matsets/mat/volume_fractions"]), + bputils::make_array_view(deviceMesh["matsets/mat/sizes"]), + bputils::make_array_view(deviceMesh["matsets/mat/offsets"]), + bputils::make_array_view(deviceMesh["matsets/mat/indices"])); + // _mir_views_matsetview_end + // clang-format on + EXPECT_EQ(matsetView.numberOfZones(), zoneDims[0] * zoneDims[1]); + test_matsetview(matsetView, allocatorID); + } + } + + template + static void test_matsetview(MatsetView matsetView, int allocatorID) + { + constexpr int MATA = 0; + constexpr int MATB = 1; + constexpr int MATC = 2; + const int zoneids[] = {0, 36, 40}; + + // clang-format off + const int results[] = {/*contains mat*/ 0, 1, 0, /*mats in zone*/ 1, /*ids.size*/ 1, /*mats in zone*/ MATB, -1, -1, + /*contains mat*/ 1, 1, 0, /*mats in zone*/ 2, /*ids.size*/ 2, /*mats in zone*/ MATA, MATB, -1, + /*contains mat*/ 1, 1, 1, /*mats in zone*/ 3, /*ids.size*/ 3, /*mats in zone*/ MATA, MATB, MATC}; + // clang-format on + constexpr int nZones = sizeof(zoneids) / sizeof(int); + + // Get zoneids into zoneidsView for device. + axom::Array zoneidsArray(nZones, nZones, allocatorID); + axom::copy(zoneidsArray.data(), zoneids, sizeof(int) * nZones); + auto zoneidsView = zoneidsArray.view(); + + // Allocate results array on device. + constexpr int nResults = sizeof(results) / sizeof(int); + axom::Array resultsArrayDevice(nResults, nResults, allocatorID); + auto resultsView = resultsArrayDevice.view(); + + // Fill in resultsView on the device. + constexpr int nResultsPerZone = nResults / nZones; + axom::for_all( + 3, + AXOM_LAMBDA(axom::IndexType index) { + resultsView[nResultsPerZone * index + 0] = + matsetView.zoneContainsMaterial(zoneidsView[index], MATA) ? 1 : 0; + resultsView[nResultsPerZone * index + 1] = + matsetView.zoneContainsMaterial(zoneidsView[index], MATB) ? 1 : 0; + resultsView[nResultsPerZone * index + 2] = + matsetView.zoneContainsMaterial(zoneidsView[index], MATC) ? 1 : 0; + resultsView[nResultsPerZone * index + 3] = + matsetView.numberOfMaterials(zoneidsView[index]); + + typename MatsetView::IDList ids {}; + typename MatsetView::VFList vfs {}; + matsetView.zoneMaterials(zoneidsView[index], ids, vfs); + resultsView[nResultsPerZone * index + 4] = ids.size(); + for(axom::IndexType i = 0; i < 3; i++) + { + resultsView[nResultsPerZone * index + 5 + i] = + (i < ids.size()) ? ids[i] : -1; + } + }); + // Get containsView data to the host and compare results + std::vector resultsHost(nResults); + axom::copy(resultsHost.data(), resultsView.data(), sizeof(int) * nResults); + for(int i = 0; i < nResults; i++) + { + EXPECT_EQ(results[i], resultsHost[i]); + } + } +}; + +TEST(mir_views, matset_unibuffer_seq) +{ + test_braid2d_mat::test("uniform", + "unibuffer", + "uniform2d_unibuffer"); +} +#if defined(AXOM_USE_OPENMP) +TEST(mir_views, matset_unibuffer_omp) +{ + test_braid2d_mat::test("uniform", + "unibuffer", + "uniform2d_unibuffer"); +} +#endif +#if defined(AXOM_USE_CUDA) +TEST(mir_views, matset_unibuffer_cuda) +{ + test_braid2d_mat::test("uniform", + "unibuffer", + "uniform2d_unibuffer"); +} +#endif +#if defined(AXOM_USE_HIP) +TEST(mir_views, matset_unibuffer_hip) +{ + test_braid2d_mat::test("uniform", + "unibuffer", + "uniform2d_unibuffer"); +} +#endif + +//------------------------------------------------------------------------------ +#if defined(DEBUGGING_TEST_CASES) +void conduit_debug_err_handler(const std::string &s1, const std::string &s2, int i1) +{ + std::cout << "s1=" << s1 << ", s2=" << s2 << ", i1=" << i1 << std::endl; + // This is on purpose. + while(1) + ; +} +#endif +//------------------------------------------------------------------------------ +int main(int argc, char *argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; // create & initialize test logger, +#if defined(DEBUGGING_TEST_CASES) + conduit::utils::set_error_handler(conduit_debug_err_handler); +#endif + result = RUN_ALL_TESTS(); + return result; +} diff --git a/src/axom/mir/tests/mir_views_indexing.cpp b/src/axom/mir/tests/mir_views_indexing.cpp new file mode 100644 index 0000000000..6165aa8735 --- /dev/null +++ b/src/axom/mir/tests/mir_views_indexing.cpp @@ -0,0 +1,151 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" +#include "axom/mir.hpp" +#include "axom/mir/views/StridedStructuredIndexing.hpp" + +//---------------------------------------------------------------------- +TEST(mir_views_indexing, strided_structured_indexing_2d) +{ + /* + + x---x---x---x---x---x---x + | | | | | | | + x---x---*---*---*---*---x *=real node, x=ignored node, O=origin node 16 + | | | | | | | + x---x---*---*---*---*---x + | | | | | | | + x---x---O---*---*---*---x + | | | | | | | + x---x---x---x---x---x---x + | | | | | | | + x---x---x---x---x---x---x + + */ + using Indexing = axom::mir::views::StridedStructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + LogicalIndex dims {4, 3}; // window size in 4*3 elements in 7,6 overall + LogicalIndex origin {2, 2}; + LogicalIndex stride {1, 7}; + Indexing indexing(dims, origin, stride); + + EXPECT_EQ(indexing.dimension(), 2); + EXPECT_EQ(indexing.size(), dims[0] * dims[1]); + + // Iterate over local + for(int j = 0; j < dims[1]; j++) + { + for(int i = 0; i < dims[0]; i++) + { + LogicalIndex logical {i, j}; + const auto flat = indexing.LogicalIndexToIndex(logical); + const auto logical2 = indexing.IndexToLogicalIndex(flat); + EXPECT_EQ(logical, logical2); + + EXPECT_EQ(logical, indexing.GlobalToLocal(indexing.LocalToGlobal(logical))); + EXPECT_EQ(flat, indexing.GlobalToLocal(indexing.LocalToGlobal(flat))); + } + } + + // Iterate over global + int index = 0; + for(int j = 0; j < dims[1] + origin[1]; j++) + { + for(int i = 0; i < stride[1]; i++, index++) + { + LogicalIndex logical {i, j}; + const auto flat = indexing.GlobalToGlobal(logical); + + // flat should start at 0 and increase + EXPECT_EQ(flat, index); + + // Global flat back to logical. + LogicalIndex logical2 = indexing.GlobalToGlobal(flat); + EXPECT_EQ(logical, logical2); + + // If we're in a valid region for the local window, try some other things. + if(i >= origin[0] && i < origin[0] + dims[0] && j >= origin[1] && + j < origin[1] + dims[1]) + { + const auto logicalLocal = indexing.GlobalToLocal(logical); + const auto flatLocal = indexing.GlobalToLocal(flat); + + // Flat local back to flat global + EXPECT_EQ(flat, indexing.LocalToGlobal(flatLocal)); + + // Logical local back to logical global + EXPECT_EQ(logical, indexing.LocalToGlobal(logicalLocal)); + } + } + } + + // Check whether these local points exist. + EXPECT_TRUE(indexing.contains(LogicalIndex {0, 0})); + EXPECT_TRUE(indexing.contains(LogicalIndex {dims[0] - 1, dims[1] - 1})); + EXPECT_FALSE(indexing.contains(LogicalIndex {4, 0})); + EXPECT_FALSE(indexing.contains(LogicalIndex {4, 3})); +} + +//---------------------------------------------------------------------- +TEST(mir_views_indexing, strided_structured_indexing_3d) +{ + using Indexing3D = axom::mir::views::StridedStructuredIndexing; + using LogicalIndex = + typename axom::mir::views::StridedStructuredIndexing::LogicalIndex; + LogicalIndex dims {4, 3, 3}; // window size in 4*3*3 elements in 6*5*5 overall + LogicalIndex origin {2, 2, 2}; + LogicalIndex stride {1, 6, 30}; + Indexing3D indexing(dims, origin, stride); + + EXPECT_EQ(indexing.dimension(), 3); + EXPECT_EQ(indexing.size(), dims[0] * dims[1] * dims[2]); + + const LogicalIndex logical0_0_0 {0, 0, 0}; + const auto index0_0_0 = indexing.LogicalIndexToIndex(logical0_0_0); + EXPECT_EQ(index0_0_0, 0); + + const LogicalIndex logical2_2_2 {2, 2, 2}; + const auto index2_2_2 = indexing.LogicalIndexToIndex(logical2_2_2); + EXPECT_EQ(index2_2_2, 2 + 2 * dims[0] + 2 * dims[0] * dims[1]); + + LogicalIndex logical = indexing.IndexToLogicalIndex(index2_2_2); + EXPECT_TRUE(logical == logical2_2_2); + + for(int k = 0; k < dims[2]; k++) + { + for(int j = 0; j < dims[1]; j++) + { + for(int i = 0; i < dims[0]; i++) + { + LogicalIndex logical {i, j, k}; + const auto flat = indexing.LogicalIndexToIndex(logical); + const auto logical2 = indexing.IndexToLogicalIndex(flat); + EXPECT_EQ(logical, logical2); + } + } + } + + EXPECT_TRUE(indexing.contains(logical0_0_0)); + EXPECT_TRUE( + indexing.contains(LogicalIndex {dims[0] - 1, dims[1] - 1, dims[2] - 1})); + EXPECT_FALSE(indexing.contains(LogicalIndex {4, 0, 0})); + EXPECT_FALSE(indexing.contains(LogicalIndex {4, 3, 0})); +} + +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} diff --git a/src/axom/mir/utilities/CoordsetBlender.hpp b/src/axom/mir/utilities/CoordsetBlender.hpp new file mode 100644 index 0000000000..fc513f4b58 --- /dev/null +++ b/src/axom/mir/utilities/CoordsetBlender.hpp @@ -0,0 +1,168 @@ +// 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) +#ifndef AXOM_MIR_COORDSET_BLENDER_HPP_ +#define AXOM_MIR_COORDSET_BLENDER_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/utilities/FieldBlender.hpp" +#include "axom/mir/utilities/blueprint_utilities.hpp" +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Vector.hpp" +#include "axom/slic.hpp" + +#include +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/*! + * \accelerated + * \class FieldBlender + * + * \tparam ExecSpace The execution space for the algorithm. + * \tparam CSVType The coordset view type. + * \tparam SelectionPolicy The selection policy to use. + * + * \brief This class uses BlendData to generate a new blended field from an input coordset. + * The output coordset will be explicit. + */ +template +class CoordsetBlender +{ +public: + using CoordsetViewType = CSVType; + + /*! + * \brief Create a new blended field from the \a n_input field and place it in \a n_output. + * + * \param blend The BlendData that will be used to make the new coordset. + * \param n_input The input coordset that we're blending. + * \param n_output The output node that will contain the new coordset. + * + * \note The coordset view must agree with the coordset in n_input. We pass both + * a view and the coordset node since the view may not be able to contain + * come coordset metadata and remain trivially copyable. + */ + void execute(const BlendData &blend, + const CoordsetViewType &view, + const conduit::Node &n_input, + conduit::Node &n_output) const + { + using value_type = typename CoordsetViewType::value_type; + using PointType = typename CoordsetViewType::PointType; + using VectorType = axom::primal::Vector; + namespace bputils = axom::mir::utilities::blueprint; + + // Get the axis names for the output coordset. For uniform, prefer x,y,z + // instead of i,j,k since we're making an explicit coordset. + std::vector axes; + if(n_input["type"].as_string() == "uniform") + { + if(n_input.has_path("dims/i")) axes.push_back("x"); + if(n_input.has_path("dims/j")) axes.push_back("y"); + if(n_input.has_path("dims/k")) axes.push_back("z"); + } + else + { + axes = conduit::blueprint::mesh::utils::coordset::axes(n_input); + } + + const auto nComponents = axes.size(); + SLIC_ASSERT(PointType::DIMENSION == nComponents); + + // Get the ID of a Conduit allocator that will allocate through Axom with device allocator allocatorID. + utilities::blueprint::ConduitAllocateThroughAxom c2a; + + n_output.reset(); + n_output["type"] = "explicit"; + conduit::Node &n_values = n_output["values"]; + + // Determine output size. + const auto origSize = blend.m_originalIdsView.size(); + const auto blendSize = SelectionPolicy::size(blend); + const auto outputSize = origSize + blendSize; + + // Make output nodes using axis names from the input coordset. Make array views too. + axom::StackArray, PointType::DIMENSION> compViews; + for(size_t i = 0; i < nComponents; i++) + { + // Allocate data in the Conduit node and make a view. + conduit::Node &comp = n_values[axes[i]]; + comp.set_allocator(c2a.getConduitAllocatorID()); + comp.set(conduit::DataType( + axom::mir::utilities::blueprint::cpp2conduit::id, + outputSize)); + compViews[i] = bputils::make_array_view(comp); + } + + const CoordsetViewType deviceView(view); + const BlendData deviceBlend(blend); + + // Copy over some original values to the start of the array. + axom::for_all( + origSize, + AXOM_LAMBDA(axom::IndexType index) { + const auto srcIndex = deviceBlend.m_originalIdsView[index]; + const auto pt = deviceView[srcIndex]; + + // Store the point into the Conduit component arrays. + for(int comp = 0; comp < PointType::DIMENSION; comp++) + { + compViews[comp][index] = pt[comp]; + } + }); + + // Append blended values to the end of the array. + axom::for_all( + blendSize, + AXOM_LAMBDA(axom::IndexType bgid) { + // Get the blend group index we want. + const auto selectedIndex = + SelectionPolicy::selectedIndex(deviceBlend, bgid); + const auto start = deviceBlend.m_blendGroupStartView[selectedIndex]; + const auto nValues = deviceBlend.m_blendGroupSizesView[selectedIndex]; + const auto destIndex = bgid + origSize; + + VectorType blended {}; + if(nValues == 1) + { + const auto index = deviceBlend.m_blendIdsView[start]; + blended = VectorType(deviceView[index]); + } + else + { + const auto end = + start + deviceBlend.m_blendGroupSizesView[selectedIndex]; + // Blend points for this blend group. + for(IndexType i = start; i < end; i++) + { + const auto index = deviceBlend.m_blendIdsView[i]; + const auto weight = deviceBlend.m_blendCoeffView[i]; + blended += + (VectorType(deviceView[index]) * static_cast(weight)); + } + } + + // Store the point into the Conduit component arrays. + for(int comp = 0; comp < PointType::DIMENSION; comp++) + { + compViews[comp][destIndex] = blended[comp]; + } + }); + } +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/CoordsetSlicer.hpp b/src/axom/mir/utilities/CoordsetSlicer.hpp new file mode 100644 index 0000000000..c8a3cfd0cd --- /dev/null +++ b/src/axom/mir/utilities/CoordsetSlicer.hpp @@ -0,0 +1,122 @@ +// 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) +#ifndef AXOM_MIR_COORDSET_SLICER_HPP_ +#define AXOM_MIR_COORDSET_SLICER_HPP_ + +#include "axom/core.hpp" +#include "axom/mir.hpp" + +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/*! + * \accelerated + * \class CoordsetSlicer + * + * \brief This class uses SliceData to generate a new sliced coordset (pulling out specific points from input coordset). + * + * \tparam ExecSpace The execution space where the algorithm will run. + * + */ +template +class CoordsetSlicer +{ +public: + /// Constructor + CoordsetSlicer(const CoordsetView &coordsetView) + : m_coordsetView(coordsetView) + { } + + /*! + * \brief Execute the slice on the \a n_input coordset and store the new sliced coordset in \a n_output. + * + * \param slice The slice data that indicates how the coordset will be sliced. + * \param n_input A Conduit node containing the coordset to be sliced. + * \param n_output A node that will contain the sliced coordset. + * + * \note We assume for now that n_input != n_output. + */ + void execute(const SliceData &slice, + const conduit::Node &n_input, + conduit::Node &n_output) + { + using value_type = typename CoordsetView::value_type; + using PointType = typename CoordsetView::PointType; + namespace bputils = axom::mir::utilities::blueprint; + + // Get the axis names for the output coordset. For uniform, prefer x,y,z + // instead of i,j,k since we're making an explicit coordset. + std::vector axes; + if(n_input["type"].as_string() == "uniform") + { + if(n_input.has_path("dims/i")) axes.push_back("x"); + if(n_input.has_path("dims/j")) axes.push_back("y"); + if(n_input.has_path("dims/k")) axes.push_back("z"); + } + else + { + axes = conduit::blueprint::mesh::utils::coordset::axes(n_input); + } + + const auto nComponents = axes.size(); + SLIC_ASSERT(PointType::DIMENSION == nComponents); + SLIC_ASSERT(slice.m_indicesView.size() > 0); + + // Get the ID of a Conduit allocator that will allocate through Axom with device allocator allocatorID. + bputils::ConduitAllocateThroughAxom c2a; + + n_output.reset(); + n_output["type"] = "explicit"; + conduit::Node &n_values = n_output["values"]; + + // Determine output size. + const auto outputSize = slice.m_indicesView.size(); + + // Make output nodes using axis names from the input coordset. Make array views too. + axom::StackArray, PointType::DIMENSION> compViews; + for(size_t i = 0; i < nComponents; i++) + { + // Allocate data in the Conduit node and make a view. + conduit::Node &comp = n_values[axes[i]]; + comp.set_allocator(c2a.getConduitAllocatorID()); + comp.set( + conduit::DataType(bputils::cpp2conduit::id, outputSize)); + compViews[i] = bputils::make_array_view(comp); + } + + // Select the nodes we want in the output. + const CoordsetView deviceView(m_coordsetView); + const auto deviceIndicesView = slice.m_indicesView; + axom::for_all( + outputSize, + AXOM_LAMBDA(axom::IndexType index) { + const auto srcIndex = deviceIndicesView[index]; + const auto pt = deviceView[srcIndex]; + + // Store the point into the Conduit component arrays. + for(int comp = 0; comp < PointType::DIMENSION; comp++) + { + compViews[comp][index] = pt[comp]; + } + }); + } + +private: + CoordsetView m_coordsetView; +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/ExtractZones.hpp b/src/axom/mir/utilities/ExtractZones.hpp new file mode 100644 index 0000000000..45ab139c96 --- /dev/null +++ b/src/axom/mir/utilities/ExtractZones.hpp @@ -0,0 +1,791 @@ +// 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) + +#ifndef AXOM_MIR_EXTRACT_ZONES_HPP +#define AXOM_MIR_EXTRACT_ZONES_HPP + +#include "axom/core.hpp" +#include "axom/mir.hpp" +#include "axom/mir/utilities/CoordsetBlender.hpp" +#include "axom/mir/utilities/CoordsetSlicer.hpp" +#include "axom/mir/utilities/MatsetSlicer.hpp" + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/*! + * \brief Make a new topology and coordset by extracting certain zones from the input mesh. + * + * \tparam ExecSpace The execution space where the algorithm will run. + * \tparam TopologyView The topology view type on which the algorithm will run. + */ +template +class ExtractZones +{ + using ConnectivityType = typename TopologyView::ConnectivityType; + using reduce_policy = typename axom::execution_space::reduce_policy; + +public: + using SelectedZonesView = axom::ArrayView; + using ZoneType = typename TopologyView::ShapeType; + + /*! + * \brief Constructor + * + * \param topoView The input topology view. + * \param coordsetView The input coordset view. + * \param matsetView The input matset view. + */ + ExtractZones(const TopologyView &topoView, const CoordsetView &coordsetView) + : m_topologyView(topoView) + , m_coordsetView(coordsetView) + , m_zoneSlice() + { } + + /*! + * \brief Select zones from the input mesh by id and output them in the output mesh. + * + * \param selectedZonesView A view that contains the selected zone ids. + * \param n_input The input mesh. + * \param n_options The input options. + * \param[out] n_output The output mesh. + * + * The \a n_options node controls how the algorithm works. + * + * \code{.yaml} + * topology: mesh + * compact: 1 + * extra: + * nodes: 0 + * zones: 0 + * connectivity: 0 + * \endcode + * + * The "topology" node contains a string that selects the name of the topology + * to extract. The "compact" node causes the algorithm to restrict the output + * coordset and vertex fields to only the nodes used by the selected zones. If + * "compact" is set to 0 then the original coordset and vertex fields will retain + * their size in the output mesh. The "extra" node is optional and it contains + * 3 integer values for extra allocation to be made for nodes, zones, and connectivity. + * This extra space can be filled in later by the application. + */ + void execute(const SelectedZonesView &selectedZonesView, + const conduit::Node &n_input, + const conduit::Node &n_options, + conduit::Node &n_output) + { + AXOM_ANNOTATE_SCOPE("ExtractZones"); + namespace bputils = axom::mir::utilities::blueprint; + + // Determine the dataSizes and map/slice information for nodes. + axom::Array old2new; + axom::Array nodeSlice; + const auto extra = getExtra(n_options); + Sizes dataSizes {}; + if(compact(n_options)) + { + dataSizes = compactNodeMap(selectedZonesView, extra, old2new, nodeSlice); + } + else + { + dataSizes = nodeMap(selectedZonesView, extra, old2new, nodeSlice); + } + + // Make a new output topology. + const conduit::Node &n_topologies = n_input.fetch_existing("topologies"); + const std::string topoName = topologyName(n_input, n_options); + const conduit::Node &n_topo = n_topologies.fetch_existing(topoName); + conduit::Node &n_newTopo = n_output["topologies/" + topoName]; + makeTopology(selectedZonesView, + dataSizes, + extra, + old2new.view(), + n_topo, + n_options, + n_newTopo); + + // Make a new coordset. + SliceData nSlice; + nSlice.m_indicesView = nodeSlice.view(); + const std::string coordsetName = + n_topo.fetch_existing("coordset").as_string(); + const conduit::Node &n_coordset = + n_input.fetch_existing("coordsets/" + coordsetName); + conduit::Node &n_newCoordset = n_output["coordsets/" + coordsetName]; + makeCoordset(nSlice, n_coordset, n_newCoordset); + + // Make new fields. If originalZones are present, they'll be sliced there. + bool makeOriginalZones = true; + if(n_input.has_child("fields")) + { + const conduit::Node &n_fields = n_input.fetch_existing("fields"); + conduit::Node &n_newFields = n_output["fields"]; + SliceData zSlice; + zSlice.m_indicesView = zoneSliceView(selectedZonesView, extra); + makeOriginalZones = !n_fields.has_child("originalZones"); + makeFields(nSlice, zSlice, n_fields, n_newFields); + } + + // Make originalElements. + if(makeOriginalZones) + { + bputils::ConduitAllocateThroughAxom c2a; + + conduit::Node &n_origZones = n_output["fields/originalElements"]; + n_origZones["topology"] = topoName; + n_origZones["association"] = "element"; + n_origZones["values"].set_allocator(c2a.getConduitAllocatorID()); + n_origZones["values"].set(conduit::DataType(cpp2conduit::id, + selectedZonesView.size())); + axom::copy(n_origZones["values"].data_ptr(), + selectedZonesView.data(), + sizeof(axom::IndexType) * selectedZonesView.size()); + } + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +protected: +#endif + /*! + * \brief This struct contains extra amounts of storage that we might want to overallocate. + */ + struct Sizes + { + axom::IndexType nodes {0}; + axom::IndexType zones {0}; + axom::IndexType connectivity {0}; + }; + + /*! + * \brief Create a zone slice view, building m_zoneSlice if needed. + * + * \param selectedZonesView A view that contains the selected zone ids. + * \param extra A Sizes object containing any extra size that needs to be allocated. + * + * \return An array view containing the zone slice. + */ + axom::ArrayView zoneSliceView( + const SelectedZonesView &selectedZonesView, + const Sizes &extra) + { + axom::ArrayView view; + if(extra.zones > 0) + { + // We need to make a zone slice array that contains selectedZonesView, plus extra indices. + if(m_zoneSlice.size() == 0) + { + const int allocatorID = axom::execution_space::allocatorID(); + const auto n = selectedZonesView.size() + extra.zones; + m_zoneSlice = axom::Array(n, n, allocatorID); + view = m_zoneSlice.view(); + axom::copy(view.data(), + selectedZonesView.data(), + sizeof(axom::IndexType) * selectedZonesView.size()); + axom::for_all( + selectedZonesView.size(), + n, + AXOM_LAMBDA(axom::IndexType index) { view[index] = 0; }); + } + view = m_zoneSlice.view(); + } + else + { + view = selectedZonesView; + } + return view; + } + + /*! + * \brief Return a Sizes object initialized from the options. + * + * \param n_options The options node that contains extra sizes. + * + * \return A Sizes object that contains extra sizes. Values not present in the options will be 0. + */ + Sizes getExtra(const conduit::Node &n_options) const + { + Sizes extra {}; + if(n_options.has_path("extra/nodes")) + { + extra.nodes = std::max(0, n_options["extra/nodes"].to_int()); + } + if(n_options.has_path("extra/zones")) + { + extra.zones = std::max(0, n_options["extra/zones"].to_int()); + } + if(n_options.has_path("extra/connectivity")) + { + extra.connectivity = std::max(0, n_options["extra/connectivity"].to_int()); + } + return extra; + } + + /*! + * \brief Make node map and node slice information for the selected zones but + * do not limit the selection to only the used nodes. + * + * \param selectedZonesView The selected zones. + * \param extra A Sizes object containing any extra sizes to use for allocation. + * \param[out] old2new An array that contains the new node id for each node in the mesh. + * \param[out] nodeSlice An array that contains the node ids that will be selected + * from the input mesh when making coordsets and vertex fields. + * + * \return A Sizes object that contains the size of the nodes,zones,connectivity + * (excluding extra) for the output mesh. + * + * \note old2new is not used in this method. + */ + Sizes nodeMap(const SelectedZonesView &selectedZonesView, + const Sizes &extra, + axom::Array &AXOM_UNUSED_PARAM(old2new), + axom::Array &nodeSlice) const + { + AXOM_ANNOTATE_SCOPE("nodeMap"); + const int allocatorID = axom::execution_space::allocatorID(); + + const auto nnodes = m_coordsetView.numberOfNodes(); + + // Figure out the topology size based on selected zones. + RAJA::ReduceSum connsize_reduce(0); + const TopologyView deviceTopologyView(m_topologyView); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + connsize_reduce += zone.numberOfNodes(); + }); + const auto newConnSize = connsize_reduce.get(); + + Sizes sizes {}; + sizes.nodes = nnodes; + sizes.zones = selectedZonesView.size(); + sizes.connectivity = newConnSize; + + nodeSlice = axom::Array(sizes.nodes + extra.nodes, + sizes.nodes + extra.nodes, + allocatorID); + auto nodeSliceView = nodeSlice.view(); + axom::for_all( + sizes.nodes + extra.nodes, + AXOM_LAMBDA(axom::IndexType index) { + nodeSliceView[index] = (index < sizes.nodes) ? index : 0; + }); + + return sizes; + } + + /*! + * \brief Make node map and node slice information for the selected zones + * selecting only the used nodes. + * + * \param selectedZonesView The selected zones. + * \param extra A Sizes object containing any extra sizes to use for allocation. + * \param[out] old2new An array that contains the new node id for each node in the mesh. + * \param[out] nodeSlice An array that contains the node ids that will be selected + * from the input mesh when making coordsets and vertex fields. + * + * \return A Sizes object that contains the size of the nodes,zones,connectivity + * (excluding extra) for the output mesh. + */ + Sizes compactNodeMap(const SelectedZonesView selectedZonesView, + const Sizes &extra, + axom::Array &old2new, + axom::Array &nodeSlice) const + { + AXOM_ANNOTATE_SCOPE("compactNodeMap"); + const int allocatorID = axom::execution_space::allocatorID(); + + // We need to figure out which nodes to keep. + const auto nnodes = m_coordsetView.numberOfNodes(); + axom::Array mask(nnodes, nnodes, allocatorID); + auto maskView = mask.view(); + mask.fill(0); + + // Mark all the selected zones' nodes as 1. Multiple threads may write 1 to the same node. + RAJA::ReduceSum connsize_reduce(0); + TopologyView deviceTopologyView(m_topologyView); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + const axom::IndexType nids = zone.numberOfNodes(); + for(axom::IndexType i = 0; i < nids; i++) + { + const auto nodeId = zone.getId(i); + maskView[nodeId] = 1; + } + connsize_reduce += nids; + }); + const auto newConnSize = connsize_reduce.get(); + + // Count the used nodes. + RAJA::ReduceSum mask_reduce(0); + axom::for_all( + nnodes, + AXOM_LAMBDA(axom::IndexType index) { mask_reduce += maskView[index]; }); + const int newNumNodes = mask_reduce.get(); + + // Make a compact list of nodes. + axom::Array maskOffsets(nnodes, nnodes, allocatorID); + auto maskOffsetsView = maskOffsets.view(); + axom::exclusive_scan(maskView, maskOffsetsView); + + // Make an array of original node ids that we can use to "slice" the nodal data. + old2new = axom::Array(nnodes, nnodes, allocatorID); + nodeSlice = axom::Array(newNumNodes + extra.nodes, + newNumNodes + extra.nodes, + allocatorID); + auto old2newView = old2new.view(); + auto nodeSliceView = nodeSlice.view(); + axom::for_all( + nnodes, + AXOM_LAMBDA(axom::IndexType index) { + if(maskView[index] > 0) + { + nodeSliceView[maskOffsetsView[index]] = index; + old2newView[index] = maskOffsetsView[index]; + } + }); + if(extra.nodes > 0) + { + axom::for_all( + nnodes, + nnodes + extra.nodes, + AXOM_LAMBDA(axom::IndexType index) { nodeSliceView[index] = 0; }); + } + + Sizes sizes {}; + sizes.nodes = newNumNodes; + sizes.zones = selectedZonesView.size(); + sizes.connectivity = newConnSize; + + return sizes; + } + + /*! + * \brief Make the output topology for just the selected zones. + * + * \param selectedZonesView A view that contains the ids of the zones to extract. + * \param dataSizes Array sizes for connectivity, sizes, etc. + * \param extra Extra sizes for connectivity, sizes, etc. + * \param old2newView A view that lets us map old node numbers to new node numbers. + * \param n_topo The input topology. + * \param n_newTopo A node to contain the new topology. + */ + void makeTopology(const SelectedZonesView selectedZonesView, + const Sizes &dataSizes, + const Sizes &extra, + const axom::ArrayView &old2newView, + const conduit::Node &n_topo, + const conduit::Node &n_options, + conduit::Node &n_newTopo) const + { + AXOM_ANNOTATE_SCOPE("makeTopology"); + namespace bputils = axom::mir::utilities::blueprint; + bputils::ConduitAllocateThroughAxom c2a; + + const std::string shape = outputShape(n_topo); + if(shape == "polyhedron") + { + // TODO: Handle polyhedron shape. + // NOTE: We could know whether we can have PH topos if the TopologyView handles PH zones. Maybe this method is separated out and partially specialized. + SLIC_ERROR("Polyhedron is not handled yet."); + } + else + { + // The topology is not polyhedral. We'll output unstructured. + n_newTopo["type"] = "unstructured"; + n_newTopo["coordset"] = n_topo["coordset"].as_string(); + n_newTopo["elements/shape"] = outputShape(n_topo); + + conduit::Node &n_conn = n_newTopo["elements/connectivity"]; + n_conn.set_allocator(c2a.getConduitAllocatorID()); + n_conn.set(conduit::DataType(cpp2conduit::id, + dataSizes.connectivity + extra.connectivity)); + auto connView = bputils::make_array_view(n_conn); + + conduit::Node &n_sizes = n_newTopo["elements/sizes"]; + n_sizes.set_allocator(c2a.getConduitAllocatorID()); + n_sizes.set(conduit::DataType(cpp2conduit::id, + dataSizes.zones + extra.zones)); + auto sizesView = bputils::make_array_view(n_sizes); + + conduit::Node &n_offsets = n_newTopo["elements/offsets"]; + n_offsets.set_allocator(c2a.getConduitAllocatorID()); + n_offsets.set(conduit::DataType(cpp2conduit::id, + dataSizes.zones + extra.zones)); + auto offsetsView = bputils::make_array_view(n_offsets); + + // Fill sizes, offsets + const TopologyView deviceTopologyView(m_topologyView); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + sizesView[szIndex] = zone.numberOfNodes(); + }); + + if(extra.zones > 0) + { + axom::for_all( + dataSizes.zones, + dataSizes.zones + extra.zones, + AXOM_LAMBDA(axom::IndexType index) { sizesView[index] = 0; }); + } + axom::exclusive_scan(sizesView, offsetsView); + + // Fill connectivity + if(compact(n_options)) + { + const axom::ArrayView deviceOld2NewView(old2newView); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + + const int size = static_cast(sizesView[szIndex]); + const auto offset = offsetsView[szIndex]; + for(int i = 0; i < size; i++) + { + const auto oldNodeId = zone.getId(i); + // When compact, we map node ids to the compact node ids. + const auto newNodeId = deviceOld2NewView[oldNodeId]; + connView[offset + i] = newNodeId; + } + }); + } + else + { + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + + const int size = static_cast(sizesView[szIndex]); + const auto offset = offsetsView[szIndex]; + for(int i = 0; i < size; i++) + { + connView[offset + i] = zone.getId(i); + } + }); + } + if(extra.connectivity > 0) + { + axom::for_all( + dataSizes.connectivity, + dataSizes.connectivity + extra.connectivity, + AXOM_LAMBDA(axom::IndexType index) { connView[index] = 0; }); + } + + // Handle shapes, if present. + if(n_topo.has_path("elements/shapes")) + { + const conduit::Node &n_shapes = + n_topo.fetch_existing("elements/shapes"); + auto shapesView = bputils::make_array_view(n_shapes); + + conduit::Node &n_newShapes = n_newTopo["elements/shapes"]; + n_newShapes.set_allocator(c2a.getConduitAllocatorID()); + n_newShapes.set(conduit::DataType(cpp2conduit::id, + dataSizes.zones + extra.zones)); + auto newShapesView = + bputils::make_array_view(n_newShapes); + + const SelectedZonesView deviceSelectedZonesView(selectedZonesView); + axom::for_all( + dataSizes.zones, + AXOM_LAMBDA(axom::IndexType index) { + newShapesView[index] = shapesView[deviceSelectedZonesView[index]]; + }); + if(extra.zones > 0) + { + axom::for_all( + dataSizes.zones, + dataSizes.zones + extra.zones, + AXOM_LAMBDA(axom::IndexType index) { newShapesView[index] = 0; }); + } + } + } + } + + /*! + * \brief Make the new coordset using the blend data and the input coordset/coordsetview. + * + * \param nodeSlice Node slice information. + * \param n_coordset The input coordset, which is passed for metadata. + * \param[out] n_newCoordset The new coordset. + */ + void makeCoordset(const SliceData &nodeSlice, + const conduit::Node &n_coordset, + conduit::Node &n_newCoordset) const + { + AXOM_ANNOTATE_SCOPE("makeCoordset"); + // _mir_utilities_coordsetslicer_begin + axom::mir::utilities::blueprint::CoordsetSlicer cs( + m_coordsetView); + n_newCoordset.reset(); + cs.execute(nodeSlice, n_coordset, n_newCoordset); + // _mir_utilities_coordsetslicer_end + } + + /*! + * \brief Make fields for the output mesh, as needed. + * + * \param nodeSlice Node slice information. + * \param zoneSlice Zone slice information. + * \param n_fields The input fields. + * \param n_newFields The output fields. + */ + void makeFields(const SliceData &nodeSlice, + const SliceData &zoneSlice, + const conduit::Node &n_fields, + conduit::Node &n_newFields) const + { + AXOM_ANNOTATE_SCOPE("makeFields"); + for(conduit::index_t i = 0; i < n_fields.number_of_children(); i++) + { + const conduit::Node &n_field = n_fields[i]; + const std::string association = n_field["association"].as_string(); + conduit::Node &n_newField = n_newFields[n_field.name()]; + axom::mir::utilities::blueprint::FieldSlicer fs; + if(association == "element") + { + fs.execute(zoneSlice, n_field, n_newField); + } + else if(association == "vertex") + { + fs.execute(nodeSlice, n_field, n_newField); + } + } + } + + /*! + * \brief Get the topology name. + * + * \param n_input The input mesh. + * \param n_options The options node that may contain a "topology" string. + * + * \return Returns the options topology name, if present. Otherwise, it returns the first topology name. + */ + std::string topologyName(const conduit::Node &n_input, + const conduit::Node &n_options) const + { + std::string name; + if(n_options.has_path("topology")) + { + name = n_options["topology"].as_string(); + } + else + { + const conduit::Node &n_topologies = n_input.fetch_existing("topologies"); + name = n_topologies[0].name(); + } + return name; + } + + /*! + * \brief Return whether coordset/vertex compaction is desired. + * + * \param n_options The options node that may contain a "topology" string. + * + * \return True if compaction is on (the default), false otherwise. + */ + bool compact(const conduit::Node &n_options) const + { + bool retval = true; + if(n_options.has_path("compact")) + { + retval = n_options["compact"].to_int() != 0; + } + return retval; + } + + /*! + * \brief Return the name of the output shape type. + * + * \param n_topo The input topology node. + * + * \return The name of the output shape. + */ + std::string outputShape(const conduit::Node &n_topo) const + { + std::string shape; + if(n_topo["type"].as_string() == "unstructured") + { + shape = n_topo["elements/shape"].as_string(); + } + else + { + if(TopologyView::dimension() == 3) + { + shape = "hex"; + } + else if(TopologyView::dimension() == 2) + { + shape = "quad"; + } + else + { + shape = "line"; + } + } + return shape; + } + +// The following members are protected (unless using CUDA) +#if !defined(__CUDACC__) +protected: +#endif + TopologyView m_topologyView; + CoordsetView m_coordsetView; + axom::Array m_zoneSlice; +}; + +/*! + * \brief Make a new topology and coordset by extracting certain zones from the input mesh. + * + * \tparam ExecSpace The execution space where the algorithm will run. + * \tparam TopologyView The topology view type on which the algorithm will run. + * \tparam MatsetView The matset view type on which the algorithm will run. + */ +template +class ExtractZonesAndMatset + : public ExtractZones +{ +public: + using SelectedZonesView = axom::ArrayView; + + /*! + * \brief Constructor + * + * \param topoView The input topology view. + * \param coordsetView The input coordset view. + * \param matsetView The input matset view. + */ + ExtractZonesAndMatset(const TopologyView &topoView, + const CoordsetView &coordsetView, + const MatsetView &matsetView) + : ExtractZones(topoView, coordsetView) + , m_matsetView(matsetView) + { } + + /*! + * \brief Select zones from the input mesh by id and output them in the output mesh. + * + * \param selectedZonesView A view that contains the selected zone ids. + * \param n_input The input mesh. + * \param n_options The input options. + * \param[out] n_output The output mesh. + * + * \note The \a n_options node contains a "topology" string that is selects the + * name of the topology to extract. + */ + void execute(const SelectedZonesView &selectedZonesView, + const conduit::Node &n_input, + const conduit::Node &n_options, + conduit::Node &n_output) + { + AXOM_ANNOTATE_SCOPE("ExtractZonesAndMatset"); + + // Call base class to handle mesh/coordset/fields + // _mir_utilities_extractzones_begin + ExtractZones::execute(selectedZonesView, + n_input, + n_options, + n_output); + // _mir_utilities_extractzones_end + + // Make new matset. + const std::string topoName = + ExtractZones::topologyName( + n_input, + n_options); + std::string mname = matsetName(n_input, topoName); + if(!mname.empty()) + { + const conduit::Node &n_matset = n_input.fetch_existing("matsets/" + mname); + conduit::Node &n_newMatset = n_output["matsets/" + mname]; + makeMatset(selectedZonesView, n_matset, n_newMatset); + } + } + +// The following members are protected (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + /*! + * \brief Return the matset for the named topology. + * + * \param n_input The input mesh. + * \param topoName The name of the topology. + * + * \return The name of the matset for the topology or an empty string if no matset was found. + */ + std::string matsetName(const conduit::Node &n_input, + const std::string &topoName) const + { + std::string matset; + if(n_input.has_child("matsets")) + { + const conduit::Node &n_matsets = n_input.fetch_existing("matsets"); + for(conduit::index_t i = 0; i < n_matsets.number_of_children(); i++) + { + const conduit::Node &n_matset = n_matsets[i]; + if(n_matset["topology"].as_string() == topoName) + { + matset = n_matset.name(); + break; + } + } + } + return matset; + } + + /*! + * \brief Make a new matset that covers the selected zones. + * + * \param selectedZonesView A view that contains the selected zones. + * \param n_matset The input matset. + * \param n_newMatset A node that will contain the new matset. + */ + void makeMatset(const SelectedZonesView &selectedZonesView, + const conduit::Node &n_matset, + conduit::Node &n_newMatset) const + { + AXOM_ANNOTATE_SCOPE("makeMatset"); + // _mir_utilities_matsetslicer_begin + MatsetSlicer ms(m_matsetView); + SliceData zSlice; + zSlice.m_indicesView = selectedZonesView; + ms.execute(zSlice, n_matset, n_newMatset); + // _mir_utilities_matsetslicer_end + } + + MatsetView m_matsetView; +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/FieldBlender.hpp b/src/axom/mir/utilities/FieldBlender.hpp new file mode 100644 index 0000000000..e44c4a5966 --- /dev/null +++ b/src/axom/mir/utilities/FieldBlender.hpp @@ -0,0 +1,231 @@ +// 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) +#ifndef AXOM_MIR_FIELD_BLENDER_HPP_ +#define AXOM_MIR_FIELD_BLENDER_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/views/NodeArrayView.hpp" +#include "axom/mir/utilities/utilities.hpp" +#include "axom/mir/utilities/blueprint_utilities.hpp" + +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/*! + * \brief This policy can be used with FieldBlender to select all blend groups. + */ +struct SelectAllPolicy +{ + AXOM_HOST_DEVICE + static inline IndexType size(const BlendData &blend) + { + return blend.m_blendGroupSizesView.size(); + } + + AXOM_HOST_DEVICE + static inline IndexType selectedIndex(const BlendData & /*blend*/, + IndexType index) + { + return index; + } +}; + +/*! + * \brief This policy can be used with FieldBlender to select a subset of blend groups, according to m_selectedIndicesView. + */ +struct SelectSubsetPolicy +{ + AXOM_HOST_DEVICE + static inline IndexType size(const BlendData &blend) + { + return blend.m_selectedIndicesView.size(); + } + + AXOM_HOST_DEVICE + static inline IndexType selectedIndex(const BlendData &blend, IndexType index) + { + return blend.m_selectedIndicesView[index]; + } +}; + +/*! + * \accelerated + * \class FieldBlender + * + * \brief This class uses BlendData to generate a new blended field from an input field. + * + * \tparam ExecSpace The execution space where the work will occur. + * \tparam SelectionPolicy The selection policy to use. + * \tparam IndexingPolicy A class that provides operator[] that can transform node indices. + */ +template +class FieldBlender +{ +public: + /// Constructor + FieldBlender() : m_indexing() { } + + /*! + * \brief Constructor + * \param indexing An object used to transform node indices. + */ + FieldBlender(const IndexingPolicy &indexing) : m_indexing(indexing) { } + + /*! + * \brief Create a new blended field from the \a n_input field and place it in \a n_output. + * + * \param blend The BlendData that will be used to make the new field. + * \param n_input The input field that we're blending. + * \param n_output The output node that will contain the new field. + */ + void execute(const BlendData &blend, + const conduit::Node &n_input, + conduit::Node &n_output) const + { + n_output.reset(); + n_output["association"] = n_input["association"]; + n_output["topology"] = n_input["topology"]; + + const conduit::Node &n_input_values = n_input["values"]; + conduit::Node &n_output_values = n_output["values"]; + if(n_input_values.number_of_children() > 0) + { + for(conduit::index_t i = 0; i < n_input_values.number_of_children(); i++) + { + const conduit::Node &n_comp = n_input_values[i]; + conduit::Node &n_out_comp = n_output_values[n_comp.name()]; + blendSingleComponent(blend, n_comp, n_out_comp); + } + } + else + { + blendSingleComponent(blend, n_input_values, n_output_values); + } + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + /*! + * \brief Blend data for a single field component. + * + * \param blend The BlendData that will be used to make the new field. + * \param n_values The input values that we're blending. + * \param n_output_values The output node that will contain the new field. + */ + void blendSingleComponent(const BlendData &blend, + const conduit::Node &n_values, + conduit::Node &n_output_values) const + { + // We're allowing selectedIndicesView to be used to select specific blend + // groups. If the user did not provide that, use all blend groups. + const auto origSize = blend.m_originalIdsView.size(); + const auto blendSize = SelectionPolicy::size(blend); + const auto outputSize = origSize + blendSize; + + // Allocate Conduit data through Axom. + utilities::blueprint::ConduitAllocateThroughAxom c2a; + n_output_values.set_allocator(c2a.getConduitAllocatorID()); + n_output_values.set(conduit::DataType(n_values.dtype().id(), outputSize)); + + views::Node_to_ArrayView_same( + n_values, + n_output_values, + [&](auto compView, auto outView) { + blendSingleComponentImpl(blend, compView, outView); + }); + } + + /*! + * \brief Slice the source view and copy values into the output view. + * + * \param valuesView The source values view. + * \param outputView The output values view. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void blendSingleComponentImpl(const BlendData &blend, + SrcView compView, + OutputView outView) const + { + using value_type = typename decltype(compView)::value_type; + using accum_type = + typename axom::mir::utilities::accumulation_traits::value_type; + + // We're allowing selectedIndicesView to be used to select specific blend + // groups. If the user did not provide that, use all blend groups. + const auto origSize = blend.m_originalIdsView.size(); + const auto blendSize = SelectionPolicy::size(blend); + // const auto outputSize = origSize + blendSize; + + const IndexingPolicy deviceIndexing(m_indexing); + const BlendData deviceBlend(blend); + + // Copy over some original values to the start of the array. + axom::for_all( + origSize, + AXOM_LAMBDA(axom::IndexType index) { + const auto srcIndex = deviceBlend.m_originalIdsView[index]; + outView[index] = compView[srcIndex]; + }); + + // Append blended values to the end of the array. + axom::for_all( + blendSize, + AXOM_LAMBDA(axom::IndexType bgid) { + // Get the blend group index we want. + const auto selectedIndex = + SelectionPolicy::selectedIndex(deviceBlend, bgid); + const auto start = deviceBlend.m_blendGroupStartView[selectedIndex]; + const auto nValues = deviceBlend.m_blendGroupSizesView[selectedIndex]; + const auto destIndex = origSize + bgid; + if(nValues == 1) + { + const auto index = deviceBlend.m_blendIdsView[start]; + const auto srcIndex = deviceIndexing[index]; + outView[destIndex] = compView[srcIndex]; + } + else + { + const auto end = start + nValues; + accum_type blended = 0; + for(IndexType i = start; i < end; i++) + { + const auto index = deviceBlend.m_blendIdsView[i]; + const auto weight = deviceBlend.m_blendCoeffView[i]; + const auto srcIndex = deviceIndexing[index]; + blended += static_cast(compView[srcIndex]) * weight; + } + outView[destIndex] = static_cast(blended); + } + }); + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + IndexingPolicy m_indexing {}; +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/FieldSlicer.hpp b/src/axom/mir/utilities/FieldSlicer.hpp new file mode 100644 index 0000000000..a63c12f426 --- /dev/null +++ b/src/axom/mir/utilities/FieldSlicer.hpp @@ -0,0 +1,157 @@ +// 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) +#ifndef AXOM_MIR_FIELD_SLICER_HPP_ +#define AXOM_MIR_FIELD_SLICER_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/views/NodeArrayView.hpp" +#include "axom/mir/utilities/blueprint_utilities.hpp" + +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/*! + * \brief Contains the indices to be sliced out of a Blueprint field. + */ +struct SliceData +{ + axom::ArrayView m_indicesView; +}; + +/*! + * \accelerated + * \class FieldSlicer + * + * \brief This class uses SliceData to generate a new sliced field from an input field. + * + * \tparam ExecSpace The execution space where the algorithm will run. + * \tparam IndexingPolicy A class that provides operator[] that can transform zone indices. + * + */ +template +class FieldSlicer +{ +public: + /// Constructor + FieldSlicer() : m_indexing() { } + + /*! + * \brief Constructor + * \param indexing An object used to transform node indices. + */ + FieldSlicer(const IndexingPolicy &indexing) : m_indexing(indexing) { } + + /*! + * \brief Execute the slice on the \a n_input field and store the new sliced field in \a n_output. + * + * \param slice The slice data that indicates how the field will be sliced. + * \param n_input A Conduit node containing the field to be sliced. + * \param n_output A node that will contain the sliced field. + * + * \note We assume for now that n_input != n_output. + */ + void execute(const SliceData &slice, + const conduit::Node &n_input, + conduit::Node &n_output) + { + n_output.reset(); + n_output["association"] = n_input["association"]; + n_output["topology"] = n_input["topology"]; + + const conduit::Node &n_input_values = n_input["values"]; + conduit::Node &n_output_values = n_output["values"]; + if(n_input_values.number_of_children() > 0) + { + for(conduit::index_t i = 0; i < n_input_values.number_of_children(); i++) + { + const conduit::Node &n_comp = n_input_values[i]; + conduit::Node &n_out_comp = n_output_values[n_comp.name()]; + sliceSingleComponent(slice, n_comp, n_out_comp); + } + } + else + { + sliceSingleComponent(slice, n_input_values, n_output_values); + } + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + /*! + * \brief Slice data for a single field component. + * + * \param slice The SliceData that will be used to make the new field. + * \param n_values The input values that we're slicing. + * \param n_output_values The output node that will contain the new field. + */ + void sliceSingleComponent(const SliceData &slice, + const conduit::Node &n_values, + conduit::Node &n_output_values) const + { + const auto outputSize = slice.m_indicesView.size(); + + // Allocate Conduit data through Axom. + utilities::blueprint::ConduitAllocateThroughAxom c2a; + n_output_values.set_allocator(c2a.getConduitAllocatorID()); + n_output_values.set(conduit::DataType(n_values.dtype().id(), outputSize)); + + views::Node_to_ArrayView_same( + n_values, + n_output_values, + [&](auto valuesView, auto outputView) { + sliceSingleComponentImpl(slice, valuesView, outputView); + }); + } + + /*! + * \brief Slice the source view and copy values into the output view. + * + * \param valuesView The source values view. + * \param outputView The output values view. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void sliceSingleComponentImpl(const SliceData &slice, + ValuesView valuesView, + OutputView outputView) const + { + IndexingPolicy deviceIndexing(m_indexing); + SliceData deviceSlice(slice); + axom::for_all( + outputView.size(), + AXOM_LAMBDA(axom::IndexType index) { + const auto zoneIndex = deviceSlice.m_indicesView[index]; + const auto transformedIndex = deviceIndexing[zoneIndex]; + outputView[index] = valuesView[transformedIndex]; + }); + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + IndexingPolicy m_indexing {}; +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/MakeUnstructured.hpp b/src/axom/mir/utilities/MakeUnstructured.hpp new file mode 100644 index 0000000000..39117d6b59 --- /dev/null +++ b/src/axom/mir/utilities/MakeUnstructured.hpp @@ -0,0 +1,142 @@ +// 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) +#ifndef AXOM_MIR_MAKE_UNSTRUCTURED_HPP_ +#define AXOM_MIR_MAKE_UNSTRUCTURED_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/views/NodeArrayView.hpp" +#include "axom/mir/utilities/utilities.hpp" +#include "axom/mir/utilities/blueprint_utilities.hpp" +#include "axom/mir/views/dispatch_structured_topology.hpp" + +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/** + * \accelerated + * \brief Make an unstructured representation of a structured topology. + */ +template +class MakeUnstructured +{ +public: + /** + * \brief Make an unstructured representation of a structured topology. + * + * \tparam ExecSpace The execution space where the work will be done. + * + * \param topo The input topology to be turned into unstructured. + * \param coordset The topology's coordset. It will be referenced as an external node in the output \a mesh. + * \param topoName The name of the new topology to create. + * \param[out] mesh The node that will contain the new topology and coordset. + * + * \note There are blueprint methods for this sort of thing but this one runs on device. + */ + static void execute(const conduit::Node &topo, + const conduit::Node &coordset, + const std::string &topoName, + conduit::Node &mesh) + { + const std::string type = topo.fetch_existing("type").as_string(); + ConduitAllocateThroughAxom c2a; + + mesh["coordsets"][coordset.name()].set_external(coordset); + conduit::Node &n_newtopo = mesh["topologies"][topoName]; + n_newtopo["coordset"] = coordset.name(); + + if(type == "unstructured") + { + n_newtopo.set_external(topo); + } + else + { + n_newtopo["type"] = "unstructured"; + conduit::Node &n_newconn = n_newtopo["elements/connectivity"]; + conduit::Node &n_newsizes = n_newtopo["elements/sizes"]; + conduit::Node &n_newoffsets = n_newtopo["elements/offsets"]; + n_newconn.set_allocator(c2a.getConduitAllocatorID()); + n_newsizes.set_allocator(c2a.getConduitAllocatorID()); + n_newoffsets.set_allocator(c2a.getConduitAllocatorID()); + + axom::mir::views::dispatch_structured_topologies( + topo, + [&](const std::string &shape, auto &topoView) { + n_newtopo["elements/shape"] = shape; + + int ptsPerZone = 2; + if(shape == "quad") + ptsPerZone = 4; + else if(shape == "hex") + ptsPerZone = 8; + + // Allocate new mesh data. + const auto nzones = topoView.numberOfZones(); + const auto connSize = nzones * ptsPerZone; + n_newconn.set(conduit::DataType::index_t(connSize)); + n_newsizes.set(conduit::DataType::index_t(nzones)); + n_newoffsets.set(conduit::DataType::index_t(nzones)); + + // Make views for the mesh data. + auto connView = make_array_view(n_newconn); + auto sizesView = make_array_view(n_newsizes); + auto offsetsView = make_array_view(n_newoffsets); + + makeUnstructured(ptsPerZone, topoView, connView, sizesView, offsetsView); + }); + } + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + /*! + * \brief Iterate over the input topology's zones and store their connectivity in + * unstructured connectivity nodes. + * + * \param ptsPerZone The number of points per zone. + * \param topoView The input topology view. + * \param connView The view that will contain the new connectivity. + * \param sizesView The view that will contain the new sizes. + * \param offsetsView The view that will contain the new offsets. + */ + template + static void makeUnstructured(int ptsPerZone, + TopologyView topoView, + axom::ArrayView connView, + axom::ArrayView sizesView, + axom::ArrayView offsetsView) + { + // Fill in the new connectivity. + axom::for_all( + topoView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zone = topoView.zone(zoneIndex); + + const auto start = zoneIndex * ptsPerZone; + for(int i = 0; i < ptsPerZone; i++) + { + connView[start + i] = static_cast(zone.getId(i)); + } + sizesView[zoneIndex] = ptsPerZone; + offsetsView[zoneIndex] = start; + }); + } +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/MatsetSlicer.hpp b/src/axom/mir/utilities/MatsetSlicer.hpp new file mode 100644 index 0000000000..61e8de5e56 --- /dev/null +++ b/src/axom/mir/utilities/MatsetSlicer.hpp @@ -0,0 +1,156 @@ +// 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) + +#ifndef AXOM_MIR_MATSET_SLICER_HPP +#define AXOM_MIR_MATSET_SLICER_HPP + +#include "axom/core.hpp" +#include "axom/mir.hpp" + +#include + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/*! + * \brief Slices the input matset view and outputs a new matset (unibuffer flavor). + * + * \tparam ExecSpace The execution space where the algorithm will run. + * \tparam MatsetView The matset view type that wraps the Blueprint matset. + */ +template +class MatsetSlicer +{ + using reduce_policy = typename axom::execution_space::reduce_policy; + +public: + using SelectedZonesView = axom::ArrayView; + + /*! + * \brief Constructor. + */ + MatsetSlicer(const MatsetView &matsetView) : m_matsetView(matsetView) { } + + /*! + * \brief Slice the input matset and output a new matset. + * + * \param matsetView A view that wraps the input matset. + * \param slice Slice data that contains the zone ids that we're extracting from the matset. + * \param n_matset The input matset. + * \param[out] n_newMatset The output matset. + */ + void execute(const SliceData &slice, + const conduit::Node &n_matset, + conduit::Node &n_newMatset) + { + using MatsetIndex = typename MatsetView::IndexType; + using MatsetFloat = typename MatsetView::FloatType; + namespace bputils = axom::mir::utilities::blueprint; + const axom::ArrayView &selectedZonesView = + slice.m_indicesView; + SLIC_ASSERT(selectedZonesView.size() > 0); + + // Copy the material_map if it exists. + const char *keys[] = {"topology", "material_map"}; + for(int i = 0; i < 2; i++) + { + if(n_matset.has_child(keys[i])) + n_newMatset[keys[i]] = n_matset.fetch_existing(keys[i]); + } + + bputils::ConduitAllocateThroughAxom c2a; + + // Allocate sizes/offsets. + conduit::Node &n_sizes = n_newMatset["sizes"]; + n_sizes.set_allocator(c2a.getConduitAllocatorID()); + n_sizes.set(conduit::DataType(cpp2conduit::id, + selectedZonesView.size())); + auto sizesView = bputils::make_array_view(n_sizes); + + conduit::Node &n_offsets = n_newMatset["offsets"]; + n_offsets.set_allocator(c2a.getConduitAllocatorID()); + n_offsets.set(conduit::DataType(cpp2conduit::id, + selectedZonesView.size())); + auto offsetsView = bputils::make_array_view(n_offsets); + + // Figure out overall size of the matset zones we're keeping. + MatsetView deviceMatsetView(m_matsetView); + const axom::ArrayView deviceSelectedZonesView( + selectedZonesView); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType index) { + const auto nmats = + deviceMatsetView.numberOfMaterials(deviceSelectedZonesView[index]); + sizesView[index] = nmats; + }); + RAJA::ReduceSum size_reduce(0); + axom::for_all( + sizesView.size(), + AXOM_LAMBDA(axom::IndexType index) { size_reduce += sizesView[index]; }); + axom::exclusive_scan(sizesView, offsetsView); + + // Allocate data for the rest of the matset. + const auto totalSize = size_reduce.get(); + SLIC_ASSERT(totalSize > 0); + + conduit::Node &n_indices = n_newMatset["indices"]; + n_indices.set_allocator(c2a.getConduitAllocatorID()); + n_indices.set(conduit::DataType(cpp2conduit::id, totalSize)); + auto indicesView = bputils::make_array_view(n_indices); + + conduit::Node &n_material_ids = n_newMatset["material_ids"]; + n_material_ids.set_allocator(c2a.getConduitAllocatorID()); + n_material_ids.set(conduit::DataType(cpp2conduit::id, totalSize)); + auto materialIdsView = bputils::make_array_view(n_material_ids); + + conduit::Node &n_volume_fractions = n_newMatset["volume_fractions"]; + n_volume_fractions.set_allocator(c2a.getConduitAllocatorID()); + n_volume_fractions.set( + conduit::DataType(cpp2conduit::id, totalSize)); + auto volumeFractionsView = + bputils::make_array_view(n_volume_fractions); + + // Fill in the matset data with the zones we're keeping. + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType index) { + const auto size = static_cast(sizesView[index]); + const auto offset = offsetsView[index]; + + typename MatsetView::IDList ids; + typename MatsetView::VFList vfs; + deviceMatsetView.zoneMaterials(deviceSelectedZonesView[index], ids, vfs); + + for(int i = 0; i < size; i++) + { + const auto destIndex = offset + i; + materialIdsView[destIndex] = ids[i]; + volumeFractionsView[destIndex] = vfs[i]; + indicesView[destIndex] = destIndex; + } + }); + } + +private: + MatsetView m_matsetView; +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/MergeMeshes.hpp b/src/axom/mir/utilities/MergeMeshes.hpp new file mode 100644 index 0000000000..48fd8f970a --- /dev/null +++ b/src/axom/mir/utilities/MergeMeshes.hpp @@ -0,0 +1,1373 @@ +// 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) +#ifndef AXOM_MIR_MERGE_MESHES_HPP_ +#define AXOM_MIR_MERGE_MESHES_HPP_ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/mir.hpp" +#include "axom/slic.hpp" + +#include "axom/mir/views/dispatch_material.hpp" + +#include + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/** + * \brief A mesh input containing a Blueprint mesh and some mapping array views. + */ +struct MeshInput +{ + conduit::Node *m_input {nullptr}; //!< Pointer to Blueprint mesh. + axom::ArrayView + m_nodeMapView {}; //!< Map for mesh nodeIds to nodeIds in final mesh. + axom::ArrayView + m_nodeSliceView {}; //!< Node ids to be extracted and added to final mesh. +}; + +/** + * \brief Merge multiple unstructured Blueprint meshes through MeshInput. + * + * \note The input meshes must currently contain a single coordset/topology/matset. + */ +template +class MergeMeshes +{ +public: + /** + * \brief Merge the input Blueprint meshes into a single Blueprint mesh. + * + * \param inputs A vector of inputs to be merged. + * \param options A Node containing algorithm options. + * \param[out] output The node that will contain the merged mesh. + */ + void execute(const std::vector &inputs, + const conduit::Node &options, + conduit::Node &output) const + { + AXOM_ANNOTATE_SCOPE("MergeMeshes"); + bool ok = validInputs(inputs, options); + if(!ok) + { + SLIC_ASSERT_MSG(ok, "Unsupported inputs were provided."); + return; + } + + if(inputs.size() == 1) + { + singleInput(inputs, output); + } + else if(inputs.size() > 1) + { + mergeInputs(inputs, output); + } + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + /** + * \brief This struct contains information used when merging fields. + */ + struct FieldInformation + { + std::string topology; + std::string association; + int dtype; + std::vector components; + }; + + /** + * \brief Check that the mesh inputs are valid and meet constraints. There must + * be 1 coordset/topology/matset. The coordset must be explicit and the + * topology must be unstructured and for now, non-polyhedral. + * + * \param inputs The mesh inputs. + * + * \return True if the inputs appear to be valid; False otherwise. + */ + bool validInputs(const std::vector &inputs, + const conduit::Node &options) const + { + std::string topoName; + if(options.has_child("topology")) + topoName = options.fetch_existing("topology").as_string(); + + for(size_t i = 0; i < inputs.size(); i++) + { + if(inputs[i].m_input == nullptr) return false; + + // If we did not specify which topology, make sure that there is only 1. + const char *keys[] = {"coordsets", "topologies", "matsets"}; + if(topoName.empty()) + { + for(int k = 0; k < 3; k++) + { + if(inputs[i].m_input->has_path(keys[k])) + { + const conduit::Node &n = inputs[i].m_input->fetch_existing(keys[k]); + if(n.number_of_children() > 1) return false; + } + } + } + + const conduit::Node &n_coordsets = + inputs[i].m_input->fetch_existing("coordsets"); + const conduit::Node &n_coordset = n_coordsets[0]; + if(n_coordset["type"].as_string() != "explicit") + { + return false; + } + + const conduit::Node &n_topologies = + inputs[i].m_input->fetch_existing("topologies"); + const conduit::Node *n_topo = nullptr; + if(topoName.empty()) + n_topo = n_topologies.child_ptr(0); + else + n_topo = n_topologies.fetch_ptr(topoName); + if(n_topo->operator[]("type").as_string() != "unstructured") + { + return false; + } + // For now + if(n_topo->operator[]("elements/shape").as_string() == "polyhedral") + { + return false; + } + + // Require no nodeMap/nodeSlice or that they both be present. + if(!((inputs[i].m_nodeMapView.size() == 0 && + inputs[i].m_nodeSliceView.size() == 0) || + (inputs[i].m_nodeMapView.size() > 0 && + inputs[i].m_nodeSliceView.size() > 0))) + { + return false; + } + } + return true; + } + + /** + * \brief Merge a single input (copy it to the output). + * + * \param inputs A vector of inputs to be merged. + * \param[out] output The node that will contain the merged mesh. + */ + void singleInput(const std::vector &inputs, conduit::Node &output) const + { + namespace bputils = axom::mir::utilities::blueprint; + bputils::copy(output, *(inputs[0].m_input)); + } + + /** + * \brief Merge a multiple inputs. + * + * \param inputs A vector of inputs to be merged. + * \param[out] output The node that will contain the merged mesh. + */ + void mergeInputs(const std::vector &inputs, conduit::Node &output) const + { + mergeCoordset(inputs, output); + mergeTopology(inputs, output); + mergeFields(inputs, output); + mergeMatset(inputs, output); + } + + /** + * \brief Merge multiple coordsets into a single coordset. No node merging takes place. + * + * \param inputs A vector of inputs to be merged. + * \param[out] output The node that will contain the output mesh. + */ + void mergeCoordset(const std::vector &inputs, + conduit::Node &output) const + { + namespace bputils = axom::mir::utilities::blueprint; + AXOM_ANNOTATE_SCOPE("mergeCoordset"); + const axom::IndexType totalNodes = countNodes(inputs); + conduit::Node &n_newCoordsets = output["coordsets"]; + conduit::Node *n_newValuesPtr = nullptr; + int nComps = 2; + + axom::IndexType offsets[3] = {0, 0, 0}; + const axom::IndexType n = static_cast(inputs.size()); + for(axom::IndexType i = 0; i < n; i++) + { + const conduit::Node &coordsets = + inputs[i].m_input->fetch_existing("coordsets"); + const conduit::Node &n_srcCoordset = coordsets[0]; + const conduit::Node &n_srcValues = n_srcCoordset.fetch_existing("values"); + + const auto type = n_srcCoordset.fetch_existing("type").as_string(); + SLIC_ASSERT(type == "explicit"); + + // Make all of the components the first time. + if(i == 0) + { + bputils::ConduitAllocateThroughAxom c2a; + + conduit::Node &n_newCoordset = n_newCoordsets[n_srcCoordset.name()]; + n_newCoordset["type"] = "explicit"; + conduit::Node &n_newValues = n_newCoordset["values"]; + n_newValuesPtr = n_newCoordset.fetch_ptr("values"); + + nComps = n_srcValues.number_of_children(); + for(int c = 0; c < nComps; c++) + { + const conduit::Node &n_srcComp = n_srcValues[c]; + conduit::Node &n_comp = n_newValues[n_srcComp.name()]; + n_comp.set_allocator(c2a.getConduitAllocatorID()); + n_comp.set(conduit::DataType(n_srcComp.dtype().id(), totalNodes)); + } + } + + // Copy this input's coordinates into the new coordset. + axom::mir::views::FloatNode_to_ArrayView(n_srcValues[0], [&](auto comp0) { + using FloatType = typename decltype(comp0)::value_type; + for(int c = 0; c < nComps; c++) + { + const conduit::Node &n_srcComp = n_srcValues[c]; + conduit::Node &n_comp = n_newValuesPtr->child(c); + auto srcCompView = bputils::make_array_view(n_srcComp); + auto compView = bputils::make_array_view(n_comp); + + axom::IndexType size = mergeCoordset_copy(inputs[i].m_nodeSliceView, + offsets[c], + compView, + srcCompView); + + offsets[c] += size; + } + }); + } + } + + /*! + * \brief Assist setting merging coordset data. + * + * \param nodeSliceView The view that contains a node slice for the current input mesh. + * \param offset The current write offset in the new coordset. + * \param compView The view that exposes the current output coordinate component. + * \param srcCompView The view that exposes the current output source coordinate component. + * + * \return The size of the data copied. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + axom::IndexType mergeCoordset_copy( + const axom::ArrayView nodeSliceView, + axom::IndexType offset, + DataArrayView compView, + DataArrayView srcCompView) const + { + axom::IndexType size = 0; + if(nodeSliceView.size() > 0) + { + // Pull out specific nodes from the input. + axom::for_all( + nodeSliceView.size(), + AXOM_LAMBDA(axom::IndexType index) { + const auto sliceIndex = nodeSliceView[index]; + compView[offset + index] = srcCompView[sliceIndex]; + }); + size = nodeSliceView.size(); + } + else + { + // Pull out all nodes from the input. + axom::for_all( + srcCompView.size(), + AXOM_LAMBDA(axom::IndexType index) { + compView[offset + index] = srcCompView[index]; + }); + size = srcCompView.size(); + } + return size; + } + + /*! + * \brief Count the number of nodes in the \a index'th input mesh. + * + * \param inputs The vector of input meshes. + * \param index The index of the mesh to count. + * + * \return The number of nodes in the \a index input mesh. + */ + axom::IndexType countNodes(const std::vector &inputs, + size_t index) const + { + SLIC_ASSERT(index < inputs.size()); + + const conduit::Node &coordsets = + inputs[index].m_input->fetch_existing("coordsets"); + const conduit::Node &coordset = coordsets[0]; + const auto type = coordset.fetch_existing("type").as_string(); + + axom::IndexType nnodes = 0; + if(inputs[index].m_nodeSliceView.size() > 0) + nnodes = inputs[index].m_nodeSliceView.size(); + else + nnodes = conduit::blueprint::mesh::utils::coordset::length(coordset); + + return nnodes; + } + + /*! + * \brief Count the number of nodes in all input meshes. + * + * \param inputs The vector of input meshes. + * + * \return The total number of nodes in the input meshes. + */ + axom::IndexType countNodes(const std::vector &inputs) const + { + axom::IndexType nodeTotal = 0; + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nnodes = countNodes(inputs, i); + nodeTotal += nnodes; + } + return nodeTotal; + } + + /*! + * \brief Count the number of zones in the \a index'th input mesh. + * + * \param inputs The vector of input meshes. + * \param index The index of the mesh to count. + * + * \return The number of zones in the \a index input mesh. + */ + axom::IndexType countZones(const std::vector &inputs, + size_t index) const + { + const conduit::Node &n_topologies = + inputs[index].m_input->fetch_existing("topologies"); + const conduit::Node &n_topo = n_topologies[0]; + + const conduit::Node &n_size = n_topo.fetch_existing("elements/sizes"); + axom::IndexType nzones = n_size.dtype().number_of_elements(); + return nzones; + } + + /*! + * \brief Count the number of nodes in all input meshes. + * + * \param inputs The vector of input meshes. + * \param[out] totalConnLength The total connectivity length for all meshes. + * \param[out] totalZones The total zones for all meshes. + */ + void countZones(const std::vector &inputs, + axom::IndexType &totalConnLength, + axom::IndexType &totalZones) const + { + totalConnLength = 0; + totalZones = 0; + axom::IndexType n = static_cast(inputs.size()); + for(axom::IndexType i = 0; i < n; i++) + { + const conduit::Node &n_topologies = + inputs[i].m_input->fetch_existing("topologies"); + const conduit::Node &n_topo = n_topologies[0]; + const auto type = n_topo.fetch_existing("type").as_string(); + SLIC_ASSERT(type == "unstructured"); + + const conduit::Node &n_conn = + n_topo.fetch_existing("elements/connectivity"); + const auto connLength = n_conn.dtype().number_of_elements(); + totalConnLength += connLength; + + const conduit::Node &n_size = n_topo.fetch_existing("elements/sizes"); + const auto nzones = n_size.dtype().number_of_elements(); + totalZones += nzones; + } + } + + /** + * \brief Merge multiple topologies into a single topology. + * + * \param inputs A vector of inputs to be merged. + * \param[out] output The node that will contain the output mesh. + */ + void mergeTopology(const std::vector &inputs, + conduit::Node &output) const + { + namespace bputils = axom::mir::utilities::blueprint; + AXOM_ANNOTATE_SCOPE("mergeTopology"); + axom::IndexType totalConnLen = 0, totalZones = 0; + countZones(inputs, totalConnLen, totalZones); + conduit::Node &n_newTopologies = output["topologies"]; + + // Check whether there are mixed shapes. + std::map shape_map; + const axom::IndexType n = static_cast(inputs.size()); + for(axom::IndexType i = 0; i < n; i++) + { + const conduit::Node &n_topologies = + inputs[i].m_input->fetch_existing("topologies"); + const conduit::Node &n_srcTopo = n_topologies[0]; + const auto type = n_srcTopo.fetch_existing("type").as_string(); + const auto shape = n_srcTopo.fetch_existing("elements/shape").as_string(); + SLIC_ASSERT(type == "unstructured"); + if(shape == "mixed") + { + const conduit::Node &n_shape_map = + n_srcTopo.fetch_existing("elements/shape_map"); + for(int s = 0; s < n_shape_map.number_of_children(); s++) + { + const std::string sname = n_shape_map[s].name(); + const auto id = axom::mir::views::shapeNameToID(sname); + SLIC_ASSERT(id == n_shape_map[s].to_int()); + shape_map[sname] = id; + } + } + else + { + shape_map[shape] = axom::mir::views::shapeNameToID(shape); + } + } + + conduit::Node *n_newTopoPtr = nullptr; + axom::IndexType connOffset = 0, sizesOffset = 0, shapesOffset = 0, + coordOffset = 0; + for(axom::IndexType i = 0; i < n; i++) + { + const conduit::Node &n_topologies = + inputs[i].m_input->fetch_existing("topologies"); + const conduit::Node &n_srcTopo = n_topologies[0]; + + const std::string srcShape = + n_srcTopo.fetch_existing("elements/shape").as_string(); + const conduit::Node &n_srcConn = + n_srcTopo.fetch_existing("elements/connectivity"); + const conduit::Node &n_srcSizes = + n_srcTopo.fetch_existing("elements/sizes"); + + // Make all of the elements the first time. + if(i == 0) + { + bputils::ConduitAllocateThroughAxom c2a; + + conduit::Node &n_newTopo = n_newTopologies[n_srcTopo.name()]; + n_newTopoPtr = n_newTopologies.fetch_ptr(n_srcTopo.name()); + n_newTopo["type"] = "unstructured"; + n_newTopo["coordset"] = n_srcTopo["coordset"].as_string(); + + conduit::Node &n_newConn = n_newTopo["elements/connectivity"]; + n_newConn.set_allocator(c2a.getConduitAllocatorID()); + n_newConn.set(conduit::DataType(n_srcConn.dtype().id(), totalConnLen)); + + conduit::Node &n_newSizes = n_newTopo["elements/sizes"]; + n_newSizes.set_allocator(c2a.getConduitAllocatorID()); + n_newSizes.set(conduit::DataType(n_srcSizes.dtype().id(), totalZones)); + + conduit::Node &n_newOffsets = n_newTopo["elements/offsets"]; + n_newOffsets.set_allocator(c2a.getConduitAllocatorID()); + n_newOffsets.set(conduit::DataType(n_srcConn.dtype().id(), totalZones)); + + if(shape_map.size() > 1) + { + n_newTopo["elements/shape"] = "mixed"; + + // Build a new shape map in the new topology. + conduit::Node &n_shape_map = n_newTopo["elements/shape_map"]; + for(auto it = shape_map.begin(); it != shape_map.end(); it++) + n_shape_map[it->first] = it->second; + + conduit::Node &n_newShapes = n_newTopo["elements/shapes"]; + n_newShapes.set_allocator(c2a.getConduitAllocatorID()); + n_newShapes.set(conduit::DataType(n_srcConn.dtype().id(), totalZones)); + } + else + { + n_newTopo["elements/shape"] = shape_map.begin()->first; + } + } + + // Copy this input's connectivity into the new topology. + axom::mir::views::IndexNode_to_ArrayView(n_srcConn, [&](auto srcConnView) { + using ConnType = typename decltype(srcConnView)::value_type; + conduit::Node &n_newConn = + n_newTopoPtr->fetch_existing("elements/connectivity"); + auto connView = bputils::make_array_view(n_newConn); + + // Copy all sizes from the input. + mergeTopology_copy(inputs[i].m_nodeMapView, + connOffset, + coordOffset, + connView, + srcConnView); + + connOffset += srcConnView.size(); + coordOffset += countNodes(inputs, static_cast(i)); + }); + + // Copy this input's sizes into the new topology. + axom::mir::views::IndexNode_to_ArrayView(n_srcSizes, [&](auto srcSizesView) { + using ConnType = typename decltype(srcSizesView)::value_type; + conduit::Node &n_newSizes = + n_newTopoPtr->fetch_existing("elements/sizes"); + auto sizesView = bputils::make_array_view(n_newSizes); + + mergeTopology_copy_sizes(sizesOffset, sizesView, srcSizesView); + + sizesOffset += srcSizesView.size(); + }); + + // Copy shape information if it exists. + if(n_srcTopo.has_path("elements/shapes")) + { + const conduit::Node &n_srcShapes = + n_srcTopo.fetch_existing("elements/shapes"); + + axom::mir::views::IndexNode_to_ArrayView( + n_srcShapes, + [&](auto srcShapesView) { + using ConnType = typename decltype(srcShapesView)::value_type; + conduit::Node &n_newShapes = + n_newTopoPtr->fetch_existing("elements/shapes"); + auto shapesView = bputils::make_array_view(n_newShapes); + // Copy all sizes from the input. + mergeTopology_copy_shapes(shapesOffset, shapesView, srcShapesView); + shapesOffset += srcShapesView.size(); + }); + } + else + { + // Fill in shape information. There is no source shape information. Use + // sizes to get the number of zones. + const conduit::Node &n_srcSizes = + n_srcTopo.fetch_existing("elements/sizes"); + axom::IndexType nz = n_srcSizes.dtype().number_of_elements(); + conduit::Node &n_newShapes = + n_newTopoPtr->fetch_existing("elements/shapes"); + axom::mir::views::IndexNode_to_ArrayView(n_newShapes, [&](auto shapesView) { + const int shapeId = axom::mir::views::shapeNameToID(srcShape); + mergeTopology_default_shapes(shapesOffset, shapesView, nz, shapeId); + shapesOffset += nz; + }); + } + } + + // Make new offsets from the sizes. + conduit::Node &n_newSizes = n_newTopoPtr->fetch_existing("elements/sizes"); + axom::mir::views::IndexNode_to_ArrayView(n_newSizes, [&](auto sizesView) { + using ConnType = typename decltype(sizesView)::value_type; + conduit::Node &n_newOffsets = + n_newTopoPtr->fetch_existing("elements/offsets"); + auto offsetsView = bputils::make_array_view(n_newOffsets); + axom::exclusive_scan(sizesView, offsetsView); + }); + } + + /*! + * \brief Assist copying topology connectivity to the merged topology. + * + * \param nodeMapView The node map. + * \param connOffset The write offset in the new connectivity. + * \param coordOffset The current mesh's coordinate offset in the new coordinates. + * \param connView The view that contains the new merged connectivity. + * \param srcConnView The view that contains the source connectivity. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void mergeTopology_copy(axom::ArrayView nodeMapView, + axom::IndexType connOffset, + axom::IndexType coordOffset, + ConnectivityView connView, + ConnectivityView srcConnView) const + { + if(nodeMapView.size() > 0) + { + // Copy all zones from the input but map the nodes to new values. + // The supplied nodeMap is assumed to be a mapping from the current + // node connectivity to the merged node connectivity. + axom::for_all( + srcConnView.size(), + AXOM_LAMBDA(axom::IndexType index) { + const auto nodeId = srcConnView[index]; + const auto newNodeId = nodeMapView[nodeId]; + connView[connOffset + index] = newNodeId; + }); + } + else + { + // Copy all zones from the input. Map the nodes to the new values. + axom::for_all( + srcConnView.size(), + AXOM_LAMBDA(axom::IndexType index) { + connView[connOffset + index] = coordOffset + srcConnView[index]; + }); + } + } + + /*! + * \brief Assist copying topology sizes to the merged topology. + * + * \param sizesOffset The write offset for sizes in the new connectivity. + * \param sizesView The view that contains sizes for the new connectivity. + * \param srcSizesView The view that contains sizes for the input mesh. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void mergeTopology_copy_sizes(axom::IndexType sizesOffset, + IntegerView sizesView, + IntegerView srcSizesView) const + { + axom::for_all( + srcSizesView.size(), + AXOM_LAMBDA(axom::IndexType index) { + sizesView[sizesOffset + index] = srcSizesView[index]; + }); + } + + /*! + * \brief Copy shapes from the source mesh to the merged mesh. + * + * \param shapesOffset The write offset for the shapes. + * \param shapesView The view that exposes shapes for the merged mesh. + * \param srcShapesView The view that exposes shapes for the source mesh. + */ + template + void mergeTopology_copy_shapes(axom::IndexType shapesOffset, + IntegerView shapesView, + IntegerView srcShapesView) const + { + axom::for_all( + srcShapesView.size(), + AXOM_LAMBDA(axom::IndexType index) { + shapesView[shapesOffset + index] = srcShapesView[index]; + }); + } + + /*! + * \brief Set shapes in the merged mesh to a specific shape. + * + * \param shapesOffset The write offset for the shapes. + * \param shapesView The view that exposes shapes for the merged mesh. + * \param srcShapesView The view that exposes shapes for the source mesh. + */ + template + void mergeTopology_default_shapes(axom::IndexType shapesOffset, + IntegerView shapesView, + axom::IndexType nzones, + int shapeId) const + { + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + shapesView[shapesOffset + index] = shapeId; + }); + } + + /*! + * \brief Merge fields that exist on the various mesh inputs. Zero-fill values + * where a field does not exist in an input. + * + * \param inputs A vector of inputs to be merged. + * \param[out] output The node that will contain the output mesh. + */ + void mergeFields(const std::vector &inputs, conduit::Node &output) const + { + namespace bputils = axom::mir::utilities::blueprint; + AXOM_ANNOTATE_SCOPE("mergeFields"); + axom::IndexType totalNodes = countNodes(inputs); + axom::IndexType totalConnLen = 0, totalZones = 0; + countZones(inputs, totalConnLen, totalZones); + const axom::IndexType n = static_cast(inputs.size()); + + // Determine whether any inputs have fields. + bool hasFields = false; + for(axom::IndexType i = 0; i < n; i++) + { + hasFields |= inputs[i].m_input->has_child("fields"); + } + + if(hasFields) + { + // Make field information in case some inputs do not have the field. + std::map fieldInfo; + for(axom::IndexType i = 0; i < n; i++) + { + if(inputs[i].m_input->has_child("fields")) + { + const conduit::Node &n_fields = + inputs[i].m_input->fetch_existing("fields"); + for(conduit::index_t c = 0; c < n_fields.number_of_children(); c++) + { + const conduit::Node &n_field = n_fields[c]; + const conduit::Node &n_values = n_field.fetch_existing("values"); + FieldInformation fi; + fi.topology = n_field.fetch_existing("topology").as_string(); + fi.association = n_field.fetch_existing("association").as_string(); + if(n_values.number_of_children() > 0) + { + for(conduit::index_t comp = 0; comp < n_values.number_of_children(); + comp++) + { + fi.components.push_back(n_values[comp].name()); + fi.dtype = n_values[comp].dtype().id(); + } + } + else + { + fi.dtype = n_values.dtype().id(); + } + fieldInfo[n_field.name()] = fi; + } + } + } + + // Make new fields + bputils::ConduitAllocateThroughAxom c2a; + conduit::Node &n_newFields = output["fields"]; + for(auto it = fieldInfo.begin(); it != fieldInfo.end(); it++) + { + conduit::Node &n_newField = n_newFields[it->first]; + n_newField["association"] = it->second.association; + n_newField["topology"] = it->second.topology; + conduit::Node &n_values = n_newField["values"]; + if(it->second.components.empty()) + { + // Scalar + conduit::Node &n_values = n_newField["values"]; + n_values.set_allocator(c2a.getConduitAllocatorID()); + const std::string srcPath("fields/" + it->first + "/values"); + if(it->second.association == "element") + { + n_values.set(conduit::DataType(it->second.dtype, totalZones)); + copyZonal(inputs, n_values, srcPath); + } + else if(it->second.association == "vertex") + { + n_values.set(conduit::DataType(it->second.dtype, totalNodes)); + copyNodal(inputs, n_values, srcPath); + } + } + else + { + // Vector + for(size_t ci = 0; ci < it->second.components.size(); ci++) + { + conduit::Node &n_comp = n_values[it->second.components[ci]]; + n_comp.set_allocator(c2a.getConduitAllocatorID()); + const std::string srcPath("fields/" + it->first + "/values/" + + it->second.components[ci]); + if(it->second.association == "element") + { + n_comp.set(conduit::DataType(it->second.dtype, totalZones)); + copyZonal(inputs, n_comp, srcPath); + } + else if(it->second.association == "vertex") + { + n_comp.set(conduit::DataType(it->second.dtype, totalNodes)); + copyNodal(inputs, n_comp, srcPath); + } + } + } + } + } + } + + /** + * \brief Copy zonal field data into a Conduit node. + * + * \param inputs A vector of inputs to be merged. + * \param[out] n_values The node will be populated with data values from the field inputs. + * \param srcPath The path to the source data in each input node. + */ + void copyZonal(const std::vector &inputs, + conduit::Node &n_values, + const std::string &srcPath) const + { + axom::IndexType offset = 0; + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nzones = countZones(inputs, i); + + if(inputs[i].m_input->has_path(srcPath)) + { + const conduit::Node &n_src_values = + inputs[i].m_input->fetch_existing(srcPath); + axom::mir::views::Node_to_ArrayView( + n_values, + n_src_values, + [&](auto destView, auto srcView) { + copyZonal_copy(nzones, offset, destView, srcView); + }); + } + else + { + axom::mir::views::Node_to_ArrayView(n_values, [&](auto destView) { + fillValues(nzones, offset, destView); + }); + } + offset += nzones; + } + } + + /*! + * \brief Copy zonal data from src to dest. + * + * \param nzones The number of zones. + * \param offset The current write offset. + * \param destView The view that exposes the new merged field. + * \param srcView The view that exposes the source field. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void copyZonal_copy(axom::IndexType nzones, + axom::IndexType offset, + DestView destView, + SrcView srcView) const + { + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + destView[offset + index] = srcView[index]; + }); + } + + /*! + * \brief Fill data in dest. + * + * \param nvalues The number of values. + * \param offset The current write offset. + * \param destView The view that exposes the new merged field. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void fillValues(axom::IndexType nvalues, + axom::IndexType offset, + DestView destView) const + { + axom::for_all( + nvalues, + AXOM_LAMBDA(axom::IndexType index) { destView[offset + index] = 0; }); + } + + /** + * \brief Copy nodal field data into a Conduit node. + * + * \param inputs A vector of inputs to be merged. + * \param[out] n_values The node will be populated with data values from the field inputs. + * \param srcPath The path to the source data in each input node. + */ + void copyNodal(const std::vector &inputs, + conduit::Node &n_values, + const std::string &srcPath) const + { + axom::IndexType offset = 0; + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nnodes = countNodes(inputs, i); + + if(inputs[i].m_input->has_path(srcPath)) + { + const conduit::Node &n_src_values = + inputs[i].m_input->fetch_existing(srcPath); + + axom::mir::views::Node_to_ArrayView(n_src_values, + n_values, + [&](auto srcView, auto destView) { + copyNodal_copy( + inputs[i].m_nodeSliceView, + nnodes, + offset, + destView, + srcView); + }); + } + else + { + axom::mir::views::Node_to_ArrayView(n_values, [&](auto destView) { + fillValues(nnodes, offset, destView); + }); + } + offset += nnodes; + } + } + + /*! + * \brief Copy nodal data from src to dest. + * + * \param nodeSliceView The nodes we're pulling out (if populated). + * \param nnodes The number of nodes. + * \param offset The current write offset. + * \param destView The view that exposes the new merged field. + * \param srcView The view that exposes the source field. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void copyNodal_copy(axom::ArrayView nodeSliceView, + axom::IndexType nnodes, + axom::IndexType offset, + SrcViewType destView, + DestViewType srcView) const + { + if(nodeSliceView.empty()) + { + axom::for_all( + nnodes, + AXOM_LAMBDA(axom::IndexType index) { + destView[offset + index] = srcView[index]; + }); + } + else + { + axom::for_all( + nodeSliceView.size(), + AXOM_LAMBDA(axom::IndexType index) { + const auto nodeId = nodeSliceView[index]; + destView[offset + index] = srcView[nodeId]; + }); + } + } + + /** + * \brief Merge matsets that exist on the various mesh inputs. + * + * \param inputs A vector of inputs to be merged. + * \param[out] output The node that will contain the output mesh. + */ + void mergeMatset(const std::vector &inputs, conduit::Node &output) const + { + AXOM_ANNOTATE_SCOPE("mergeMatset"); + namespace bputils = axom::mir::utilities::blueprint; + bputils::ConduitAllocateThroughAxom c2a; + + // Make a pass through the inputs and make a list of the material names. + bool hasMatsets = false, defaultMaterial = false; + int nmats = 0; + std::map allMats; + std::string matsetName, topoName; + for(size_t i = 0; i < inputs.size(); i++) + { + if(inputs[i].m_input->has_path("matsets")) + { + conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); + conduit::Node &n_matset = n_matsets[0]; + matsetName = n_matset.name(); + topoName = n_matset.fetch_existing("topology").as_string(); + auto matInfo = axom::mir::views::materials(n_matset); + for(const auto &info : matInfo) + { + if(allMats.find(info.name) == allMats.end()) + { + allMats[info.name] = nmats++; + } + } + hasMatsets = true; + } + else + { + defaultMaterial = true; + } + } + + if(hasMatsets) + { + // One or more inputs did not have a matset. + if(defaultMaterial) allMats["default"] = nmats++; + + // Make a pass through the matsets to determine the overall storage. + axom::IndexType totalZones = 0, totalMatCount = 0; + int itype, ftype; + { + AXOM_ANNOTATE_SCOPE("sizes"); + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nzones = countZones(inputs, i); + totalZones += nzones; + + if(inputs[i].m_input->has_path("matsets")) + { + conduit::Node &n_matsets = + inputs[i].m_input->fetch_existing("matsets"); + conduit::Node &n_matset = n_matsets[0]; + axom::IndexType matCount = 0; + axom::mir::views::dispatch_material(n_matset, [&](auto matsetView) { + // Figure out the types to use for storing the data. + using IType = typename decltype(matsetView)::IndexType; + using FType = typename decltype(matsetView)::FloatType; + itype = bputils::cpp2conduit::id; + ftype = bputils::cpp2conduit::id; + + matCount = mergeMatset_count(matsetView, nzones); + }); + totalMatCount += matCount; + } + else + { + totalMatCount += nzones; + } + } + } + + // Allocate + AXOM_ANNOTATE_BEGIN("allocate"); + conduit::Node &n_newMatset = output["matsets/" + matsetName]; + n_newMatset["topology"] = topoName; + conduit::Node &n_volume_fractions = n_newMatset["volume_fractions"]; + n_volume_fractions.set_allocator(c2a.getConduitAllocatorID()); + n_volume_fractions.set(conduit::DataType(ftype, totalMatCount)); + + conduit::Node &n_material_ids = n_newMatset["material_ids"]; + n_material_ids.set_allocator(c2a.getConduitAllocatorID()); + n_material_ids.set(conduit::DataType(itype, totalMatCount)); + + conduit::Node &n_sizes = n_newMatset["sizes"]; + n_sizes.set_allocator(c2a.getConduitAllocatorID()); + n_sizes.set(conduit::DataType(itype, totalZones)); + + conduit::Node &n_offsets = n_newMatset["offsets"]; + n_offsets.set_allocator(c2a.getConduitAllocatorID()); + n_offsets.set(conduit::DataType(itype, totalZones)); + + conduit::Node &n_indices = n_newMatset["indices"]; + n_indices.set_allocator(c2a.getConduitAllocatorID()); + n_indices.set(conduit::DataType(itype, totalMatCount)); + AXOM_ANNOTATE_END("allocate"); + + { + AXOM_ANNOTATE_SCOPE("populate"); + + // Make material_map. + conduit::Node &n_material_map = n_newMatset["material_map"]; + for(auto it = allMats.begin(); it != allMats.end(); it++) + n_material_map[it->first] = it->second; + + // Populate + axom::mir::views::IndexNode_to_ArrayView_same( + n_material_ids, + n_sizes, + n_offsets, + n_indices, + [&](auto materialIdsView, + auto sizesView, + auto offsetsView, + auto indicesView) { + axom::mir::views::FloatNode_to_ArrayView( + n_volume_fractions, + [&](auto volumeFractionsView) { + // Fill in sizes array. + axom::IndexType zOffset = 0; + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nzones = countZones(inputs, i); + + if(inputs[i].m_input->has_child("matsets")) + { + conduit::Node &n_matsets = + inputs[i].m_input->fetch_existing("matsets"); + conduit::Node &n_matset = n_matsets[0]; + + axom::mir::views::dispatch_material( + n_matset, + [&](auto matsetView) { + mergeMatset_sizes(matsetView, sizesView, nzones, zOffset); + }); + } + else + { + mergeMatset_sizes1(sizesView, nzones, zOffset); + } + zOffset += nzones; + } + // Make offsets. + axom::exclusive_scan(sizesView, offsetsView); + + // Make indices. + mergeMatset_indices(indicesView, totalMatCount); + + // Fill in material info. + zOffset = 0; + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nzones = countZones(inputs, i); + + if(inputs[i].m_input->has_child("matsets")) + { + conduit::Node &n_matsets = + inputs[i].m_input->fetch_existing("matsets"); + conduit::Node &n_matset = n_matsets[0]; + + axom::mir::views::dispatch_material(n_matset, + [&](auto matsetView) { + mergeMatset_copy( + n_matset, + allMats, + materialIdsView, + offsetsView, + volumeFractionsView, + matsetView, + nzones, + zOffset); + }); + } + else + { + const int dmat = allMats["default"]; + mergeMatset_default(materialIdsView, + offsetsView, + volumeFractionsView, + dmat, + nzones, + zOffset); + } + zOffset += nzones; + } + }); + }); + } + } // if hasMatsets + } + + /*! + * \brief Assist in counting the total material elements needed for the input matset. + * + * \param matsetView The view that wraps the material data. + * \param nzones The number of zones. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + axom::IndexType mergeMatset_count(MatsetView matsetView, + axom::IndexType nzones) const + { + using reduce_policy = + typename axom::execution_space::reduce_policy; + RAJA::ReduceSum matCount_reduce(0); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto nmats = matsetView.numberOfMaterials(zoneIndex); + matCount_reduce += nmats; + }); + return matCount_reduce.get(); + } + + /*! + * \brief Assist in setting sizes for the new matset. + * + * \param matsetView The view that wraps the material data. + * \param sizesView The view that exposes the new matset sizes. + * \param nzones The number of zones in the current input. + * \param zOffset The current offset in the merged output. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void mergeMatset_sizes(const MatsetView matsetView, + IntegerArrayView sizesView, + axom::IndexType nzones, + axom::IndexType zOffset) const + { + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + sizesView[zOffset + zoneIndex] = matsetView.numberOfMaterials(zoneIndex); + }); + } + + /*! + * \brief Assist in setting sizes for the new matset. + * + * \param sizesView The view that exposes the new matset sizes. + * \param nzones The number of zones in the current input. + * \param zOffset The current offset in the merged output. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void mergeMatset_sizes1(IntegerArrayView sizesView, + axom::IndexType nzones, + axom::IndexType zOffset) const + { + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + sizesView[zOffset + zoneIndex] = 1; + }); + } + + /*! + * \brief Assist in setting indices for the new matset. + * + * \param indicesView The view that exposes the new matset indices. + * \param totalMatCount The total number of elements in the material_ids or volume_fractions array. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void mergeMatset_indices(IntegerArrayView indicesView, + axom::IndexType totalMatCount) const + { + axom::for_all( + totalMatCount, + AXOM_LAMBDA(axom::IndexType index) { indicesView[index] = index; }); + } + + /*! + * \brief Assist in copying matset data into the new merged matset. + * + * \param n_matset A Node that contains the matset. + * \param allMats A map of material numbers to merged material numbers. + * \param materialIdsView The view that exposes the merged material ids. + * \param offsetsView The view that exposes the merged material offsets. + * \param volumeFractionsView The view that exposes the merged volume fractions. + * \param matsetView The matset view that contains the source matset data. + * \param nzones The number of zones in the current input. + * \param zOffset The current offset in the merged output. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void mergeMatset_copy(const conduit::Node &n_matset, + const std::map &allMats, + IntegerView materialIdsView, + IntegerView offsetsView, + FloatView volumeFractionsView, + MatsetView matsetView, + axom::IndexType nzones, + axom::IndexType zOffset) const + { + using IDList = typename decltype(matsetView)::IDList; + using VFList = typename decltype(matsetView)::VFList; + using MatID = typename decltype(matsetView)::IndexType; + + // Make some maps for renumbering material numbers. + const auto localMaterialMap = axom::mir::views::materials(n_matset); + std::map localToAll; + for(const auto &info : localMaterialMap) + { + const auto it = allMats.find(info.name); + SLIC_ASSERT(it != allMats.end()); + MatID matno = it->second; + localToAll[info.number] = matno; + } + std::vector localVec, allVec; + for(auto it = localToAll.begin(); it != localToAll.end(); it++) + { + localVec.push_back(it->first); + allVec.push_back(it->second); + } + // Put maps on device. + const int allocatorID = axom::execution_space::allocatorID(); + axom::Array local(localVec.size(), localVec.size(), allocatorID); + axom::Array all(allVec.size(), allVec.size(), allocatorID); + axom::copy(local.data(), localVec.data(), sizeof(MatID) * local.size()); + axom::copy(all.data(), allVec.data(), sizeof(MatID) * all.size()); + const auto localView = local.view(); + const auto allView = all.view(); + + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + // Get this zone's materials. + IDList ids; + VFList vfs; + matsetView.zoneMaterials(zoneIndex, ids, vfs); + + // Store the materials in the new material. + const auto zoneStart = offsetsView[zOffset + zoneIndex]; + for(axom::IndexType mi = 0; mi < ids.size(); mi++) + { + const auto destIndex = zoneStart + mi; + volumeFractionsView[destIndex] = vfs[mi]; + + // Get the index of the material number in the local map. + const auto mapIndex = axom::mir::utilities::bsearch(ids[mi], localView); + assert(mapIndex != -1); + // We'll store the all materials number. + const auto allMatno = allView[mapIndex]; + materialIdsView[destIndex] = allMatno; + } + }); + } + + /*! + * \brief Assist setting default material data when the input mesh has no material. + * + * \param materialIdsView The view that exposes the merged material ids. + * \param offsetsView The view that exposes the merged material offsets. + * \param volumeFractionsView The view that exposes the merged volume fractions. + * \param matno The material number to use for these zones. + * \param nzones The number of zones in the current input. + * \param zOffset The current offset in the merged output. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void mergeMatset_default(IntegerView materialIdsView, + IntegerView offsetsView, + FloatView volumeFractionsView, + int matno, + axom::IndexType nzones, + axom::IndexType zOffset) const + { + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zoneStart = offsetsView[zOffset + zoneIndex]; + volumeFractionsView[zoneStart] = 1; + materialIdsView[zoneStart] = matno; + }); + } +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/NodeToZoneRelationBuilder.hpp b/src/axom/mir/utilities/NodeToZoneRelationBuilder.hpp new file mode 100644 index 0000000000..de594e3d10 --- /dev/null +++ b/src/axom/mir/utilities/NodeToZoneRelationBuilder.hpp @@ -0,0 +1,482 @@ +// 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) + +#ifndef AXOM_MIR_NODE_TO_ZONE_RELATION_BUILDER_HPP_ +#define AXOM_MIR_NODE_TO_ZONE_RELATION_BUILDER_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/utilities/utilities.hpp" +#include "axom/mir/utilities/blueprint_utilities.hpp" +#include "axom/mir/views/dispatch_unstructured_topology.hpp" +#include "axom/mir/utilities/MakeUnstructured.hpp" + +#include +#include +#include + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +#include +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +namespace details +{ +/*! + * \brief Build the node to zone relation. + */ +template +struct BuildRelation +{ + /*! + * \brief Given views that contain the nodes and zones, sort the zones using the + * node numbers to produce a list of zones for each node and an offsets array + * that points to the start of each list of zones. + * + * \param[in] nodesView A view that contains the set of all of the nodes in the topology (the connectivity) + * \param[inout] zonesView A view (same size as \a nodesView) that contains the zone number of each node. + * \param[out] sizesView A view that we fill with sizes. + * \param[out] offsetsView A view that we fill with offsets so offsetsView[i] points to the start of the i'th list in \a zonesView. + * + * \note RAJA::sort_pairs can be slow if there are a lot of nodes (depends on ExecSpace too). + */ + static void execute(ViewType nodesView, + ViewType zonesView, + ViewType sizesView, + ViewType offsetsView) + { + AXOM_ANNOTATE_SCOPE("FillZonesAndOffsets"); + assert(nodesView.size() == zonesView.size()); + + using loop_policy = typename axom::execution_space::loop_policy; + using value_type = typename ViewType::value_type; + const int allocatorID = axom::execution_space::allocatorID(); + + // Make a copy of the nodes that we'll use as keys. + const auto n = nodesView.size(); + axom::Array keys(n, n, allocatorID); + auto keysView = keys.view(); + axom::for_all( + n, + AXOM_LAMBDA(axom::IndexType i) { keysView[i] = nodesView[i]; }); + + // Sort the keys, zones in place. This sorts the zonesView which we want for output. + RAJA::sort_pairs(RAJA::make_span(keysView.data(), n), + RAJA::make_span(zonesView.data(), n)); + + // Make a mask array for where differences occur. + axom::Array mask(n, n, allocatorID); + auto maskView = mask.view(); + axom::for_all( + n, + AXOM_LAMBDA(axom::IndexType i) { + maskView[i] = (i >= 1) ? ((keysView[i] != keysView[i - 1]) ? 1 : 0) : 1; + }); + + // Do a scan on the mask array to build an offset array. + axom::Array dest_offsets(n, n, allocatorID); + auto dest_offsetsView = dest_offsets.view(); + axom::exclusive_scan(maskView, dest_offsetsView); + + // Build the offsets to each node's zone ids. + axom::for_all( + offsetsView.size(), + AXOM_LAMBDA(axom::IndexType i) { offsetsView[i] = 0; }); + axom::for_all( + n, + AXOM_LAMBDA(axom::IndexType i) { + if(maskView[i]) + { + offsetsView[dest_offsetsView[i]] = i; + } + }); + + // Compute sizes from offsets. + const value_type totalSize = nodesView.size(); + axom::for_all( + offsetsView.size(), + AXOM_LAMBDA(axom::IndexType i) { + sizesView[i] = (i < offsetsView.size() - 1) + ? (offsetsView[i + 1] - offsetsView[i]) + : (totalSize - offsetsView[i]); + }); + } +}; + +/// Partial specialization for axom::SEQ_EXEC. +template +struct BuildRelation +{ + static void execute(ViewType nodesView, + ViewType zonesView, + ViewType sizesView, + ViewType offsetsView) + { + AXOM_ANNOTATE_SCOPE("FillZonesAndOffsets"); + assert(nodesView.size() == zonesView.size()); + using value_type = typename ViewType::value_type; + using ExecSpace = axom::SEQ_EXEC; + const int allocatorID = execution_space::allocatorID(); + + // Count how many times a node is used. + axom::for_all( + sizesView.size(), + AXOM_LAMBDA(axom::IndexType index) { sizesView[index] = 0; }); + axom::for_all( + nodesView.size(), + AXOM_LAMBDA(axom::IndexType index) { + // Works because ExecSpace=SEQ_EXEC. + sizesView[nodesView[index]]++; + }); + // Make offsets + axom::exclusive_scan(sizesView, offsetsView); + + axom::for_all( + sizesView.size(), + AXOM_LAMBDA(axom::IndexType index) { sizesView[index] = 0; }); + + // Make a copy of zonesView so we can reorganize zonesView. + axom::Array zcopy(zonesView.size(), zonesView.size(), allocatorID); + axom::copy(zcopy.data(), + zonesView.data(), + zonesView.size() * sizeof(value_type)); + auto zcopyView = zcopy.view(); + + // Fill in zonesView, sizesView with each node's zones. + axom::for_all( + nodesView.size(), + AXOM_LAMBDA(axom::IndexType index) { + const auto ni = nodesView[index]; + const auto destOffset = offsetsView[ni] + sizesView[ni]; + zonesView[destOffset] = zcopyView[index]; + // Works because ExecSpace=SEQ_EXEC. + sizesView[ni]++; + }); + } +}; + +} // end namespace details + +/*! + * \brief Build an o2m relation that lets us look up the zones for a node. + * + * \note The zone list for each point is not sorted. + */ +template +class NodeToZoneRelationBuilder +{ +public: + /*! + * \brief Build a node to zone relation and store the resulting O2M relation in the \a relation conduit node. + * + * \param topo The topology for which we're building the O2M relation. + * \param coordset The topology's coordset. + * \param[out] The node that will contain the O2M relation. + */ + void execute(const conduit::Node &topo, + const conduit::Node &coordset, + conduit::Node &relation) + { + const std::string type = topo.fetch_existing("type").as_string(); + + // Get the ID of a Conduit allocator that will allocate through Axom with device allocator allocatorID. + utilities::blueprint::ConduitAllocateThroughAxom c2a; + const int conduitAllocatorID = c2a.getConduitAllocatorID(); + + conduit::Node &n_zones = relation["zones"]; + conduit::Node &n_sizes = relation["sizes"]; + conduit::Node &n_offsets = relation["offsets"]; + n_zones.set_allocator(conduitAllocatorID); + n_sizes.set_allocator(conduitAllocatorID); + n_offsets.set_allocator(conduitAllocatorID); + + if(type == "unstructured") + { + conduit::blueprint::mesh::utils::ShapeType shape(topo); + const conduit::Node &n_connectivity = topo["elements/connectivity"]; + const std::string shapeType = topo["elements/shape"].as_string(); + const auto intTypeId = n_connectivity.dtype().id(); + const auto connSize = n_connectivity.dtype().number_of_elements(); + + // Use the coordset to get the number of nodes. Conduit should be able to do this using only metadata. + const auto nnodes = + conduit::blueprint::mesh::utils::coordset::length(coordset); + + if(shape.is_polyhedral()) + { + views::dispatch_unstructured_polyhedral_topology( + topo, + [&](auto AXOM_UNUSED_PARAM(shape), auto topoView) { + handlePolyhedralView(topoView, + n_zones, + n_sizes, + n_offsets, + nnodes, + intTypeId); + }); + } + else if(shape.is_polygonal() || shapeType == "mixed") + { + const conduit::Node &n_topo_sizes = topo["elements/sizes"]; + const conduit::Node &n_topo_offsets = topo["elements/offsets"]; + + const auto nzones = n_topo_sizes.dtype().number_of_elements(); + + // Allocate Conduit arrays on the device in a data type that matches the connectivity. + n_zones.set(conduit::DataType(intTypeId, connSize)); + n_sizes.set(conduit::DataType(intTypeId, nnodes)); + n_offsets.set(conduit::DataType(intTypeId, nnodes)); + + // Make zones for each node + views::IndexNode_to_ArrayView_same( + n_zones, + n_topo_sizes, + n_topo_offsets, + [&](auto zonesView, auto sizesView, auto offsetsView) { + fillZonesMixed(nzones, zonesView, sizesView, offsetsView); + }); + + views::IndexNode_to_ArrayView_same( + n_connectivity, + n_zones, + n_sizes, + n_offsets, + [&](auto connectivityView, auto zonesView, auto sizesView, auto offsetsView) { + // Make the relation. + using ViewType = decltype(connectivityView); + details::BuildRelation::execute(connectivityView, + zonesView, + sizesView, + offsetsView); + }); + } + else + { + // Shapes are all the same size. + const auto nodesPerShape = shape.indices; + + // Allocate Conduit arrays on the device in a data type that matches the connectivity. + n_zones.set(conduit::DataType(intTypeId, connSize)); + n_sizes.set(conduit::DataType(intTypeId, nnodes)); + n_offsets.set(conduit::DataType(intTypeId, nnodes)); + + views::IndexNode_to_ArrayView_same( + n_connectivity, + n_zones, + n_sizes, + n_offsets, + [&](auto connectivityView, auto zonesView, auto sizesView, auto offsetsView) { + // Make zones for each node + fillZones(zonesView, connSize, nodesPerShape); + + // Make the relation. + using ViewType = decltype(connectivityView); + details::BuildRelation::execute(connectivityView, + zonesView, + sizesView, + offsetsView); + }); + } + } + else + { + // These are all structured topos of some sort. Make an unstructured representation and recurse. + + conduit::Node mesh; + axom::mir::utilities::blueprint::MakeUnstructured::execute( + topo, + coordset, + "newtopo", + mesh); + + // Recurse using the unstructured mesh. + execute(mesh.fetch_existing("topologies/newtopo"), coordset, relation); + } + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + /*! + * \brief Handle a polyhedral view. + * + * \param topoView A polyhedral topology view. + * \param[out] n_zones The new zones node for the relation. + * \param[out] n_sizes The new sizes node for the relation. + * \param[out] n_offsets The new offsets node for the relation. + * \param nnodes The number of nodes in the mesh's coordset. + * \param intTypeId The dtype id for the connectivity. + * \param connSize The length of the connectivity. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void handlePolyhedralView(PHView topoView, + conduit::Node &n_zones, + conduit::Node &n_sizes, + conduit::Node &n_offsets, + axom::IndexType nnodes, + int intTypeId) const + { + using reduce_policy = + typename axom::execution_space::reduce_policy; + utilities::blueprint::ConduitAllocateThroughAxom c2a; + const int conduitAllocatorID = c2a.getConduitAllocatorID(); + const auto allocatorID = axom::execution_space::allocatorID(); + + const auto nzones = topoView.numberOfZones(); + axom::Array sizes(nzones, nzones, allocatorID); + auto sizes_view = sizes.view(); + + // Run through the topology once to do a count of each zone's unique node ids. + RAJA::ReduceSum count(0); + const PHView deviceTopologyView(topoView); + axom::for_all( + topoView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zone = deviceTopologyView.zone(zoneIndex); + const auto uniqueIds = zone.getUniqueIds(); + sizes_view[zoneIndex] = uniqueIds.size(); + count += uniqueIds.size(); + }); + const auto connSize = count.get(); + + // Do a scan on the size array to build an offset array. + axom::Array offsets(nzones, nzones, allocatorID); + auto offsets_view = offsets.view(); + axom::exclusive_scan(sizes_view, offsets_view); + sizes.clear(); + + // Allocate Conduit arrays on the device in a data type that matches the connectivity. + conduit::Node n_conn; + n_conn.set_allocator(conduitAllocatorID); + n_conn.set(conduit::DataType(intTypeId, connSize)); + + n_zones.set(conduit::DataType(intTypeId, connSize)); + n_sizes.set(conduit::DataType(intTypeId, nnodes)); + n_offsets.set(conduit::DataType(intTypeId, nnodes)); + + views::IndexNode_to_ArrayView_same( + n_conn, + n_zones, + n_sizes, + n_offsets, + [&](auto connectivityView, auto zonesView, auto sizesView, auto offsetsView) { + fillZonesPH(topoView, connectivityView, zonesView, offsets_view); + + // Make the relation. + using ViewType = decltype(connectivityView); + details::BuildRelation::execute(connectivityView, + zonesView, + sizesView, + offsetsView); + }); + } + + /*! + * \brief Fill in the zone numbers for each mixed-sized zone. + * + * \param topoView The topology view for the PH mesh. + * \param connectivityView The view that contains the connectivity. + * \param zonesView The view that will contain the zone ids. + * \param offsetsView The view that contains the offsets. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void fillZonesPH(const TopologyView &topoView, + IntegerView connectivityView, + IntegerView zonesView, + OffsetsView offsets_view) const + { + // Run through the data one more time to build the nodes and zones arrays. + const TopologyView deviceTopologyView(topoView); + axom::for_all( + topoView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zone = deviceTopologyView.zone(zoneIndex); + const auto uniqueIds = zone.getUniqueIds(); + auto destIdx = offsets_view[zoneIndex]; + for(axom::IndexType i = 0; i < uniqueIds.size(); i++, destIdx++) + { + connectivityView[destIdx] = uniqueIds[i]; + zonesView[destIdx] = zoneIndex; + } + }); + } + + /*! + * \brief Fill in the zone numbers for each mixed-sized zone. + * + * \param nzones The number of zones. + * \param zonesView The view that will contain the zone ids. + * \param sizesView The view that contains the sizes. + * \param offsetsView The view that contains the offsets. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void fillZonesMixed(axom::IndexType nzones, + IntegerView zonesView, + IntegerView sizesView, + IntegerView offsetsView) const + { + using DataType = typename decltype(zonesView)::value_type; + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + for(DataType i = 0; i < sizesView[zoneIndex]; i++) + zonesView[offsetsView[zoneIndex] + i] = zoneIndex; + }); + } + + /*! + * \brief Fill in the zone numbers for each node in the connectivity. + * + * \param zonesView The view that will contain the zone ids. + * \param connSize The length of the connectivity. + * \param nodesPerShape The number of nodes per shape. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + void fillZones(IntegerView zonesView, + axom::IndexType connSize, + axom::IndexType nodesPerShape) const + { + axom::for_all( + connSize, + AXOM_LAMBDA(axom::IndexType index) { + zonesView[index] = index / nodesPerShape; + }); + } +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/RecenterField.hpp b/src/axom/mir/utilities/RecenterField.hpp new file mode 100644 index 0000000000..e75408c61e --- /dev/null +++ b/src/axom/mir/utilities/RecenterField.hpp @@ -0,0 +1,158 @@ +// 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) + +#ifndef AXOM_MIR_RECENTER_FIELD_HPP_ +#define AXOM_MIR_RECENTER_FIELD_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/views/NodeArrayView.hpp" +#include "axom/mir/utilities/utilities.hpp" + +#include +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/*! + * \brief Convert a field with one association type to a field of another association type using an o2mrelation. + * + * \tparam ExecSpace The execution space where the algorithm runs. + */ +template +class RecenterField +{ +public: + /*! + * \brief Convert the input field to a different association type using the o2mrelation and store the new field in the output field. + * + * \param field The input field. + * \param relation The node that contains an o2mrelation with nodes to zones. + * \param outField[out] The node that will contain the new field. + */ + void execute(const conduit::Node &field, + const conduit::Node &relation, + conduit::Node &outField) const + { + const std::string association = + field.fetch_existing("association").as_string(); + + // Assume that we're flipping the association. + outField["association"] = (association == "element") ? "vertex" : "element"; + outField["topology"] = field["topology"]; + + // Make output values. + const conduit::Node &n_values = field["values"]; + if(n_values.number_of_children() > 0) + { + for(conduit::index_t c = 0; c < n_values.number_of_children(); c++) + { + const conduit::Node &n_comp = n_values[c]; + recenterSingleComponent(n_comp, + relation, + outField["values"][n_comp.name()]); + } + } + else + { + recenterSingleComponent(n_values, relation, outField["values"]); + } + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + /*! + * \brief Recenter a single field component. + * + * \param relation The node that contains an o2mrelation with nodes to zones. + * \param n_comp The input component. + * \param n_out[out] The node that will contain the new field. + */ + void recenterSingleComponent(const conduit::Node &n_comp, + const conduit::Node &relation, + conduit::Node &n_out) const + { + // Get the data field for the o2m relation. + const auto data_paths = conduit::blueprint::o2mrelation::data_paths(relation); + + // Use the o2mrelation to average data from n_comp to the n_out. + const conduit::Node &n_relvalues = relation[data_paths[0]]; + const conduit::Node &n_sizes = relation["sizes"]; + const conduit::Node &n_offsets = relation["offsets"]; + views::IndexNode_to_ArrayView_same( + n_relvalues, + n_sizes, + n_offsets, + [&](auto relView, auto sizesView, auto offsetsView) { + // Allocate Conduit data through Axom. + const auto relSize = sizesView.size(); + utilities::blueprint::ConduitAllocateThroughAxom c2a; + n_out.set_allocator(c2a.getConduitAllocatorID()); + n_out.set(conduit::DataType(n_comp.dtype().id(), relSize)); + + views::Node_to_ArrayView_same(n_out, + n_comp, + [&](auto outView, auto compView) { + recenterSingleComponentImpl(relView, + sizesView, + offsetsView, + outView, + compView); + }); + }); + } + + /*! + * \brief Recenter a single field component. + * + * \param relView The view that contains the ids for the relation. + * \param sizesView The view that contains the sizes for the relation. + * \param offsetsView The view that contains the offsets for the relation. + * \param outView The view that contains the out data. + * \param compView The view that contains the source data. + */ + template + void recenterSingleComponentImpl(IndexView relView, + IndexView sizesView, + IndexView offsetsView, + DataView outView, + DataView compView) const + { + using Precision = typename DataView::value_type; + using AccumType = + typename axom::mir::utilities::accumulation_traits::value_type; + const auto relSize = sizesView.size(); + axom::for_all( + relSize, + AXOM_LAMBDA(axom::IndexType relIndex) { + const auto n = static_cast(sizesView[relIndex]); + const auto offset = offsetsView[relIndex]; + + AccumType sum {}; + for(axom::IndexType i = 0; i < n; i++) + { + const auto id = relView[offset + i]; + sum += static_cast(compView[id]); + } + + outView[relIndex] = static_cast(sum / n); + }); + } +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/SelectedZones.hpp b/src/axom/mir/utilities/SelectedZones.hpp new file mode 100644 index 0000000000..2b2bd36443 --- /dev/null +++ b/src/axom/mir/utilities/SelectedZones.hpp @@ -0,0 +1,151 @@ +// 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) +#ifndef AXOM_MIR_SELECTED_ZONES_HPP_ +#define AXOM_MIR_SELECTED_ZONES_HPP_ + +#include "axom/core.hpp" + +#include + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +namespace axom +{ +namespace mir +{ +/*! + * \brief This class provides a kind of schema over options, as well + * as default values, and some utilities functions. + */ +template +class SelectedZones +{ +public: + /*! + * \brief Constructor + * + * \param nzones The total number of zones in the associated topology. + * \param options The node that contains the clipping options. + */ + SelectedZones(axom::IndexType nzones, const conduit::Node &options) + : m_selectedZones() + , m_selectedZonesView() + { + buildSelectedZones(nzones, options); + } + + /*! + * \brief Return a view that contains the list of selected zone ids for the mesh. + * \return A view that contains the list of selected zone ids for the mesh. + */ + const axom::ArrayView &view() const + { + return m_selectedZonesView; + } + +// The following members are protected (unless using CUDA) +#if !defined(__CUDACC__) +protected: +#endif + + /*! + * \brief The options may contain a "selectedZones" member that is a list of zones + * that will be operated on. If such an array is present, copy and sort it. + * If the zone list is not present, make an array that selects every zone. + * + * \note selectedZones should contain local zone numbers, which in the case of + * strided-structured indexing are the [0..n) zone numbers that exist only + * within the selected window. + */ + void buildSelectedZones(axom::IndexType nzones, const conduit::Node &options) + { + const auto allocatorID = axom::execution_space::allocatorID(); + + if(options.has_child("selectedZones")) + { + // Store the zone list in m_selectedZones. + int badValueCount = 0; + views::IndexNode_to_ArrayView(options["selectedZones"], [&](auto zonesView) { + // It probably does not make sense to request more zones than we have in the mesh. + SLIC_ASSERT(zonesView.size() <= nzones); + + badValueCount = buildSelectedZones(zonesView, nzones); + }); + + if(badValueCount > 0) + { + SLIC_ERROR("Out of range selectedZones values."); + } + } + else + { + // Select all zones. + m_selectedZones = axom::Array(nzones, nzones, allocatorID); + auto szView = m_selectedZonesView = m_selectedZones.view(); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { szView[zoneIndex] = zoneIndex; }); + } + } + + /*! + * \brief Help build the selected zones, converting them to axom::IndexType and sorting them. + * + * \param zonesView The view that contains the source zone ids. + * \param nzones The number of zones in the mesh. + * + * \return The number of invalid zone ids. + * + * \note This method was broken out into a template member method since nvcc + * would not instantiate the lambda for axom::for_all() from an anonymous + * lambda. + */ + template + int buildSelectedZones(ZonesViewType zonesView, axom::IndexType nzones) + { + using loop_policy = typename axom::execution_space::loop_policy; + using reduce_policy = + typename axom::execution_space::reduce_policy; + + const auto allocatorID = axom::execution_space::allocatorID(); + m_selectedZones = axom::Array(zonesView.size(), + zonesView.size(), + allocatorID); + auto szView = m_selectedZonesView = m_selectedZones.view(); + axom::for_all( + szView.size(), + AXOM_LAMBDA(axom::IndexType index) { szView[index] = zonesView[index]; }); + + // Check that the selected zone values are in range. + RAJA::ReduceSum errReduce(0); + axom::for_all( + szView.size(), + AXOM_LAMBDA(axom::IndexType index) { + const int err = (szView[index] < 0 || szView[index] >= nzones) ? 1 : 0; + errReduce += err; + }); + + // Make sure the selectedZones are sorted. + RAJA::sort(RAJA::make_span(szView.data(), szView.size())); + + return errReduce.get(); + } + +// The following members are protected (unless using CUDA) +#if !defined(__CUDACC__) +protected: +#endif + + axom::Array m_selectedZones; // Storage for a list of selected zone ids. + axom::ArrayView m_selectedZonesView; +}; + +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/ZoneListBuilder.hpp b/src/axom/mir/utilities/ZoneListBuilder.hpp new file mode 100644 index 0000000000..faaa478c32 --- /dev/null +++ b/src/axom/mir/utilities/ZoneListBuilder.hpp @@ -0,0 +1,335 @@ +// 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) + +#ifndef AXOM_MIR_ZONELIST_BUILDER_HPP +#define AXOM_MIR_ZONELIST_BUILDER_HPP + +#include "axom/core.hpp" +#include "axom/mir.hpp" + +#include + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +/*! + * \brief This struct builds lists of clean and mixed zones using the input topology and matset views. + * + * \tparam ExecSpace The execution space where the algorithm will run. + * \tparam TopologyView The topology view type on which the algorithm will run. + * \tparam MatsetView The matset view type on which the algorithm will run. + */ +template +class ZoneListBuilder +{ +public: + using SelectedZonesView = axom::ArrayView; + using ZoneType = typename TopologyView::ShapeType; + + /*! + * \brief Constructor + * + * \param topoView The topology view to use for creating the zone lists. + * \param matsetView The matset view to use for creating the zone lists. + */ + ZoneListBuilder(const TopologyView &topoView, const MatsetView &matsetView) + : m_topologyView(topoView) + , m_matsetView(matsetView) + { } + + /*! + * \brief Build the list of clean and mixed zones using the number of materials + * per zone, maxed to the nodes. + * + * \param nnodes The number of nodes in the topology's coordset. + * \param[out] cleanIndices An array that will contain the list of clean material zone ids. + * \param[out] mixedIndices An array that will contain the list of mixed material zone ids. + * + * \note The clean/mixed index arrays are not strictly what could be determined by the matset alone. + * We figure out which nodes touch multiple materials. Then we iterate over the zones and + * those that touch only nodes that have 1 material are marked clean, otherwise they are + * considered mixed as we might have to split those zones. + */ + void execute(axom::IndexType nnodes, + axom::Array &cleanIndices, + axom::Array &mixedIndices) const + { + using atomic_policy = + typename axom::execution_space::atomic_policy; + using reduce_policy = + typename axom::execution_space::reduce_policy; + + AXOM_ANNOTATE_SCOPE("ZoneListBuilder"); + const int allocatorID = axom::execution_space::allocatorID(); + + AXOM_ANNOTATE_BEGIN("nMatsPerNode"); + axom::Array nMatsPerNode(nnodes, nnodes, allocatorID); + nMatsPerNode.fill(0); + auto nMatsPerNodeView = nMatsPerNode.view(); + + // Determine max number of materials a node might touch. + MatsetView deviceMatsetView(m_matsetView); + const TopologyView deviceTopologyView(m_topologyView); + axom::for_all( + m_topologyView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zone = deviceTopologyView.zone(zoneIndex); + const int nmats = deviceMatsetView.numberOfMaterials(zoneIndex); + const auto nnodesThisZone = zone.numberOfNodes(); + int *nodeData = nMatsPerNodeView.data(); + for(axom::IndexType i = 0; i < nnodesThisZone; i++) + { + const auto nodeId = zone.getId(i); + int *nodePtr = nodeData + nodeId; + RAJA::atomicMax(nodePtr, nmats); + } + }); + AXOM_ANNOTATE_END("nMatsPerNode"); + + // Now, mark all zones that have 1 mat per node as clean. + AXOM_ANNOTATE_BEGIN("mask"); + const auto nzones = m_topologyView.numberOfZones(); + axom::Array mask(nzones, nzones, allocatorID); + auto maskView = mask.view(); + RAJA::ReduceSum mask_reduce(0); + axom::for_all( + m_topologyView.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zone = deviceTopologyView.zone(zoneIndex); + + bool clean = true; + const axom::IndexType nnodesThisZone = zone.numberOfNodes(); + for(axom::IndexType i = 0; i < nnodesThisZone && clean; i++) + { + const auto nodeId = zone.getId(i); + clean &= (nMatsPerNodeView[nodeId] == 1); + } + + const int ival = clean ? 1 : 0; + maskView[zoneIndex] = ival; + mask_reduce += ival; + }); + AXOM_ANNOTATE_END("mask"); + + const int nClean = mask_reduce.get(); + if(nClean > 0) + { + AXOM_ANNOTATE_BEGIN("offsets"); + axom::Array maskOffsets(nzones, nzones, allocatorID); + auto maskOffsetsView = maskOffsets.view(); + axom::exclusive_scan(maskView, maskOffsetsView); + AXOM_ANNOTATE_END("offsets"); + + // Make the output cleanIndices array. + AXOM_ANNOTATE_BEGIN("cleanIndices"); + cleanIndices = axom::Array(nClean, nClean, allocatorID); + auto cleanIndicesView = cleanIndices.view(); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + if(maskView[index] > 0) + { + cleanIndicesView[maskOffsetsView[index]] = index; + } + }); + AXOM_ANNOTATE_END("cleanIndices"); + + // Make the mixedIndices array. + AXOM_ANNOTATE_BEGIN("mixedIndices"); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + maskView[index] = (maskView[index] == 1) ? 0 : 1; + }); + axom::exclusive_scan(maskView, maskOffsetsView); + const int nMixed = nzones - nClean; + mixedIndices = axom::Array(nMixed, nMixed, allocatorID); + auto mixedIndicesView = mixedIndices.view(); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + if(maskView[index] > 0) + { + mixedIndicesView[maskOffsetsView[index]] = index; + } + }); + AXOM_ANNOTATE_END("mixedIndices"); + } + else + { + AXOM_ANNOTATE_SCOPE("mixedIndices"); + cleanIndices = axom::Array(); + + // There were no clean, so it must all be mixed. + mixedIndices = axom::Array(nzones, nzones, allocatorID); + auto mixedIndicesView = mixedIndices.view(); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { mixedIndicesView[index] = index; }); + } + } + + /*! + * \brief Build the list of clean and mixed zones using the number of materials + * per zone, maxed to the nodes. Limit the number of zones. + * + * \param nnodes The number of nodes in the topology's coordset. + * \param selectedZonesView A view containing the zone indices we're considering. + * \param[out] cleanIndices An array that will contain the list of clean material zone ids. + * \param[out] mixedIndices An array that will contain the list of mixed material zone ids. + * + * \note The clean/mixed index arrays are not strictly what could be determined by the matset alone. + * We figure out which nodes touch multiple materials. Then we iterate over the zones and + * those that touch only nodes that have 1 material are marked clean, otherwise they are + * considered mixed as we might have to split those zones. + */ + void execute(axom::IndexType nnodes, + const SelectedZonesView &selectedZonesView, + axom::Array &cleanIndices, + axom::Array &mixedIndices) const + { + using atomic_policy = + typename axom::execution_space::atomic_policy; + using reduce_policy = + typename axom::execution_space::reduce_policy; + + AXOM_ANNOTATE_SCOPE("ZoneListBuilder"); + SLIC_ASSERT(selectedZonesView.size() > 0); + + const int allocatorID = axom::execution_space::allocatorID(); + + AXOM_ANNOTATE_BEGIN("nMatsPerNode"); + axom::Array nMatsPerNode(nnodes, nnodes, allocatorID); + nMatsPerNode.fill(0); + auto nMatsPerNodeView = nMatsPerNode.view(); + + // Determine max number of materials a node might touch. + MatsetView deviceMatsetView(m_matsetView); + const TopologyView deviceTopologyView(m_topologyView); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + + const int nmats = deviceMatsetView.numberOfMaterials(zoneIndex); + const auto nnodesThisZone = zone.numberOfNodes(); + int *nodeData = nMatsPerNodeView.data(); + for(axom::IndexType i = 0; i < nnodesThisZone; i++) + { + const auto nodeId = zone.getId(i); + int *nodePtr = nodeData + nodeId; + RAJA::atomicMax(nodePtr, nmats); + } + }); + AXOM_ANNOTATE_END("nMatsPerNode"); + + // Now, mark all selected zones that have 1 mat per node as clean. + AXOM_ANNOTATE_BEGIN("mask"); + const auto nzones = selectedZonesView.size(); + axom::Array mask(nzones, nzones, allocatorID); + auto maskView = mask.view(); + RAJA::ReduceSum mask_reduce(0); + axom::for_all( + selectedZonesView.size(), + AXOM_LAMBDA(axom::IndexType szIndex) { + const auto zoneIndex = selectedZonesView[szIndex]; + const auto zone = deviceTopologyView.zone(zoneIndex); + + bool clean = true; + const axom::IndexType nnodesThisZone = zone.numberOfNodes(); + for(axom::IndexType i = 0; i < nnodesThisZone && clean; i++) + { + const auto nodeId = zone.getId(i); + clean &= (nMatsPerNodeView[nodeId] == 1); + } + + const int ival = clean ? 1 : 0; + maskView[szIndex] = ival; + mask_reduce += ival; + }); + AXOM_ANNOTATE_END("mask"); + + const int nClean = mask_reduce.get(); + if(nClean > 0) + { + AXOM_ANNOTATE_BEGIN("offsets"); + axom::Array maskOffsets(nzones, nzones, allocatorID); + auto maskOffsetsView = maskOffsets.view(); + axom::exclusive_scan(maskView, maskOffsetsView); + AXOM_ANNOTATE_END("offsets"); + + // Make the output cleanIndices array. + AXOM_ANNOTATE_BEGIN("cleanIndices"); + cleanIndices = axom::Array(nClean, nClean, allocatorID); + auto cleanIndicesView = cleanIndices.view(); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + if(maskView[index] > 0) + { + cleanIndicesView[maskOffsetsView[index]] = selectedZonesView[index]; + } + }); + AXOM_ANNOTATE_END("cleanIndices"); + + // Make the mixedIndices array. + AXOM_ANNOTATE_BEGIN("mixedIndices"); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + maskView[index] = (maskView[index] == 1) ? 0 : 1; + }); + axom::exclusive_scan(maskView, maskOffsetsView); + const int nMixed = nzones - nClean; + mixedIndices = axom::Array(nMixed, nMixed, allocatorID); + auto mixedIndicesView = mixedIndices.view(); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + if(maskView[index] > 0) + { + mixedIndicesView[maskOffsetsView[index]] = selectedZonesView[index]; + } + }); + AXOM_ANNOTATE_END("mixedIndices"); + } + else + { + AXOM_ANNOTATE_SCOPE("mixedIndices"); + cleanIndices = axom::Array(); + + // There were no clean, so it must all be mixed. + mixedIndices = axom::Array(nzones, nzones, allocatorID); + auto mixedIndicesView = mixedIndices.view(); + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType index) { + mixedIndicesView[index] = selectedZonesView[index]; + }); + } + } + +private: + TopologyView m_topologyView; + MatsetView m_matsetView; +}; + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/blueprint_utilities.cpp b/src/axom/mir/utilities/blueprint_utilities.cpp new file mode 100644 index 0000000000..2ee8e13179 --- /dev/null +++ b/src/axom/mir/utilities/blueprint_utilities.cpp @@ -0,0 +1,183 @@ +// 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/core.hpp" +#include "axom/mir.hpp" +#include +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +// Static data. These originally appeared as constexpr members in the header file +// but there were linker errors despite constexpr. +const char *cpp2conduit::name = "int8"; +const char *cpp2conduit::name = "int16"; +const char *cpp2conduit::name = "int32"; +const char *cpp2conduit::name = "int64"; +const char *cpp2conduit::name = "uint8"; +const char *cpp2conduit::name = "uint16"; +const char *cpp2conduit::name = "uint32"; +const char *cpp2conduit::name = "uint64"; +const char *cpp2conduit::name = "float32"; +const char *cpp2conduit::name = "float64"; + +/** + * \brief Turns a ShapeID to a VTK cell type value. + * + * \param shape_value A ShapeID. + * \return A corresponding VTK cell type. + */ +static int ShapeID_to_vtk_cell(int shape_value) +{ + int vtktype = 0; + if(shape_value == axom::mir::views::Tri_ShapeID) + vtktype = 5; // VTK_TRIANGLE + else if(shape_value == axom::mir::views::Quad_ShapeID) + vtktype = 9; // VTK_QUAD + else if(shape_value == axom::mir::views::Polygon_ShapeID) + vtktype = 7; // VTK_POLYGON + else if(shape_value == axom::mir::views::Tet_ShapeID) + vtktype = 10; // VTK_TETRA + else if(shape_value == axom::mir::views::Pyramid_ShapeID) + vtktype = 14; // VTK_PYRAMID + else if(shape_value == axom::mir::views::Wedge_ShapeID) + vtktype = 13; // VTK_WEDGE + else if(shape_value == axom::mir::views::Hex_ShapeID) + vtktype = 12; // VTK_HEXAHEDRON + else if(shape_value == axom::mir::views::Polyhedron_ShapeID) + vtktype = 42; // VTK_POLYHEDRON + + return vtktype; +} + +static void save_unstructured_vtk(const conduit::Node &mesh, + const std::string &path) +{ + FILE *file = fopen(path.c_str(), "wt"); + if(file == nullptr) + { + SLIC_ERROR(fmt::format("The file {} could not be created.", path)); + return; + } + + // Write the VTK file header + fprintf(file, "# vtk DataFile Version 3.0\n"); + fprintf(file, "Unstructured Grid Example\n"); + fprintf(file, "ASCII\n"); + fprintf(file, "DATASET UNSTRUCTURED_GRID\n"); + + // Write the points + const conduit::Node &coordset = mesh["coordsets"][0]; + const conduit::Node &points = coordset["values"]; + const auto x = points["x"].as_double_accessor(); + const auto y = points["y"].as_double_accessor(); + size_t num_points = 0; + views::dispatch_coordset(coordset, [&](auto coordsetView) { + num_points = coordsetView.size(); + fprintf(file, "POINTS %zu float\n", num_points); + + if(coordsetView.dimension() == 3) + { + const auto z = points["z"].as_double_accessor(); + for(size_t i = 0; i < num_points; ++i) + { + const auto p = coordsetView[i]; + fprintf(file, + "%f %f %f\n", + static_cast(p[0]), + static_cast(p[1]), + static_cast(p[2])); + } + } + else + { + for(size_t i = 0; i < num_points; ++i) + { + const auto p = coordsetView[i]; + fprintf(file, + "%f %f 0\n", + static_cast(p[0]), + static_cast(p[1])); + } + } + }); + + // Write the cells + const conduit::Node &topologies = mesh["topologies"]; + const conduit::Node &topo = topologies[0]; + const conduit::Node &elements = topo["elements"]; + const conduit::Node &connectivity = elements["connectivity"]; + size_t num_cells = elements["sizes"].dtype().number_of_elements(); + size_t total_num_indices = connectivity.dtype().number_of_elements(); + + fprintf(file, "CELLS %zu %zu\n", num_cells, total_num_indices + num_cells); + size_t index = 0; + for(size_t i = 0; i < num_cells; ++i) + { + size_t cell_size = elements["sizes"].as_int32_array()[i]; + fprintf(file, "%zu", cell_size); + for(size_t j = 0; j < cell_size; ++j) + { + fprintf(file, " %d", connectivity.as_int32_array()[index++]); + } + fprintf(file, "\n"); + } + + // Write the cell types + fprintf(file, "CELL_TYPES %zu\n", num_cells); + if(elements.has_child("shapes")) + { + const conduit::Node &shapes = elements["shapes"]; + for(size_t i = 0; i < num_cells; ++i) + { + const auto type = ShapeID_to_vtk_cell(shapes.as_int32_array()[i]); + fprintf(file, "%d\n", type); + } + } + else + { + const auto type = ShapeID_to_vtk_cell( + axom::mir::views::shapeNameToID(elements["shape"].as_string())); + for(size_t i = 0; i < num_cells; ++i) + { + fprintf(file, "%d\n", type); + } + } + + // TODO: write fields. + + // Close the file + fclose(file); +} + +void save_vtk(const conduit::Node &mesh, const std::string &path) +{ + const conduit::Node &n_topologies = mesh.fetch_existing("topologies"); + if(n_topologies.number_of_children() != 1) + { + SLIC_ERROR("The mesh must have a single topology."); + return; + } + + // For now. + if(n_topologies[0].fetch_existing("type").as_string() != "unstructured") + { + SLIC_ERROR("The mesh must have a single unstructured topology."); + return; + } + + save_unstructured_vtk(mesh, path); +} + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom diff --git a/src/axom/mir/utilities/blueprint_utilities.hpp b/src/axom/mir/utilities/blueprint_utilities.hpp new file mode 100644 index 0000000000..28d32171d9 --- /dev/null +++ b/src/axom/mir/utilities/blueprint_utilities.hpp @@ -0,0 +1,505 @@ +// 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) + +#ifndef AXOM_MIR_BLUEPRINT_UTILITIES_HPP_ +#define AXOM_MIR_BLUEPRINT_UTILITIES_HPP_ + +#include "axom/core/execution/execution_space.hpp" +#include "axom/core/Array.hpp" +#include "axom/core/ArrayView.hpp" +#include "axom/core/NumericLimits.hpp" +#include "axom/core/memory_management.hpp" +#include "axom/mir/views/NodeArrayView.hpp" + +#include +#include + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +#include +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +namespace blueprint +{ +//------------------------------------------------------------------------------ +/*! + * \brief This class provides a couple of type traits that let us map C++ types + * to types / values useful in Conduit. + */ +template +struct cpp2conduit +{ }; + +template <> +struct cpp2conduit +{ + using type = conduit::int8; + static constexpr conduit::index_t id = conduit::DataType::INT8_ID; + static const char *name; +}; + +template <> +struct cpp2conduit +{ + using type = conduit::int16; + static constexpr conduit::index_t id = conduit::DataType::INT16_ID; + static const char *name; +}; + +template <> +struct cpp2conduit +{ + using type = conduit::int32; + static constexpr conduit::index_t id = conduit::DataType::INT32_ID; + static const char *name; +}; + +template <> +struct cpp2conduit +{ + using type = conduit::int64; + static constexpr conduit::index_t id = conduit::DataType::INT64_ID; + static const char *name; +}; + +template <> +struct cpp2conduit +{ + using type = conduit::uint8; + static constexpr conduit::index_t id = conduit::DataType::UINT8_ID; + static const char *name; +}; + +template <> +struct cpp2conduit +{ + using type = conduit::uint16; + static constexpr conduit::index_t id = conduit::DataType::UINT16_ID; + static const char *name; +}; + +template <> +struct cpp2conduit +{ + using type = conduit::uint32; + static constexpr conduit::index_t id = conduit::DataType::UINT32_ID; + static const char *name; +}; + +template <> +struct cpp2conduit +{ + using type = conduit::uint64; + static constexpr conduit::index_t id = conduit::DataType::UINT64_ID; + static const char *name; +}; + +template <> +struct cpp2conduit +{ + using type = conduit::float32; + static constexpr conduit::index_t id = conduit::DataType::FLOAT32_ID; + static const char *name; +}; + +template <> +struct cpp2conduit +{ + using type = conduit::float64; + static constexpr conduit::index_t id = conduit::DataType::FLOAT64_ID; + static const char *name; +}; + +//------------------------------------------------------------------------------ + +/*! + * \brief Make an axom::ArrayView from a Conduit node. + * + * \tparam T The type for the array view elements. + * + * \param n The conduit node for which we want an array view. + * + * \return An axom::ArrayView that wraps the data in the Conduit node. + */ +/// @{ +template +inline axom::ArrayView make_array_view(conduit::Node &n) +{ + SLIC_ASSERT_MSG( + cpp2conduit::id == n.dtype().id(), + axom::fmt::format("Cannot create ArrayView<{}> for Conduit {} data.", + cpp2conduit::name, + n.dtype().name())); + return axom::ArrayView(static_cast(n.data_ptr()), + n.dtype().number_of_elements()); +} + +template +inline axom::ArrayView make_array_view(const conduit::Node &n) +{ + SLIC_ASSERT_MSG( + cpp2conduit::id == n.dtype().id(), + axom::fmt::format("Cannot create ArrayView<{}> for Conduit {} data.", + cpp2conduit::name, + n.dtype().name())); + return axom::ArrayView(static_cast(const_cast(n.data_ptr())), + n.dtype().number_of_elements()); +} +/// @} + +//------------------------------------------------------------------------------ +/*! + * \brief This class contains views of blend data. Blend data lets is make new + * nodal fields and coordsets. The field data are sampled using m_originalIdsView + * which is a compact list of the original node ids that we want to preserve + * without any blending. This stream is followed by a second stream of data + * made using the field and the blend groups. Each blend group has + * m_blendGroupSizesView[i] elements, starts at m_blendGroupStartView[i] and + * uses values from the m_blendIdsView, m_blendCoeffView members to blend the + * data values. + * + */ +struct BlendData +{ + axom::ArrayView + m_originalIdsView; // Contains indices of original node ids to be preserved. + + axom::ArrayView m_selectedIndicesView; // Contains indices of the selected blend groups. + + axom::ArrayView m_blendGroupSizesView; // The number of ids/weights in each blend group. + axom::ArrayView + m_blendGroupStartView; // The starting offset for a blend group in the ids/weights. + axom::ArrayView m_blendIdsView; // Contains ids that make up the blend groups + axom::ArrayView m_blendCoeffView; // Contains the weights that make up the blend groups. +}; + +//------------------------------------------------------------------------------ +/*! + * \brief This class registers a Conduit allocator that can make Conduit allocate + * through Axom's allocate/deallocate functions using a specific allocator. + * This permits Conduit to allocate through Axom's UMPIRE logic. + * + * \tparam ExecSpace The execution space. + */ +template +class ConduitAllocateThroughAxom +{ +public: + /*! + * \brief Get the Conduit allocator ID for this ExecSpace. + * + * \return The Conduit allocator ID for this ExecSpace. + */ + static conduit::index_t getConduitAllocatorID() + { + constexpr conduit::index_t NoAllocator = -1; + static conduit::index_t conduitAllocatorID = NoAllocator; + if(conduitAllocatorID == NoAllocator) + { + conduitAllocatorID = + conduit::utils::register_allocator(internal_allocate, internal_free); + } + return conduitAllocatorID; + } + +private: + /*! + * \brief A function we register with Conduit to allocate memory. + * + * \param items The number of items to allocate. + * \param item_size The size of each item in bytes. + * + * \brief A block of newly allocated memory large enough for the requested items. + */ + static void *internal_allocate(size_t items, size_t item_size) + { + const auto axomAllocatorID = axom::execution_space::allocatorID(); + void *ptr = static_cast( + axom::allocate(items * item_size, axomAllocatorID)); + //std::cout << axom::execution_space::name() << ": Allocated for Conduit via axom: items=" << items << ", item_size=" << item_size << ", ptr=" << ptr << std::endl; + return ptr; + } + + /*! + * \brief A deallocation function we register with Conduit. + */ + static void internal_free(void *ptr) + { + //std::cout << axom::execution_space::name() << ": Dellocating for Conduit via axom: ptr=" << ptr << std::endl; + axom::deallocate(ptr); + } +}; + +//------------------------------------------------------------------------------ +/*! + * \brief Copies a Conduit tree in the \a src node to a new Conduit \a dest node, + * making sure to allocate array data in the appropriate memory space for + * the execution space. + * + * \tparam The destination execution space (e.g. axom::SEQ_EXEC). + * + * \param dest The conduit node that will receive the copied data. + * \param src The source data to be copied. + */ +template +void copy(conduit::Node &dest, const conduit::Node &src) +{ + ConduitAllocateThroughAxom c2a; + dest.reset(); + if(src.number_of_children() > 0) + { + for(conduit::index_t i = 0; i < src.number_of_children(); i++) + { + copy(dest[src[i].name()], src[i]); + } + } + else + { + if(!src.dtype().is_string() && src.dtype().number_of_elements() > 1) + { + // Allocate the node's memory in the right place. + dest.reset(); + dest.set_allocator(c2a.getConduitAllocatorID()); + dest.set( + conduit::DataType(src.dtype().id(), src.dtype().number_of_elements())); + + // Copy the data to the destination node. Axom uses Umpire to manage that. + if(src.is_compact()) + axom::copy(dest.data_ptr(), src.data_ptr(), src.dtype().bytes_compact()); + else + { + // NOTE: This assumes that src is on the host. + conduit::Node tmp; + src.compact_to(tmp); + axom::copy(dest.data_ptr(), tmp.data_ptr(), tmp.dtype().bytes_compact()); + } + } + else + { + // The data fits in the node or is a string. It's on the host. + dest.set(src); + } + } +} + +//------------------------------------------------------------------------------ +/*! + * \brief Fill an array with int values from a Conduit node. + * + * \tparam ArrayType The array type being filled. + * + * \param n The node that contains the data. + * \param key The name of the node that contains the data in \a n. + * \param[out] arr The array being filled. + * \param moveToHost Sometimes data are on device and need to be moved to host first. + */ +template +bool fillFromNode(const conduit::Node &n, + const std::string &key, + ArrayType &arr, + bool moveToHost = false) +{ + bool found = false; + if((found = n.has_path(key)) == true) + { + if(moveToHost) + { + // Make sure data are on host. + conduit::Node hostNode; + copy(hostNode, n.fetch_existing(key)); + + const auto acc = hostNode.as_int_accessor(); + for(int i = 0; i < arr.size(); i++) + { + arr[i] = acc[i]; + } + } + else + { + const auto acc = n.fetch_existing(key).as_int_accessor(); + for(int i = 0; i < arr.size(); i++) + { + arr[i] = acc[i]; + } + } + } + return found; +} +//------------------------------------------------------------------------------ + +/*! + * \brief Returns the input index (no changes). + */ +struct DirectIndexing +{ + /*! + * \brief Return the input index (no changes). + * \param index The input index. + * \return The input index. + */ + AXOM_HOST_DEVICE + inline axom::IndexType operator[](axom::IndexType index) const + { + return index; + } +}; + +//------------------------------------------------------------------------------ +/*! + * \brief Help turn slice data zone indices into strided structured element field indices. + * \tparam Indexing A StridedStructuredIndexing of some dimension. + */ +template +struct SSElementFieldIndexing +{ + /*! + * \brief Update the indexing offsets/strides from a Conduit node. + * \param field The Conduit node for a field. + * + * \note Executes on the host. + */ + void update(const conduit::Node &field) + { + fillFromNode(field, "offsets", m_indexing.m_offsets, true); + fillFromNode(field, "strides", m_indexing.m_strides, true); + } + + /*! + * \brief Transforms the index from local to global through an indexing object. + * \param index The local index + * \return The global index for the field. + */ + AXOM_HOST_DEVICE + inline axom::IndexType operator[](axom::IndexType index) const + { + return m_indexing.LocalToGlobal(index); + } + + Indexing m_indexing {}; +}; + +//------------------------------------------------------------------------------ +/*! + * \brief Help turn blend group node indices (global) into vertex field indices. + * \tparam Indexing A StridedStructuredIndexing of some dimension. + */ +template +struct SSVertexFieldIndexing +{ + /*! + * \brief Update the indexing offsets/strides from a Conduit node. + * \param field The Conduit node for a field. + * + * \note Executes on the host. + */ + void update(const conduit::Node &field) + { + fillFromNode(field, "offsets", m_fieldIndexing.m_offsets, true); + fillFromNode(field, "strides", m_fieldIndexing.m_strides, true); + } + + /*! + * \brief Transforms the index from local to global through an indexing object. + * \param index The global index + * \return The global index for the field. + */ + AXOM_HOST_DEVICE + inline axom::IndexType operator[](axom::IndexType index) const + { + // Make the global index into a global logical in the topo. + const auto topoGlobalLogical = m_topoIndexing.GlobalToGlobal(index); + // Make the global logical into a local logical in the topo. + const auto topoLocalLogical = m_topoIndexing.GlobalToLocal(topoGlobalLogical); + // Make the global logical index in the field. + const auto fieldGlobalLogical = + m_fieldIndexing.LocalToGlobal(topoLocalLogical); + // Make the global index in the field. + const auto fieldGlobalIndex = + m_fieldIndexing.GlobalToGlobal(fieldGlobalLogical); + return fieldGlobalIndex; + } + + Indexing m_topoIndexing {}; + Indexing m_fieldIndexing {}; +}; + +/*! + * \brief Get the min/max values for the data in a Conduit node or ArrayView. + */ +template +struct minmax +{ + /*! + * \brief Get the min/max values for the data in a Conduit node. + * + * \param[in] n The Conduit node whose data we're checking. + * + * \return A pair containing the min,max values in the node. + */ + static std::pair execute(const conduit::Node &n) + { + SLIC_ASSERT(n.dtype().number_of_elements() > 0); + std::pair retval; + + axom::mir::views::Node_to_ArrayView(n, [&](auto nview) { + retval = execute(nview); + }); + return retval; + } + + /*! + * \brief Get the min/max values for the data in an ArrayView. + * + * \param[in] n The Conduit node whose data we're checking. + * + * \return A pair containing the min,max values in the node. + */ + template + static std::pair execute(const axom::ArrayView nview) + { + using reduce_policy = + typename axom::execution_space::reduce_policy; + + RAJA::ReduceMin vmin(axom::numeric_limits::max()); + RAJA::ReduceMax vmax(axom::numeric_limits::min()); + + axom::for_all( + nview.size(), + AXOM_LAMBDA(axom::IndexType index) { + vmin.min(nview[index]); + vmax.max(nview[index]); + }); + + return std::pair { + static_cast(vmin.get()), + static_cast(vmax.get())}; + } +}; + +/*! + * \brief Save a Blueprint mesh to a legacy ASCII VTK file. + * + * \param node The node that contains the mesh data. + * \param path The file path to save. + * + * \note This function currently handles only unstructured topos with explicit coordsets. + */ +void save_vtk(const conduit::Node &node, const std::string &path); + +} // end namespace blueprint +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/utilities/utilities.hpp b/src/axom/mir/utilities/utilities.hpp new file mode 100644 index 0000000000..e56b1f85be --- /dev/null +++ b/src/axom/mir/utilities/utilities.hpp @@ -0,0 +1,536 @@ +// 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) + +#ifndef AXOM_MIR_UTILITIES_HPP_ +#define AXOM_MIR_UTILITIES_HPP_ + +#include "axom/core.hpp" + +#include +#include + +// RAJA +#if defined(AXOM_USE_RAJA) + #include "RAJA/RAJA.hpp" +#endif + +#include + +namespace axom +{ +namespace mir +{ +namespace utilities +{ +//------------------------------------------------------------------------------ +/*! + * \brief This class and its specializations provide a type trait that lets us + * determine the type that should be used to accumulate values when we + * do floating point math. + * + * \note this belongs in algorithm utilities, maybe core. + */ +template +struct accumulation_traits +{ + using value_type = float; +}; + +template <> +struct accumulation_traits +{ + using value_type = double; +}; + +template <> +struct accumulation_traits +{ + using value_type = double; +}; + +template <> +struct accumulation_traits +{ + using value_type = double; +}; + +//------------------------------------------------------------------------------ +/*! + * \brief Use binary search to find the index of the \a value in the supplied + * sorted view. + * + * \param[in] value The search value. + * \param[in] view The view that contains the sorted search data values. + * + * \return The index where value was located in view or -1 if not found. + */ +template +AXOM_HOST_DEVICE std::int32_t bsearch(T value, const axom::ArrayView &view) +{ + std::int32_t index = -1; + std::int32_t left = 0; + std::int32_t right = view.size() - 1; + while(left <= right) + { + std::int32_t m = (left + right) / 2; + if(view[m] < value) + left = m + 1; + else if(view[m] > value) + right = m - 1; + else + { + index = m; + break; + } + } + + return index; +} + +//------------------------------------------------------------------------------ +/*! + * \brief Hash a stream of bytes into a uint64_t hash value. + * + * \param[in] data The bytes to be hashed. + * \param[in] length The number of bytes to be hashed. + * + * \return A uint64_t hash for the byte stream. + * + * \note The byte stream is hashed using a Jenkins-hash algorithm forwards and + * backwards and the two results are merged into a uint64_t. The length is + * also part of the hash to guard against a lot of repeated values in the + * byte stream hashing to the same thing. + * + * \note We make this function inline since it is not a template and we want to + * use it in both host and device code. + */ +AXOM_HOST_DEVICE +inline std::uint64_t hash_bytes(const std::uint8_t *data, std::uint32_t length) +{ + std::uint32_t hash = 0; + + // Build the length into the hash. + const auto ldata = reinterpret_cast(&length); + for(int e = 0; e < 4; e++) + { + hash += ldata[e]; + hash += hash << 10; + hash ^= hash >> 6; + } + + std::uint32_t hashr = hash; + for(std::uint32_t i = 0; i < length; i++) + { + hash += data[i]; + hash += hash << 10; + hash ^= hash >> 6; + + hashr += data[length - 1 - i]; + hashr += hashr << 10; + hashr ^= hashr >> 6; + } + hash += hash << 3; + hash ^= hash >> 11; + hash += hash << 15; + + hashr += hashr << 3; + hashr ^= hashr >> 11; + hashr += hashr << 15; + + return (static_cast(hash) << 32) | hashr; +} + +//------------------------------------------------------------------------------ +/*! + * \brief This class implements a naming policy that uses some hashing functions + * to produce a "name" for an array of ids. + */ +template +class HashNaming +{ +public: + using KeyType = std::uint64_t; + using IndexType = IndexT; + + // The top 2 bits are reserved for the key type. + constexpr static KeyType KeyIDSingle = 0; + constexpr static KeyType KeyIDPair = KeyType(1) << 62; + constexpr static KeyType KeyIDPack = KeyType(2) << 62; + constexpr static KeyType KeyIDHash = KeyType(3) << 62; + + // The rest of the bits can be divided in various ways. + constexpr static KeyType KeyMask = KeyType(3) << 62; + constexpr static KeyType PayloadMask = ~KeyMask; + constexpr static KeyType Max15Bit = (KeyType(1) << 15) - 1; + constexpr static KeyType Max16Bit = (KeyType(1) << 16) - 1; + constexpr static KeyType Max20Bit = (KeyType(1) << 20) - 1; + constexpr static KeyType Max31Bit = (KeyType(1) << 31) - 1; + constexpr static KeyType Max32Bit = (KeyType(1) << 32) - 1; + + /*! + * \brief A view for making names, suitable for use in device code. + */ + class View + { + public: + using KeyType = HashNaming::KeyType; + + /*! + * \brief Make a name from an array of ids. + * + * \param p The array of ids. + * \param n The number of ids in the array. + * + * \return The name that describes the array of ids. + * + * \note Different make_name_* functions are used because we can skip most + * sorting for 1,2 element arrays. Also, there is a small benefit + * to some of the other shortcuts for smaller arrays. + */ + AXOM_HOST_DEVICE + KeyType makeName(const IndexType *p, int n) const + { + KeyType name {}; + if(n == 1) + name = make_name_1(p[0]); + else if(n == 2) + name = make_name_2(p[0], p[1]); + else + name = make_name_n(p, n); + return name; + } + + /// Set the max number of nodes, which can be useful for packing/narrowing. + AXOM_HOST_DEVICE + void setMaxId(IndexType m) { m_maxId = static_cast(m); } + + private: + /*! + * \brief Encode a single id as a name. + * \param p0 The id to encode. + * \return A name that encodes the id. + */ + AXOM_HOST_DEVICE + inline KeyType make_name_1(IndexType p0) const + { + assert(static_cast(p0) < PayloadMask); + // Store p0 in the key as a 62-bit integer + KeyType k0 = (static_cast(p0) & PayloadMask); + return KeyIDSingle | k0; + } + + /*! + * \brief Encode 2 ids as a name. + * \param p0 The first id to encode. + * \param p1 The second id to encode. + * \return A name that encodes the ids. + */ + AXOM_HOST_DEVICE + inline KeyType make_name_2(IndexType p0, IndexType p1) const + { + assert(static_cast(p0) <= Max31Bit && + static_cast(p1) <= Max31Bit); + // Store p0 and p1 both in the 64-bit key as 31-bit integers + KeyType k0 = + (static_cast(axom::utilities::min(p0, p1)) & Max31Bit); + KeyType k1 = + (static_cast(axom::utilities::max(p0, p1)) & Max31Bit); + return KeyIDPair | (k0 << 31) | k1; + } + + /*! + * \brief Encode multiple ids as a name. + * \param p The ids to encode. + * \param n The number of ids. + * \return A name that encodes the ids. + */ + AXOM_HOST_DEVICE + KeyType make_name_n(const IndexType *p, int n) const + { + KeyType retval {}; + if(n == 3 && m_maxId <= Max20Bit) + { + // We can pack 3 values into the id lossless + IndexType sorted[3]; + sorted[0] = p[0]; + sorted[1] = p[1]; + sorted[2] = p[2]; + axom::utilities::Sorting::sort(sorted); + + KeyType k0 = static_cast(sorted[0]) & Max20Bit; + KeyType k1 = static_cast(sorted[1]) & Max20Bit; + KeyType k2 = static_cast(sorted[2]) & Max20Bit; + constexpr KeyType len = KeyType(3 - 1) << 60; + retval = KeyIDPack | len | (k0 << 40) | (k1 << 20) | k2; + } + else if(n == 4 && m_maxId <= Max15Bit) + { + // We can pack 4 values into the id lossless + IndexType sorted[4]; + sorted[0] = p[0]; + sorted[1] = p[1]; + sorted[2] = p[2]; + sorted[3] = p[3]; + axom::utilities::Sorting::sort(sorted); + + KeyType k0 = static_cast(sorted[0]) & Max15Bit; + KeyType k1 = static_cast(sorted[1]) & Max15Bit; + KeyType k2 = static_cast(sorted[2]) & Max15Bit; + KeyType k3 = static_cast(sorted[3]) & Max15Bit; + constexpr KeyType len = KeyType(4 - 1) << 60; + retval = KeyIDPack | len | (k0 << 45) | (k1 << 30) | (k2 << 15) | k3; + } + else if(m_maxId < Max16Bit) + { + // Narrow to 16-bit, sort + std::uint16_t sorted[MAXIDS]; + for(int i = 0; i < n; i++) + { + sorted[i] = static_cast(p[i]); + } + axom::utilities::Sorting::sort(sorted, n); + + // Make a hash from the narrowed ids + void *ptr = static_cast(sorted); + KeyType k0 = + axom::mir::utilities::hash_bytes(static_cast(ptr), + n * sizeof(std::uint16_t)); + retval = KeyIDHash | (k0 & PayloadMask); + } + else if(m_maxId < Max32Bit) + { + // Narrow to 32-bit, sort + std::uint32_t sorted[MAXIDS]; + for(int i = 0; i < n; i++) + { + sorted[i] = static_cast(p[i]); + } + axom::utilities::Sorting::sort(sorted, n); + + // Make a hash from the narrowed ids + void *ptr = static_cast(sorted); + KeyType k0 = + axom::mir::utilities::hash_bytes(static_cast(ptr), + n * sizeof(std::uint32_t)); + retval = KeyIDHash | (k0 & PayloadMask); + } + else + { + IndexType sorted[MAXIDS]; + for(int i = 0; i < n; i++) + { + sorted[i] = p[i]; + } + axom::utilities::Sorting::sort(sorted, n); + + // Make a hash from the ids + void *ptr = static_cast(sorted); + KeyType k0 = + axom::mir::utilities::hash_bytes(static_cast(ptr), + n * sizeof(IndexType)); + retval = KeyIDHash | (k0 & PayloadMask); + } + return retval; + } + + KeyType m_maxId {axom::numeric_limits::max()}; + }; + + // Host-callable methods + + /// Make a name from the array of ids. + KeyType makeName(const IndexType *p, int n) const + { + return m_view.makeName(p, n); + } + + /*! + * \brief Set the max number of nodes, which can help with id packing/narrowing. + * \param n The number of nodes. + */ + void setMaxId(IndexType n) { m_view.setMaxId(n); } + + /// Return a view that can be used on device. + View view() { return m_view; } + + /*! + * \brief Turn name into a string. + * \param key The name. + * \return A string that represents the name. + */ + static std::string toString(KeyType key) + { + std::stringstream ss; + auto kt = key & KeyMask; + if(kt == KeyIDSingle) + { + auto id = key & PayloadMask; + ss << "single(" << std::hex << id << ")"; + } + else if(kt == KeyIDPair) + { + auto payload = key & PayloadMask; + auto p0 = (payload >> 31) & Max31Bit; + auto p1 = payload & Max31Bit; + ss << "pair(" << std::hex << p0 << ", " << p1 << ")"; + } + else if(kt == KeyIDHash) + { + ss << "hash(" << std::hex << key << ")"; + } + else if(kt == KeyIDPack) + { + auto npts = ((key >> 60) & 3) + 1; + if(npts == 3) + { + auto p0 = (key >> 40) & Max20Bit; + auto p1 = (key >> 20) & Max20Bit; + auto p2 = (key)&Max20Bit; + ss << "pack(" << std::hex << p0 << ", " << p1 << ", " << p2 << ")"; + } + else if(npts == 4) + { + auto p0 = (key >> 45) & Max15Bit; + auto p1 = (key >> 30) & Max15Bit; + auto p2 = (key >> 15) & Max15Bit; + auto p3 = (key)&Max15Bit; + ss << "pack(" << std::hex << p0 << ", " << p1 << ", " << p2 << ", " + << p3 << ")"; + } + } + return ss.str(); + } + + View m_view {}; +}; + +//------------------------------------------------------------------------------ +/*! + * \brief Makes a unique array of values from an input list of values. + * + * \tparam ExecSpace The execution space. + * \tparam KeyType The data type for the keys. + * + */ +template +struct Unique +{ + /*! + * \brief This function makes a unique array of values from an input list of keys. + * + * \param[in] keys_orig_view The input view that contains the input keys to be made unique. + * \param[out] skeys A sorted unique array of keys produced from keys_orig_view. + * \param[out] sindices An array of indices that indicate where in the original view the keys came from. + * + */ + static void execute(const axom::ArrayView &keys_orig_view, + axom::Array &skeys, + axom::Array &sindices) + { + using loop_policy = typename axom::execution_space::loop_policy; + using reduce_policy = + typename axom::execution_space::reduce_policy; + const int allocatorID = axom::execution_space::allocatorID(); + + // Make a copy of the keys and make original indices. + const auto n = keys_orig_view.size(); + axom::Array keys(n, n, allocatorID); + axom::Array indices(n, n, allocatorID); + auto keys_view = keys.view(); + auto indices_view = indices.view(); + axom::for_all( + n, + AXOM_LAMBDA(axom::IndexType i) { + keys_view[i] = keys_orig_view[i]; + indices_view[i] = i; + }); + + // Sort the keys, indices in place. + RAJA::sort_pairs(RAJA::make_span(keys_view.data(), n), + RAJA::make_span(indices_view.data(), n)); + + // Make a mask array for where differences occur. + axom::Array mask(n, n, allocatorID); + auto mask_view = mask.view(); + RAJA::ReduceSum mask_sum(0); + axom::for_all( + n, + AXOM_LAMBDA(axom::IndexType i) { + const axom::IndexType m = + (i >= 1) ? ((keys_view[i] != keys_view[i - 1]) ? 1 : 0) : 1; + mask_view[i] = m; + mask_sum += m; + }); + + // Do a scan on the mask array to build an offset array. + axom::Array offsets(n, n, allocatorID); + auto offsets_view = offsets.view(); + axom::exclusive_scan(mask_view, offsets_view); + + // Allocate the output arrays. + const axom::IndexType newsize = mask_sum.get(); + skeys = axom::Array(newsize, newsize, allocatorID); + sindices = axom::Array(newsize, newsize, allocatorID); + + // Iterate over the mask/offsets to store values at the right + // offset in the new array. + auto skeys_view = skeys.view(); + auto sindices_view = sindices.view(); + axom::for_all( + n, + AXOM_LAMBDA(axom::IndexType i) { + if(mask_view[i]) + { + skeys_view[offsets_view[i]] = keys_view[i]; + sindices_view[offsets_view[i]] = indices_view[i]; + } + }); + } +}; + +//------------------------------------------------------------------------------ +/// Partial specialization for SEQ_EXEC. +template +struct Unique +{ + /*! + * \brief This function makes a unique array of values from an input list of keys. + * + * \param[in] keys_orig_view The input view that contains the input keys to be made unique. + * \param[out] skeys A sorted unique array of keys produced from keys_orig_view. + * \param[out] sindices An array of indices that indicate where in the original view the keys came from. + * + */ + static void execute(const axom::ArrayView &keys_orig_view, + axom::Array &skeys, + axom::Array &sindices) + { + std::map unique; + const axom::IndexType n = keys_orig_view.size(); + axom::IndexType index = 0; + for(; index < n; index++) + { + const auto k = keys_orig_view[index]; + unique[k] = index; + } + // Allocate the output arrays. + const axom::IndexType newsize = unique.size(); + const int allocatorID = axom::execution_space::allocatorID(); + skeys = axom::Array(newsize, newsize, allocatorID); + sindices = axom::Array(newsize, newsize, allocatorID); + index = 0; + for(auto it = unique.begin(); it != unique.end(); it++, index++) + { + skeys[index] = it->first; + sindices[index] = it->second; + } + } +}; + +} // end namespace utilities +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/ExplicitCoordsetView.hpp b/src/axom/mir/views/ExplicitCoordsetView.hpp new file mode 100644 index 0000000000..c43f52d797 --- /dev/null +++ b/src/axom/mir/views/ExplicitCoordsetView.hpp @@ -0,0 +1,206 @@ +// 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) + +#ifndef AXOM_MIR_EXPLICIT_COORDSET_VIEW_HPP_ +#define AXOM_MIR_EXPLICIT_COORDSET_VIEW_HPP_ + +#include "axom/core/ArrayView.hpp" +#include "axom/slic.hpp" +#include "axom/primal/geometry/Point.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief This class provides a view for Conduit/Blueprint explicit coordsets. + */ +template +class ExplicitCoordsetView +{ }; + +/*! + * \brief This class provides a view for Conduit/Blueprint 2d explicit coordsets. + */ +template +class ExplicitCoordsetView +{ +public: + using IndexType = axom::IndexType; + using value_type = DataType; + using PointType = axom::primal::Point; + + constexpr static int dimension() { return 2; } + + /*! + * \brief Constructor + * + * \param x The first coordinate component. + * \param y The second coordinate component. + */ + AXOM_HOST_DEVICE + ExplicitCoordsetView(const axom::ArrayView &x, + const axom::ArrayView &y) + : m_coordinates {x, y} + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(x.size() == y.size()); + #else + SLIC_ASSERT_MSG(x.size() == y.size(), "Coordinate size mismatch."); + #endif +#endif + } + + /*! + * \brief Return the number of nodes in the coordset. + * + * \return The number of nodes in the coordset. + */ + /// @{ + AXOM_HOST_DEVICE + IndexType size() const { return m_coordinates[0].size(); } + + AXOM_HOST_DEVICE + IndexType numberOfNodes() const { return m_coordinates[0].size(); } + /// @} + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType getPoint(IndexType vertex_index) const + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(vertex_index < size()); + #else + SLIC_ASSERT_MSG(vertex_index < size(), + axom::fmt::format("Out of range index {}.", vertex_index)); + #endif +#endif + const DataType X[3] = {m_coordinates[0][vertex_index], + m_coordinates[1][vertex_index]}; + return PointType(X); + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType operator[](IndexType vertex_index) const + { + return getPoint(vertex_index); + } + +private: + axom::ArrayView m_coordinates[2]; +}; + +/*! + * \brief This class provides a view for Conduit/Blueprint 3d explicit coordsets. + */ +template +class ExplicitCoordsetView +{ +public: + using IndexType = axom::IndexType; + using value_type = DataType; + using PointType = axom::primal::Point; + + constexpr static int dimension() { return 3; } + + /*! + * \brief Constructor + * + * \param x The first coordinate component. + * \param y The second coordinate component. + * \param z The third coordinate component. + */ + AXOM_HOST_DEVICE + ExplicitCoordsetView(const axom::ArrayView &x, + const axom::ArrayView &y, + const axom::ArrayView &z) + : m_coordinates {x, y, z} + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(x.size() == y.size() && x.size() == z.size()); + #else + SLIC_ASSERT_MSG(x.size() == y.size() && x.size() == z.size(), + "Coordinate size mismatch."); + #endif +#endif + } + + /*! + * \brief Return the number of nodes in the coordset. + * + * \return The number of nodes in the coordset. + */ + /// @{ + AXOM_HOST_DEVICE + IndexType size() const { return m_coordinates[0].size(); } + + AXOM_HOST_DEVICE + IndexType numberOfNodes() const { return m_coordinates[0].size(); } + /// @} + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType getPoint(IndexType vertex_index) const + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(vertex_index < size()); + #else + SLIC_ASSERT_MSG(vertex_index < size(), + axom::fmt::format("Out of range index {}.", vertex_index)); + #endif +#endif + const DataType X[3] = {m_coordinates[0][vertex_index], + m_coordinates[1][vertex_index], + m_coordinates[2][vertex_index]}; + return PointType(X); + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType operator[](IndexType vertex_index) const + { + return getPoint(vertex_index); + } + +private: + axom::ArrayView m_coordinates[3]; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/MaterialView.cpp b/src/axom/mir/views/MaterialView.cpp new file mode 100644 index 0000000000..2ea3a03461 --- /dev/null +++ b/src/axom/mir/views/MaterialView.cpp @@ -0,0 +1,30 @@ +// 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/mir/views/MaterialView.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +MaterialInformation materials(const conduit::Node &matset) +{ + MaterialInformation info; + if(matset.has_child("material_map")) + { + const conduit::Node &mm = matset["material_map"]; + for(conduit::index_t i = 0; i < mm.number_of_children(); i++) + { + info.push_back(Material {mm[i].to_int(), mm[i].name()}); + } + } + return info; +} + +} // end namespace views +} // end namespace mir +} // end namespace axom diff --git a/src/axom/mir/views/MaterialView.hpp b/src/axom/mir/views/MaterialView.hpp new file mode 100644 index 0000000000..a61717bfb7 --- /dev/null +++ b/src/axom/mir/views/MaterialView.hpp @@ -0,0 +1,563 @@ +// 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) + +#ifndef AXOM_MIR_VIEWS_MATERIAL_VIEW_HPP_ +#define AXOM_MIR_VIEWS_MATERIAL_VIEW_HPP_ + +#include "axom/core.hpp" + +#include + +#include +#include +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief This object contains information about the materials as provided by a Conduit node. + * + * \note This would only be used on the host. + */ +struct Material +{ + int number {}; + std::string name {}; +}; + +using MaterialInformation = std::vector; + +/*! + * \brief Return a vector of Material from a matset (this is the material_map) + * + * \param matset The Conduit node that contains the matset. + * + * \return A vector of Material that contains the materials in the material_map. + */ +MaterialInformation materials(const conduit::Node &matset); + +//--------------------------------------------------------------------------- +// Material views - These objects are meant to wrap Blueprint Matsets behind +// an interface that lets us query materials for a single +// zone. It is intended that these views will be used in +// device kernels. +//--------------------------------------------------------------------------- + +/*! + \brief Material view for unibuffer matsets. + + \tparam IndexT The integer type used for material data. + \tparam FloatT The floating point type used for material data (volume fractions). + \tparam MAXMATERIALS The maximum number of materials to support. + + \verbatum + +matsets: + matset: + topology: topology + material_map: + a: 1 + b: 2 + c: 0 + material_ids: [0, 1, 2, 2, 2, 0, 1, 0] + volume_fractions: [0, a0, b2, b1, b0, 0, a1, 0] + sizes: [2, 2, 1] + offsets: [0, 2, 4] + indices: [1, 4, 6, 3, 2] + + \endverbatum + */ +template +class UnibufferMaterialView +{ +public: + using MaterialIndex = IndexT; + using ZoneIndex = IndexT; + using IndexType = IndexT; + using FloatType = FloatT; + using IDList = StaticArray; + using VFList = StaticArray; + + constexpr static axom::IndexType MaxMaterials = MAXMATERIALS; + + void set(const axom::ArrayView &material_ids, + const axom::ArrayView &volume_fractions, + const axom::ArrayView &sizes, + const axom::ArrayView &offsets, + const axom::ArrayView &indices) + { + assert(material_ids.size() == volume_fractions.size()); + assert(sizes.size() == offsets.size()); + + m_material_ids = material_ids; + m_volume_fractions = volume_fractions; + m_sizes = sizes; + m_offsets = offsets; + m_indices = indices; + } + + AXOM_HOST_DEVICE + inline axom::IndexType numberOfZones() const { return m_sizes.size(); } + + AXOM_HOST_DEVICE + inline axom::IndexType numberOfMaterials(ZoneIndex zi) const + { + assert(zi < static_cast(numberOfZones())); + return m_sizes[zi]; + } + + AXOM_HOST_DEVICE + void zoneMaterials(ZoneIndex zi, IDList &ids, VFList &vfs) const + { + assert(zi < static_cast(numberOfZones())); + + ids.clear(); + vfs.clear(); + + const auto sz = numberOfMaterials(zi); + const auto offset = m_offsets[zi]; + for(axom::IndexType i = 0; i < sz; i++) + { + const auto idx = m_indices[offset + i]; + + ids.push_back(m_material_ids[idx]); + vfs.push_back(m_volume_fractions[idx]); + } + } + + AXOM_HOST_DEVICE + bool zoneContainsMaterial(ZoneIndex zi, MaterialIndex mat) const + { + assert(zi < static_cast(numberOfZones())); + const auto sz = numberOfMaterials(zi); + const auto offset = m_offsets[zi]; + for(axom::IndexType i = 0; i < sz; i++) + { + const auto idx = m_indices[offset + i]; + + if(m_material_ids[idx] == mat) return true; + } + return false; + } + + AXOM_HOST_DEVICE + bool zoneContainsMaterial(ZoneIndex zi, MaterialIndex mat, FloatType &vf) const + { + assert(zi < static_cast(numberOfZones())); + const auto sz = numberOfMaterials(zi); + const auto offset = m_offsets[zi]; + for(axom::IndexType i = 0; i < sz; i++) + { + const auto idx = m_indices[offset + i]; + + if(m_material_ids[idx] == mat) + { + vf = m_volume_fractions[idx]; + return true; + } + } + vf = 0; + return false; + } + +private: + axom::ArrayView m_material_ids; + axom::ArrayView m_volume_fractions; + axom::ArrayView m_sizes; + axom::ArrayView m_offsets; + axom::ArrayView m_indices; +}; + +/*! + \brief View for multi-buffer matsets. + + \tparam IndexT The integer type used for material data. + \tparam FloatT The floating point type used for material data (volume fractions). + \tparam MAXMATERIALS The maximum number of materials to support. + + \verbatum + +matsets: + matset: + topology: topology + volume_fractions: + a: + values: [0, 0, 0, a1, 0, a0] + indices: [5, 3] + b: + values: [0, b0, b2, b1, 0] + indices: [1, 3, 2] + material_map: # (optional) + a: 0 + b: 1 + + \endverbatum + */ +template +class MultiBufferMaterialView +{ +public: + using MaterialIndex = IndexT; + using ZoneIndex = IndexT; + using IndexType = IndexT; + using FloatType = FloatT; + using IDList = StaticArray; + using VFList = StaticArray; + + constexpr static axom::IndexType MaxMaterials = MAXMATERIALS; + + void add(const axom::ArrayView &ids, + const axom::ArrayView &vfs) + { + assert(m_size + 1 < MaxMaterials); + + m_indices[m_size] = ids; + m_values[m_size] = vfs; + m_size++; + } + + AXOM_HOST_DEVICE + axom::IndexType numberOfZones() const + { + axom::IndexType nzones = 0; + for(int i = 0; i < m_size; i++) + nzones = axom::utilities::max(nzones, m_indices[i].size()); + return nzones; + } + + AXOM_HOST_DEVICE + axom::IndexType numberOfMaterials(ZoneIndex zi) const + { + axom::IndexType nmats = 0; + for(axom::IndexType i = 0; i < m_size; i++) + { + if(zi < m_indices[i].size()) + { + const auto idx = m_indices[zi]; + if(m_values[i][idx] > 0.) nmats++; + } + } + + return nmats; + } + + AXOM_HOST_DEVICE + void zoneMaterials(ZoneIndex zi, IDList &ids, VFList &vfs) const + { + ids.clear(); + vfs.clear(); + + for(axom::IndexType i = 0; i < m_size; i++) + { + if(zi < m_indices[i].size()) + { + const auto idx = m_indices[zi]; + if(m_values[i][idx] > 0.) + { + ids.push_back(i); + vfs.push_back(m_values[i][idx]); + } + } + } + } + + AXOM_HOST_DEVICE + bool zoneContainsMaterial(ZoneIndex zi, MaterialIndex mat) const + { + assert(mat < m_size); + assert(zi < m_indices[mat].size()); + + const auto idx = m_indices[mat][zi]; + return m_values[mat][zi] > 0.; + } + + AXOM_HOST_DEVICE + bool zoneContainsMaterial(ZoneIndex zi, MaterialIndex mat, FloatType &vf) const + { + assert(mat < m_size); + assert(zi < m_indices[mat].size()); + + const auto idx = m_indices[mat][zi]; + vf = m_values[mat][zi]; + return vf > 0.; + } + +private: + axom::StackArray, MAXMATERIALS> m_values {}; + axom::StackArray, MAXMATERIALS> m_indices {}; + axom::IndexType m_size {0}; +}; + +/*! + \brief View for element-dominant matsets. + + \tparam IndexT The integer type used for material data. + \tparam FloatT The floating point type used for material data (volume fractions). + \tparam MAXMATERIALS The maximum number of materials to support. + + \verbatum + +matsets: + matset: + topology: topology + volume_fractions: + a: [a0, a1, 0] + b: [b0, b1, b2] + c: [0, 0, c2] + material_map: # (optional) + a: 0 + b: 1 + c: 2 + + \endverbatum + */ +template +class ElementDominantMaterialView +{ +public: + using MaterialIndex = IndexT; + using ZoneIndex = IndexT; + using IndexType = IndexT; + using FloatType = FloatT; + using IDList = StaticArray; + using VFList = StaticArray; + + constexpr static axom::IndexType MaxMaterials = MAXMATERIALS; + + void add(const axom::ArrayView &vfs) + { + m_volume_fractions.push_back(vfs); + } + + AXOM_HOST_DEVICE + axom::IndexType numberOfZones() const + { + return (m_volume_fractions.size() > 0) ? m_volume_fractions[0].size() : 0; + } + + AXOM_HOST_DEVICE + axom::IndexType numberOfMaterials(ZoneIndex zi) const + { + axom::IndexType nmats = 0; + if(m_volume_fractions.size() > 0) + { + assert(zi < m_volume_fractions[0].size()); + for(axom::IndexType i = 0; i < m_volume_fractions.size(); i++) + nmats += m_volume_fractions[i][zi] > 0. ? 1 : 0; + } + return nmats; + } + + AXOM_HOST_DEVICE + void zoneMaterials(ZoneIndex zi, IDList &ids, VFList &vfs) const + { + ids.clear(); + vfs.clear(); + + if(m_volume_fractions.size() > 0) + { + assert(zi < m_volume_fractions[0].size()); + for(axom::IndexType i = 0; i < m_volume_fractions.size(); i++) + { + if(m_volume_fractions[i][zi] > 0) + { + ids.push_back(i); + vfs.push_back(m_volume_fractions[i][zi]); + } + } + } + } + + AXOM_HOST_DEVICE + bool zoneContainsMaterial(ZoneIndex zi, MaterialIndex mat) const + { + bool contains = false; + if(m_volume_fractions.size() > 0) + { + assert(zi < m_volume_fractions[0].size()); + contains = m_volume_fractions[mat][zi] > 0; + } + return contains; + } + + AXOM_HOST_DEVICE + bool zoneContainsMaterial(ZoneIndex zi, MaterialIndex mat, FloatType &vf) const + { + bool contains = false; + vf = 0; + if(m_volume_fractions.size() > 0) + { + assert(zi < m_volume_fractions[0].size()); + vf = m_volume_fractions[mat][zi] > 0; + contains = vf > 0; + } + return contains; + } + +private: + axom::StaticArray, MAXMATERIALS> m_volume_fractions {}; +}; + +/*! + \brief View for material-dominant matsets. + + \tparam IndexT The integer type used for material data. + \tparam FloatT The floating point type used for material data (volume fractions). + \tparam MAXMATERIALS The maximum number of materials to support. + + \verbatum + +matsets: + matset: + topology: topology + volume_fractions: + a: [a0, a1] + b: [b0, b1, b2] + c: [c2] + element_ids: + a: [0, 1] + b: [0, 1, 2] + c: [2] + material_map: # (optional) + a: 0 + b: 1 + c: 2 + + \endverbatum + + \note This matset type does not seem so GPU friendly since there is some work to do for some of the queries. + + */ +template +class MaterialDominantMaterialView +{ +public: + using MaterialIndex = IndexT; + using ZoneIndex = IndexT; + using IndexType = IndexT; + using FloatType = FloatT; + using IDList = StaticArray; + using VFList = StaticArray; + + constexpr static axom::IndexType MaxMaterials = MAXMATERIALS; + + void add(const axom::ArrayView &ids, + const axom::ArrayView &vfs) + { + assert(m_size + 1 < MaxMaterials); + + m_element_ids[m_size] = ids; + m_volume_fractions[m_size] = vfs; + m_size++; + } + + AXOM_HOST_DEVICE + axom::IndexType numberOfZones() + { + if(m_nzones == 0) + { + for(axom::IndexType mi = 0; mi < m_size; mi++) + { + const auto sz = m_element_ids[mi].size(); + for(axom::IndexType i = 0; i < sz; i++) + m_nzones = axom::utilities::max(m_nzones, m_element_ids[mi][i]); + } + } + return m_nzones; + } + + AXOM_HOST_DEVICE + axom::IndexType numberOfMaterials(ZoneIndex zi) const + { + axom::IndexType nmats = 0; + for(axom::IndexType mi = 0; mi < m_size; mi++) + { + const auto sz = m_element_ids[mi].size(); + for(axom::IndexType i = 0; i < sz; i++) + { + if(m_element_ids[mi][i] == zi) + { + nmats++; + break; + } + } + } + return nmats; + } + + AXOM_HOST_DEVICE + void zoneMaterials(ZoneIndex zi, IDList &ids, VFList &vfs) const + { + ids.clear(); + vfs.clear(); + + for(axom::IndexType mi = 0; mi < m_size; mi++) + { + const auto sz = m_element_ids[mi].size(); + for(axom::IndexType i = 0; i < sz; i++) + { + if(m_element_ids[mi][i] == zi) + { + ids.push_back(mi); + vfs.push_back(m_volume_fractions[mi][i]); + break; + } + } + } + } + + AXOM_HOST_DEVICE + bool zoneContainsMaterial(ZoneIndex zi, MaterialIndex mat) const + { + assert(mat < m_element_ids.size()); + + bool found = false; + const auto element_ids = m_element_ids[mat]; + for(axom::IndexType i = 0; i < element_ids.size(); i++) + { + if(element_ids[i] == zi) + { + found = true; + break; + } + } + return found; + } + + AXOM_HOST_DEVICE + bool zoneContainsMaterial(ZoneIndex zi, MaterialIndex mat, FloatType &vf) const + { + assert(mat < m_element_ids.size()); + + bool found = false; + const auto element_ids = m_element_ids[mat]; + for(axom::IndexType i = 0; i < element_ids.size(); i++) + { + if(element_ids[i] == zi) + { + found = true; + vf = m_volume_fractions[mat][i]; + break; + } + } + return found; + } + +private: + StackArray, MAXMATERIALS> m_element_ids {}; + StackArray, MAXMATERIALS> m_volume_fractions {}; + axom::IndexType m_size {0}; + axom::IndexType m_nzones {0}; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/NodeArrayView.hpp b/src/axom/mir/views/NodeArrayView.hpp new file mode 100644 index 0000000000..954795e0f0 --- /dev/null +++ b/src/axom/mir/views/NodeArrayView.hpp @@ -0,0 +1,1089 @@ +// 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) + +#ifndef AXOM_MIR_VIEWS_NODE_ARRAY_VIEW_HPP_ +#define AXOM_MIR_VIEWS_NODE_ARRAY_VIEW_HPP_ + +#include "axom/slic/interface/slic.hpp" + +#include +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +/* +using Node = ::conduit::Node; +using int8 = ::conduit::int8; +using int16 = ::conduit::int16; +using int32 = ::conduit::int32; +using int64 = ::conduit::int64; +using uint8 = ::conduit::uint8; +using uint16 = ::conduit::uint16; +using uint32 = ::conduit::uint32; +using uint64 = ::conduit::uint64; +using float32 = ::conduit::float32; +using float64 = ::conduit::float64; +using index_t = ::conduit::index_t; +using DataType = ::conduit::DataType; +*/ + +namespace detail +{ +struct Delimiter +{ }; + +/// Used to separate arguments. +constexpr Delimiter ArgumentDelimiter; + +#if __cplusplus >= 201703L +// C++17 and later. +template +constexpr int encode_types(Args... args) +{ + return (... | args); +} +#else +template +constexpr int encode_types_impl(T arg) +{ + return arg; +} + +template +constexpr int encode_types_impl(T arg, Args... args) +{ + return (arg | encode_types_impl(args...)); +} + +template +constexpr int encode_types(Args... args) +{ + return encode_types_impl(args...); +} +#endif + +template +constexpr int select_types(Args... args) +{ + return encode_types((1 << args)...); +} + +constexpr bool type_selected(int flag, int bit) { return flag & (1 << bit); } + +constexpr int select_all_types() { return -1; } + +constexpr int select_index_types() +{ + return select_types(conduit::DataType::INT32_ID, + conduit::DataType::INT64_ID, + conduit::DataType::UINT32_ID, + conduit::DataType::UINT64_ID); +} + +constexpr int select_float_types() +{ + return select_types(conduit::DataType::FLOAT32_ID, + conduit::DataType::FLOAT64_ID); +} + +//------------------------------------------------------------------------------ +// General Node to ArrayView. Handle all types. +//------------------------------------------------------------------------------ +/// NOTE: Some of these functions use const_cast to get data into the ArrayView. +/// Is there a better way that does not let const bleed all over? +/// +/// TODO: Handle strided data from the Conduit node. + +template +std::enable_if_t Node_to_ArrayView_single_int8(const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_int8_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_int8( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported int8 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_int8(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_int8_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_int8( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported int8 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_int16( + const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_int16_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_int16( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported int16 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_int16(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_int16_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_int16( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported int16 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_int32( + const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_int32_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_int32( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported int32 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_int32(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_int32_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_int32( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported int32 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_int64( + const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_int64_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_int64( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported int64 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_int64(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_int64_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_int64( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported int64 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint8( + const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_uint8_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint8( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported uint8 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint8(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_uint8_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint8( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported uint8 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint16( + const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_uint16_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint16( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported uint16 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint16(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_uint16_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint16( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported uint16 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint32( + const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_uint32_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint32( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported uint32 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint32(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_uint32_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint32( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported uint32 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint64( + const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_uint64_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint64( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported uint64 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint64(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_uint64_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_uint64( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported uint64 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_float32( + const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_float32_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_float32( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported float32 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_float32(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_float32_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_float32( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported float32 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_float64( + const conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view( + const_cast(n.as_float64_ptr()), + size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_float64( + const conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported float64 node."); +} + +template +std::enable_if_t Node_to_ArrayView_single_float64(conduit::Node &n, + FuncType &&func) +{ + const auto size = n.dtype().number_of_elements(); + axom::ArrayView view(n.as_float64_ptr(), size); + func(view); +} + +template +std::enable_if_t Node_to_ArrayView_single_float64( + conduit::Node &AXOM_UNUSED_PARAM(n), + FuncType &&AXOM_UNUSED_PARAM(func)) +{ + SLIC_WARNING("Unsupported float64 node."); +} + +template +void Node_to_ArrayView_single(const conduit::Node &n, FuncType &&func) +{ + /* Later, with C++17, we can do this instead of using all of the SFINAE functions above: + if constexpr (type_selected(Types, conduit::DataType::INT8_ID)) + { + if(n.dtype().is_int8()) + { + axom::ArrayView view(n.as_int8_ptr(), size); + func(view); + } + } + */ + + if(n.dtype().is_int8()) + { + Node_to_ArrayView_single_int8( + n, + func); + } + else if(n.dtype().is_int16()) + { + Node_to_ArrayView_single_int16( + n, + func); + } + else if(n.dtype().is_int32()) + { + Node_to_ArrayView_single_int32( + n, + func); + } + else if(n.dtype().is_int64()) + { + Node_to_ArrayView_single_int64( + n, + func); + } + else if(n.dtype().is_uint8()) + { + Node_to_ArrayView_single_uint8( + n, + func); + } + else if(n.dtype().is_uint16()) + { + Node_to_ArrayView_single_uint16( + n, + func); + } + else if(n.dtype().is_uint32()) + { + Node_to_ArrayView_single_uint32( + n, + func); + } + else if(n.dtype().is_uint64()) + { + Node_to_ArrayView_single_uint64( + n, + func); + } + else if(n.dtype().is_float32()) + { + Node_to_ArrayView_single_float32< + type_selected(Types, conduit::DataType::FLOAT32_ID)>(n, func); + } + else if(n.dtype().is_float64()) + { + Node_to_ArrayView_single_float64< + type_selected(Types, conduit::DataType::FLOAT64_ID)>(n, func); + } + else + { + SLIC_ERROR("Unsupported data type " << n.dtype().name() << " on node " + << n.path()); + } +} + +template +void Node_to_ArrayView_single(conduit::Node &n, FuncType &&func) +{ + if(n.dtype().is_int8()) + { + Node_to_ArrayView_single_int8( + n, + func); + } + else if(n.dtype().is_int16()) + { + Node_to_ArrayView_single_int16( + n, + func); + } + else if(n.dtype().is_int32()) + { + Node_to_ArrayView_single_int32( + n, + func); + } + else if(n.dtype().is_int64()) + { + Node_to_ArrayView_single_int64( + n, + func); + } + else if(n.dtype().is_uint8()) + { + Node_to_ArrayView_single_uint8( + n, + func); + } + else if(n.dtype().is_uint16()) + { + Node_to_ArrayView_single_uint16( + n, + func); + } + else if(n.dtype().is_uint32()) + { + Node_to_ArrayView_single_uint32( + n, + func); + } + else if(n.dtype().is_uint64()) + { + Node_to_ArrayView_single_uint64( + n, + func); + } + else if(n.dtype().is_float32()) + { + Node_to_ArrayView_single_float32< + type_selected(Types, conduit::DataType::FLOAT32_ID)>(n, func); + } + else if(n.dtype().is_float64()) + { + Node_to_ArrayView_single_float64< + type_selected(Types, conduit::DataType::FLOAT64_ID)>(n, func); + } + else + { + SLIC_ERROR("Unsupported data type " << n.dtype().name() << " on node " + << n.path()); + } +} + +template +void Node_to_ArrayView_internal(FuncType &&func, Delimiter, View &...views) +{ + func(views...); +} + +template +void Node_to_ArrayView_internal(const conduit::Node &first, Args &&...args) +{ + Node_to_ArrayView_single(first, [&](auto view) { + Node_to_ArrayView_internal(args..., view); + }); +} + +template +void Node_to_ArrayView_internal(conduit::Node &first, Args &&...args) +{ + Node_to_ArrayView_single(first, [&](auto view) { + Node_to_ArrayView_internal(args..., view); + }); +} + +//------------------------------------------------------------------------------ +/// NOTE: handle const conduit::Node& better. For now, const_cast. + +template +std::enable_if_t Node_to_ArrayView_same_internal_int8( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_int8_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_int8( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +std::enable_if_t Node_to_ArrayView_same_internal_int16( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_int16_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_int16( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +std::enable_if_t Node_to_ArrayView_same_internal_int32( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_int32_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_int32( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +std::enable_if_t Node_to_ArrayView_same_internal_int64( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_int64_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_int64( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +std::enable_if_t Node_to_ArrayView_same_internal_uint8( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_uint8_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_uint8( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +std::enable_if_t Node_to_ArrayView_same_internal_uint16( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_uint16_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_uint16( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +std::enable_if_t Node_to_ArrayView_same_internal_uint32( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_uint32_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_uint32( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +std::enable_if_t Node_to_ArrayView_same_internal_uint64( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_uint64_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_uint64( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +std::enable_if_t Node_to_ArrayView_same_internal_float32( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_float32_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_float32( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +std::enable_if_t Node_to_ArrayView_same_internal_float64( + FuncType &&func, + Args &&...args) +{ + func(axom::ArrayView( + const_cast(args.as_float64_ptr()), + args.dtype().number_of_elements())...); +} + +template +std::enable_if_t Node_to_ArrayView_same_internal_float64( + FuncType &&AXOM_UNUSED_PARAM(func), + Args &&...AXOM_UNUSED_PARAM(args)) +{ } + +template +void Node_to_ArrayView_same_internal(FuncType &&func, + Delimiter, + const conduit::Node &first, + Args &&...args) +{ + if(first.dtype().is_int8()) + { + Node_to_ArrayView_same_internal_int8< + type_selected(Types, conduit::DataType::INT8_ID)>(func, first, args...); + } + else if(first.dtype().is_int16()) + { + Node_to_ArrayView_same_internal_int16< + type_selected(Types, conduit::DataType::INT16_ID)>(func, first, args...); + } + else if(first.dtype().is_int32()) + { + Node_to_ArrayView_same_internal_int32< + type_selected(Types, conduit::DataType::INT32_ID)>(func, first, args...); + } + else if(first.dtype().is_int64()) + { + Node_to_ArrayView_same_internal_int64< + type_selected(Types, conduit::DataType::INT64_ID)>(func, first, args...); + } + else if(first.dtype().is_uint8()) + { + Node_to_ArrayView_same_internal_uint8< + type_selected(Types, conduit::DataType::UINT8_ID)>(func, first, args...); + } + else if(first.dtype().is_uint16()) + { + Node_to_ArrayView_same_internal_uint16< + type_selected(Types, conduit::DataType::UINT16_ID)>(func, first, args...); + } + else if(first.dtype().is_uint32()) + { + Node_to_ArrayView_same_internal_uint32< + type_selected(Types, conduit::DataType::UINT32_ID)>(func, first, args...); + } + else if(first.dtype().is_uint64()) + { + Node_to_ArrayView_same_internal_uint64< + type_selected(Types, conduit::DataType::UINT64_ID)>(func, first, args...); + } + else if(first.dtype().is_float32()) + { + Node_to_ArrayView_same_internal_float32< + type_selected(Types, conduit::DataType::FLOAT32_ID)>(func, first, args...); + } + else if(first.dtype().is_float64()) + { + Node_to_ArrayView_same_internal_float64< + type_selected(Types, conduit::DataType::FLOAT64_ID)>(func, first, args...); + } + else + { + SLIC_ERROR("Unsupported data type " << first.dtype().name() << " on node " + << first.path()); + } +} + +template +void Node_to_ArrayView_same_internal(FuncType &&func, + Delimiter, + conduit::Node &first, + Args &&...args) +{ + if(first.dtype().is_int8()) + { + Node_to_ArrayView_same_internal_int8< + type_selected(Types, conduit::DataType::INT8_ID)>(func, first, args...); + } + else if(first.dtype().is_int16()) + { + Node_to_ArrayView_same_internal_int16< + type_selected(Types, conduit::DataType::INT16_ID)>(func, first, args...); + } + else if(first.dtype().is_int32()) + { + Node_to_ArrayView_same_internal_int32< + type_selected(Types, conduit::DataType::INT32_ID)>(func, first, args...); + } + else if(first.dtype().is_int64()) + { + Node_to_ArrayView_same_internal_int64< + type_selected(Types, conduit::DataType::INT64_ID)>(func, first, args...); + } + else if(first.dtype().is_uint8()) + { + Node_to_ArrayView_same_internal_uint8< + type_selected(Types, conduit::DataType::UINT8_ID)>(func, first, args...); + } + else if(first.dtype().is_uint16()) + { + Node_to_ArrayView_same_internal_uint16< + type_selected(Types, conduit::DataType::UINT16_ID)>(func, first, args...); + } + else if(first.dtype().is_uint32()) + { + Node_to_ArrayView_same_internal_uint32< + type_selected(Types, conduit::DataType::UINT32_ID)>(func, first, args...); + } + else if(first.dtype().is_uint64()) + { + Node_to_ArrayView_same_internal_uint64< + type_selected(Types, conduit::DataType::UINT64_ID)>(func, first, args...); + } + else if(first.dtype().is_float32()) + { + Node_to_ArrayView_same_internal_float32< + type_selected(Types, conduit::DataType::FLOAT32_ID)>(func, first, args...); + } + else if(first.dtype().is_float64()) + { + Node_to_ArrayView_same_internal_float64< + type_selected(Types, conduit::DataType::FLOAT64_ID)>(func, first, args...); + } + else + { + SLIC_ERROR("Unsupported data type " << first.dtype().name() << " on node " + << first.path()); + } +} + +/// Reorder args +template +void Node_to_ArrayView_same_internal(const conduit::Node &first, Args &&...args) +{ + Node_to_ArrayView_same_internal(args..., first); +} + +template +void Node_to_ArrayView_same_internal(conduit::Node &first, Args &&...args) +{ + Node_to_ArrayView_same_internal(args..., first); +} + +} // namespace detail + +//------------------------------------------------------------------------------ +// Node to ArrayView. Handle all types. +//------------------------------------------------------------------------------ + +/*! + * \brief Convert a series of Conduit nodes to axom::ArrayView and pass the concrete + * views to a lambda function passed as the last argument. + * + * \note This method handles all Conduit array types and will instantiate views + * of any type. In other words, mixed node types can be used. + * + * \param first A Conduit node to be convered to a view. + * \param args A sequence of Conduit nodes, followed by a lambda that can accept + * the same number of views of any type. + * + * Node_to_ArrayView(node1, node2, [](auto &view1, auto &view2) { }); + * + */ +template +void Node_to_ArrayView(const conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_internal(first, args..., detail::ArgumentDelimiter); +} + +template +void Node_to_ArrayView(conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_internal(first, args..., detail::ArgumentDelimiter); +} + +/*! + * \brief Convert a series of Conduit nodes to axom::ArrayView and pass the concrete + * views to a lambda function passed as the last argument. + * + * \note This method handles all Conduit array types. All nodes will be treated + * as the same type as the first Conduit node. Use this when all nodes + * are assumed to contain the same type since it results in less code. + * + * \param first A Conduit node to be convered to a view. + * \param args A sequence of Conduit nodes, followed by a lambda that can accept + * the same number of views of any type. + * + * Node_to_ArrayView_same(node1, node2, [](auto &view1, auto &view2) { }); + * + */ +template +void Node_to_ArrayView_same(const conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_same_internal(first, + args..., + detail::ArgumentDelimiter); +} + +template +void Node_to_ArrayView_same(conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_same_internal(first, + args..., + detail::ArgumentDelimiter); +} + +//------------------------------------------------------------------------------ +// Index Node to ArrayView. Handle types used for indexing. +//------------------------------------------------------------------------------ + +template +void IndexNode_to_ArrayView(const conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_internal( + first, + args..., + detail::ArgumentDelimiter); +} + +template +void IndexNode_to_ArrayView(conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_internal( + first, + args..., + detail::ArgumentDelimiter); +} + +template +void IndexNode_to_ArrayView_same(const conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_same_internal( + first, + args..., + detail::ArgumentDelimiter); +} + +template +void IndexNode_to_ArrayView_same(conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_same_internal( + first, + args..., + detail::ArgumentDelimiter); +} + +//------------------------------------------------------------------------------ +// Float Node to ArrayView. Handle float types. +//------------------------------------------------------------------------------ +template +void FloatNode_to_ArrayView(const conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_internal( + first, + args..., + detail::ArgumentDelimiter); +} + +template +void FloatNode_to_ArrayView(conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_internal( + first, + args..., + detail::ArgumentDelimiter); +} + +template +void FloatNode_to_ArrayView_same(const conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_same_internal( + first, + args..., + detail::ArgumentDelimiter); +} + +template +void FloatNode_to_ArrayView_same(conduit::Node &first, Args &&...args) +{ + detail::Node_to_ArrayView_same_internal( + first, + args..., + detail::ArgumentDelimiter); +} + +} // namespace views +} // namespace mir +} // namespace axom + +#endif diff --git a/src/axom/mir/views/RectilinearCoordsetView.hpp b/src/axom/mir/views/RectilinearCoordsetView.hpp new file mode 100644 index 0000000000..da88b4b3b5 --- /dev/null +++ b/src/axom/mir/views/RectilinearCoordsetView.hpp @@ -0,0 +1,229 @@ +// 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) + +#ifndef AXOM_MIR_RECTILINEAR_COORDSET_VIEW_HPP_ +#define AXOM_MIR_RECTILINEAR_COORDSET_VIEW_HPP_ + +#include "axom/core/StackArray.hpp" +#include "axom/core/ArrayView.hpp" +#include "axom/primal/geometry/Point.hpp" +#include "axom/mir/views/StructuredIndexing.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/// NOTE: The rectilinear coordset views could be combined into a single RectilinearCoordset +/// view that is templated on NDIMS but the resulting SFINAE would just overcomplicate it. + +/*! + * \class This class provides a view for Conduit/Blueprint 2D rectilinear coordsets. + */ +template +class RectilinearCoordsetView2 +{ +public: + using LogicalIndex = axom::StackArray; + using IndexType = axom::IndexType; + using value_type = DataType; + using PointType = axom::primal::Point; + + constexpr static int dimension() { return 2; } + + /*! + * \brief Constructor + * + * \param x The first coordinate component. + * \param y The second coordinate component. + */ + AXOM_HOST_DEVICE + RectilinearCoordsetView2(const axom::ArrayView &x, + const axom::ArrayView &y) + : m_coordinates {x, y} + , m_indexing(LogicalIndex {{x.size(), y.size()}}) + { } + + /*! + * \brief Return the number of points in the coordset. + * + * \return The number of points in the coordset. + */ + /// @{ + AXOM_HOST_DEVICE + IndexType size() const { return m_indexing.size(); } + + AXOM_HOST_DEVICE + IndexType numberOfNodes() const { return m_indexing.size(); } + /// @} + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The logical index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType getPoint(LogicalIndex vertex_index) const + { + const DataType X[2] = {m_coordinates[0][vertex_index[0]], + m_coordinates[1][vertex_index[1]]}; + return PointType(X); + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType getPoint(IndexType vertex_index) const + { + return getPoint(m_indexing.IndexToLogicalIndex(vertex_index)); + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The logical index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType operator[](LogicalIndex vertex_index) const + { + return getPoint(vertex_index); + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType operator[](IndexType vertex_index) const + { + return getPoint(m_indexing.IndexToLogicalIndex(vertex_index)); + } + +private: + axom::ArrayView m_coordinates[2]; + StructuredIndexing m_indexing; +}; + +/*! + * \class This class provides a view for Conduit/Blueprint 3D rectilinear coordsets. + */ +template +class RectilinearCoordsetView3 +{ +public: + using LogicalIndex = axom::StackArray; + using IndexType = axom::IndexType; + using value_type = DataType; + using PointType = axom::primal::Point; + + constexpr static int dimension() { return 3; } + + /*! + * \brief Constructor + * + * \param x The first coordinate component. + * \param y The second coordinate component. + * \param z The third coordinate component. + */ + AXOM_HOST_DEVICE + RectilinearCoordsetView3(const axom::ArrayView &x, + const axom::ArrayView &y, + const axom::ArrayView &z) + : m_coordinates {x, y, z} + , m_indexing(LogicalIndex {{x.size(), y.size(), z.size()}}) + { } + + /*! + * \brief Return the number of nodes in the coordset. + * + * \return The number of nodes in the coordset. + */ + /// @{ + + AXOM_HOST_DEVICE + IndexType size() const { return m_indexing.size(); } + + AXOM_HOST_DEVICE + IndexType numberOfNodes() const { return m_indexing.size(); } + /// @} + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The logical index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType getPoint(LogicalIndex vertex_index) const + { + const DataType X[3] = {m_coordinates[0][vertex_index[0]], + m_coordinates[1][vertex_index[1]], + m_coordinates[2][vertex_index[2]]}; + return PointType(X); + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType getPoint(IndexType vertex_index) const + { + return getPoint(m_indexing.IndexToLogicalIndex(vertex_index)); + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The logical index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType operator[](LogicalIndex vertex_index) const + { + return getPoint(vertex_index); + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType operator[](IndexType vertex_index) const + { + return getPoint(m_indexing.IndexToLogicalIndex(vertex_index)); + } + +private: + axom::ArrayView m_coordinates[3]; + StructuredIndexing m_indexing; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/Shapes.hpp b/src/axom/mir/views/Shapes.hpp new file mode 100644 index 0000000000..313d582e7d --- /dev/null +++ b/src/axom/mir/views/Shapes.hpp @@ -0,0 +1,1033 @@ +// 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) + +#ifndef AXOM_MIR_VIEWS_SHAPES_HPP_ +#define AXOM_MIR_VIEWS_SHAPES_HPP_ + +#include "axom/core/ArrayView.hpp" + +#include +#include + +#include +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief Shape ids. These are used to identify shapes. These are used as + * indices in bit fields in some algorithms. + */ +enum +{ + Point_ShapeID = 0, + Line_ShapeID = 1, + Tri_ShapeID = 2, + Quad_ShapeID = 3, + Polygon_ShapeID = 4, + Tet_ShapeID = 5, + Pyramid_ShapeID = 6, + Wedge_ShapeID = 7, + Hex_ShapeID = 8, + Polyhedron_ShapeID = 9, + Mixed_ShapeID = 10, + + Invalid_ShapeID = 20 +}; + +/*! + \brief Point type traits. + +\verbatim + + 0* + +\endverbatim + */ +struct PointTraits +{ + AXOM_HOST_DEVICE constexpr static int id() { return Point_ShapeID; } + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return false; } + + AXOM_HOST_DEVICE constexpr static IndexType dimension() { return 0; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodes() { return 1; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodesInFace( + int AXOM_UNUSED_PARAM(faceIndex)) + { + return 1; + } + AXOM_HOST_DEVICE constexpr static IndexType maxNodesInFace() { return 1; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfFaces() { return 0; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfEdges() { return 0; } + AXOM_HOST_DEVICE constexpr static IndexType zoneOffset(int zoneIndex) + { + return zoneIndex; + } + + constexpr static IndexType faces[][1] = {{0}}; + + AXOM_HOST_DEVICE constexpr static axom::StackArray getEdge( + int AXOM_UNUSED_PARAM(edgeIndex)) + { + return axom::StackArray(); + } + + AXOM_HOST_DEVICE constexpr static const char *name() { return "point"; } +}; + +/*! + \brief Line type traits. + +\verbatim + + 0*-----------* 1 + +\endverbatim + */ +struct LineTraits +{ + AXOM_HOST_DEVICE constexpr static int id() { return Line_ShapeID; } + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return false; } + + AXOM_HOST_DEVICE constexpr static IndexType dimension() { return 1; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodes() { return 2; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodesInFace( + int AXOM_UNUSED_PARAM(faceIndex)) + { + return 2; + } + AXOM_HOST_DEVICE constexpr static IndexType maxNodesInFace() { return 2; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfFaces() { return 1; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfEdges() { return 1; } + AXOM_HOST_DEVICE constexpr static IndexType zoneOffset(int zoneIndex) + { + return numberOfNodes() * zoneIndex; + } + + constexpr static IndexType faces[][2] = {{0, 1}}; + + AXOM_HOST_DEVICE constexpr static axom::StackArray getEdge( + int /*edgeIndex*/) + { + return axom::StackArray {0, 1}; + } + + AXOM_HOST_DEVICE constexpr static const char *name() { return "line"; } +}; + +/*! + \brief Triangle type traits. + +\verbatim + + 2* + |\ + | \ + | \ + | \ + | \ + 0*-----* 1 + +\endverbatim + */ +struct TriTraits +{ + AXOM_HOST_DEVICE constexpr static int id() { return Tri_ShapeID; } + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return false; } + + AXOM_HOST_DEVICE constexpr static IndexType dimension() { return 2; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodes() { return 3; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodesInFace( + int AXOM_UNUSED_PARAM(faceIndex)) + { + return 3; + } + AXOM_HOST_DEVICE constexpr static IndexType maxNodesInFace() { return 3; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfFaces() { return 1; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfEdges() { return 3; } + AXOM_HOST_DEVICE constexpr static IndexType zoneOffset(int zoneIndex) + { + return numberOfNodes() * zoneIndex; + } + + constexpr static IndexType faces[][3] = {{0, 1, 2}}; + + AXOM_HOST_DEVICE constexpr static axom::StackArray getEdge( + int edgeIndex) + { + const axom::StackArray edges[] = {{0, 1}, {1, 2}, {2, 0}}; + return edges[edgeIndex]; + } + + AXOM_HOST_DEVICE constexpr static const char *name() { return "tri"; } +}; + +/*! + \brief Quad type traits. + +\verbatim + + 3*-----------* 2 + | | + | | + | | + | | + | | + 0*-----------* 1 + +\endverbatim + */ +struct QuadTraits +{ + AXOM_HOST_DEVICE constexpr static int id() { return Quad_ShapeID; } + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return false; } + + AXOM_HOST_DEVICE constexpr static IndexType dimension() { return 2; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodes() { return 4; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodesInFace( + int AXOM_UNUSED_PARAM(faceIndex)) + { + return 4; + } + AXOM_HOST_DEVICE constexpr static IndexType maxNodesInFace() { return 4; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfFaces() { return 1; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfEdges() { return 4; } + AXOM_HOST_DEVICE constexpr static IndexType zoneOffset(int zoneIndex) + { + return numberOfNodes() * zoneIndex; + } + + constexpr static IndexType faces[][4] = {{0, 1, 2, 3}}; + + AXOM_HOST_DEVICE constexpr static axom::StackArray getEdge( + int edgeIndex) + { + const axom::StackArray edges[] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}}; + return edges[edgeIndex]; + } + + AXOM_HOST_DEVICE constexpr static const char *name() { return "quad"; } +}; + +/*! + \brief Tet type traits. + +\verbatim + + 3 + * + /|\ face 0: 0,2,1 + / | \ face 1: 0,1,3 + / | \ face 2: 1,2,3 + / | \ face 3: 2,0,3 +0*----|----* 2 + \ | / edge 0: 0,1 + \ | / edge 1: 1,2 + \ | / edge 2: 2,0 + \|/ edge 3: 0,3 + * edge 4: 1,3 + 1 edge 5: 2,3 + +\endverbatim + */ +struct TetTraits +{ + AXOM_HOST_DEVICE constexpr static int id() { return Tet_ShapeID; } + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return false; } + + AXOM_HOST_DEVICE constexpr static IndexType dimension() { return 3; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodes() { return 4; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodesInFace( + int AXOM_UNUSED_PARAM(faceIndex)) + { + return 3; + } + AXOM_HOST_DEVICE constexpr static IndexType maxNodesInFace() { return 3; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfFaces() { return 4; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfEdges() { return 6; } + AXOM_HOST_DEVICE constexpr static IndexType zoneOffset(int zoneIndex) + { + return numberOfNodes() * zoneIndex; + } + + constexpr static IndexType faces[][3] = {{0, 1, 3}, + {1, 2, 3}, + {2, 0, 3}, + {0, 2, 1}}; + + AXOM_HOST_DEVICE constexpr static axom::StackArray getEdge( + int edgeIndex) + { + const axom::StackArray edges[] = + {{0, 1}, {1, 2}, {2, 0}, {0, 3}, {1, 3}, {2, 3}}; + return edges[edgeIndex]; + } + + AXOM_HOST_DEVICE constexpr static const char *name() { return "tet"; } +}; + +/*! + \brief Pyramid type traits. + +\verbatim + +3*-----------* 2 face 0: 3,2,1,0 + |\ /| face 1: 0,1,4 + | \ / | face 2: 1,2,4 + | \ / | face 3: 2,3,4 + | \ / | face 4: 3,0,4 + | \ / | + | * 4 | edge 0: 0,1 + | / \ | edge 1: 1,2 + | / \ | edge 2: 2,3 + | / \ | edge 3: 3,0 + | / \ | edge 4: 0,4 + |/ \| edge 5: 1,4 +0*-----------* 1 edge 6: 2,4 + edge 7: 3,4 + +\endverbatim + */ +struct PyramidTraits +{ + AXOM_HOST_DEVICE constexpr static int id() { return Pyramid_ShapeID; } + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return false; } + + AXOM_HOST_DEVICE constexpr static IndexType dimension() { return 3; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodes() { return 5; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodesInFace(int faceIndex) + { + return faceIndex == 0 ? 4 : 3; + } + AXOM_HOST_DEVICE constexpr static IndexType maxNodesInFace() { return 4; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfFaces() { return 5; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfEdges() { return 8; } + AXOM_HOST_DEVICE constexpr static IndexType zoneOffset(int zoneIndex) + { + return numberOfNodes() * zoneIndex; + } + + constexpr static int faces[][4] = {{3, 2, 1, 0}, + {0, 1, 4, -1}, + {1, 2, 4, -1}, + {2, 3, 4, -1}, + {3, 0, 4, -1}}; + + AXOM_HOST_DEVICE constexpr static axom::StackArray getEdge( + int edgeIndex) + { + const axom::StackArray edges[] = + {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {0, 4}, {1, 4}, {2, 4}, {3, 4}}; + return edges[edgeIndex]; + } + + AXOM_HOST_DEVICE constexpr static const char *name() { return "pyramid"; } +}; + +/*! + \brief Wedge type traits. + +\verbatim + +3*---------* 5 face 0: 0,2,1 + |\ /| face 1: 3,4,5 + | \ / | face 2: 0,1,4,3 + | \ / | face 3: 1,2,5,4 + | \ / | face 4: 2,0,3,5 + | *4 | + | | | edge 0: 0,1 +0*----|----* 2 edge 1: 1,2 + \ | / edge 2: 2,0 + \ | / edge 3: 3,4 + \ | / edge 4: 4,5 + \|/ edge 5: 5,3 + * 1 edge 6: 0,3 + edge 7: 1,4 + edge 8: 2,3 + +\endverbatim + */ +struct WedgeTraits +{ + AXOM_HOST_DEVICE constexpr static int id() { return Wedge_ShapeID; } + + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return false; } + + AXOM_HOST_DEVICE constexpr static IndexType dimension() { return 3; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodes() { return 6; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodesInFace(int faceIndex) + { + return (faceIndex == 0 || faceIndex == 1) ? 3 : 4; + } + AXOM_HOST_DEVICE constexpr static IndexType maxNodesInFace() { return 4; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfFaces() { return 5; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfEdges() { return 9; } + AXOM_HOST_DEVICE constexpr static IndexType zoneOffset(int zoneIndex) + { + return numberOfNodes() * zoneIndex; + } + + constexpr static int faces[][4] = {{0, 2, 1, -1}, + {3, 4, 5, -1}, + {0, 1, 4, 3}, + {1, 2, 5, 4}, + {2, 0, 3, 5}}; + + AXOM_HOST_DEVICE constexpr static axom::StackArray getEdge( + int edgeIndex) + { + const axom::StackArray edges[] = + {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}, {0, 3}, {1, 4}, {2, 5}}; + return edges[edgeIndex]; + } + + AXOM_HOST_DEVICE constexpr static const char *name() { return "wedge"; } +}; + +/*! + \brief Hex type traits. + +\verbatim + + 4*------------* 7 + /| /| + / | / | + / | / | + 5*------------*6 | + | | | | + | | | | + | 0*--------|---* 3 + | / | / + | / | / + |/ |/ + *------------* + 1 2 + +\endverbatim + */ +struct HexTraits +{ + AXOM_HOST_DEVICE constexpr static int id() { return Hex_ShapeID; } + + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return false; } + + AXOM_HOST_DEVICE constexpr static IndexType dimension() { return 3; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodes() { return 8; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfNodesInFace( + int AXOM_UNUSED_PARAM(faceIndex)) + { + return 4; + } + AXOM_HOST_DEVICE constexpr static IndexType maxNodesInFace() { return 4; } + + AXOM_HOST_DEVICE constexpr static IndexType numberOfFaces() { return 6; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfEdges() { return 12; } + AXOM_HOST_DEVICE constexpr static IndexType zoneOffset(int zoneIndex) + { + return numberOfNodes() * zoneIndex; + } + + constexpr static IndexType faces[][4] = {{3, 0, 4, 7}, + {1, 2, 6, 5}, + {0, 1, 5, 4}, + {3, 7, 6, 2}, + {0, 3, 2, 1}, + {4, 5, 6, 7}}; + + AXOM_HOST_DEVICE constexpr static axom::StackArray getEdge( + int edgeIndex) + { + const axom::StackArray edges[] = {{0, 1}, + {1, 2}, + {2, 3}, + {3, 0}, + {4, 5}, + {5, 6}, + {6, 7}, + {7, 4}, + {0, 4}, + {1, 5}, + {3, 7}, + {2, 6}}; + return edges[edgeIndex]; + } + + AXOM_HOST_DEVICE constexpr static const char *name() { return "hex"; } +}; + +/*! + +\verbatim + +n-1 *-... * 2 + | | + | | + 0 *-----* 1 + +\endverbatim + */ +struct PolygonTraits +{ + AXOM_HOST_DEVICE constexpr static int id() { return Polygon_ShapeID; } + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return true; } + + AXOM_HOST_DEVICE constexpr static IndexType dimension() { return 2; } + AXOM_HOST_DEVICE constexpr static IndexType numberOfFaces() { return 1; } + AXOM_HOST_DEVICE constexpr static IndexType maxNodesInFace() { return 20; } + AXOM_HOST_DEVICE constexpr static const char *name() { return "polygon"; } +}; + +/*! + * \brief This struct represents a polygon zone. + */ +template +struct PolygonShape : public PolygonTraits +{ + using ConnectivityType = ConnType; + using ConnectivityView = axom::ArrayView; + + /*! + * \brief Construct a shape. + */ + AXOM_HOST_DEVICE PolygonShape(const ConnectivityView &ids) : m_ids(ids) { } + + /*! + * \brief Get the ids that make up this shape. + * + * \return A view containing the ids that make up this shape. + */ + AXOM_HOST_DEVICE const ConnectivityView &getIds() const { return m_ids; } + + /*! + * \brief Get the ids for the requested face. + * + * \param faceIndex The index of the desired face. + * + * \return An array view (wrapping m_faceIds) that contains the ids for the face. + */ + AXOM_HOST_DEVICE ConnectivityView getFace(int AXOM_UNUSED_PARAM(faceIndex)) const + { + return m_ids; + } + + AXOM_HOST_DEVICE axom::StackArray getEdge(int edgeIndex) const + { + const auto p0 = edgeIndex % m_ids.size(); + const auto p1 = (edgeIndex + 1) % m_ids.size(); + return axom::StackArray {p0, p1}; + } + +private: + ConnectivityView m_ids; +}; + +/*! + * \brief This class extends the ShapeTraits with object state so it can represent a zone. + * + * \tparam ShapeTraits A shape traits class from which to inherit. + * \tparam ConnStorage A view or container that contains connectivity. + * + */ +template +struct Shape : public ShapeTraits +{ + using ConnectivityStorage = ConnStorage; + using ConnectivityStorageRef = ConnStorage &; + using ConnectivityStorageConstRef = const ConnStorage &; + using ConnectivityType = typename ConnStorage::value_type; + using ConnectivityView = axom::ArrayView; + + /*! + * \brief Construct a shape. + */ + AXOM_HOST_DEVICE Shape() : m_ids(), m_faceIds() { } + + /*! + * \brief Construct a shape. + * + * \param ids A reference to connectivity storage for this shape. + */ + AXOM_HOST_DEVICE Shape(ConnectivityStorageConstRef ids) + : m_ids(ids) + , m_faceIds() + { + assert(m_ids.size() == ShapeTraits::numberOfNodes()); + } + + /*! + * \brief Get a specific id that makes up this shape. + * + * \return The i'th id that makes up this shape. + */ + AXOM_HOST_DEVICE ConnectivityType getId(size_t index) const + { +#if defined(AXOM_DEBUG) + assert(index < static_cast(m_ids.size())); +#endif + return m_ids[index]; + } + + /*! + * \brief Get the storage for the ids that make up this shape. + * + * \return The container for the ids that make up this shape. + */ + AXOM_HOST_DEVICE ConnectivityStorageRef getIdsStorage() { return m_ids; } + + /*! + * \brief Get the storage for the ids that make up this shape. + * + * \return The container for the ids that make up this shape. + */ + AXOM_HOST_DEVICE ConnectivityStorageConstRef getIdsStorage() const + { + return m_ids; + } + + /*! + * \brief Get the ids that make up this shape as a view. + * + * \return A view containing the ids that make up this shape. + */ + AXOM_HOST_DEVICE ConnectivityView getIds() const + { + return ConnectivityView(const_cast(m_ids.data()), + m_ids.size()); + } + + /*! + * \brief Get the unique ids that make up this shape. For basic shapes, assume they are unique. + * + * \return The unique ids that make up this shape. + */ + AXOM_HOST_DEVICE ConnectivityStorageConstRef getUniqueIds() const + { + return m_ids; + } + + /*! + * \brief Get the ids for the requested face. + * + * \param faceIndex The index of the desired face. + * + * \return The ids that make up the face + */ + /// @{ + template + AXOM_HOST_DEVICE + typename std::enable_if<_ndims == 2, ConnectivityStorageConstRef>::type + getFace(axom::IndexType AXOM_UNUSED_PARAM(faceIndex)) const + { + return m_ids; + } + + // template + // AXOM_HOST_DEVICE typename std::enable_if<_ndims > 2, ConnectivityStorage>::type + // getFace(axom::IndexType faceIndex) const + // { + // const auto nnodes = ShapeTraits::numberOfNodesInFace(faceIndex); + // for(IndexType i = 0; i < nnodes; i++) + // m_faceIds[i] = m_ids[ShapeTraits::faces[faceIndex][i]]; + // return ConnectivityStorage(m_faceIds.m_data, nnodes); + // } + /// @} + +private: + ConnectivityStorage m_ids; + mutable axom::StackArray m_faceIds; +}; + +/*! + * \brief Some concrete shape classes based on the shape traits. + * + * \tparam ConnType A type of the connectivity values or a type that "stores" + * the data either in actuality, or as a view. If an integral + * type is passed, an axom::ArrayView will be used. + */ +/// @{ +template +using LineShape = + Shape::value, + axom::ArrayView, + ConnType>::type>; + +template +using TriShape = + Shape::value, + axom::ArrayView, + ConnType>::type>; + +template +using QuadShape = + Shape::value, + axom::ArrayView, + ConnType>::type>; + +template +using TetShape = + Shape::value, + axom::ArrayView, + ConnType>::type>; + +template +using PyramidShape = + Shape::value, + axom::ArrayView, + ConnType>::type>; + +template +using WedgeShape = + Shape::value, + axom::ArrayView, + ConnType>::type>; + +template +using HexShape = + Shape::value, + axom::ArrayView, + ConnType>::type>; +/// @} + +/*! + * \brief This is a shape that can act as any of the other shapes. + * + * \tparam ConnType type of the connectivity values. + * + * \note This is a substitute for polymorphism so we can run on device. + */ +template +struct VariableShape +{ + using ConnectivityStorage = axom::ArrayView; + using ConnectivityStorageRef = ConnectivityStorage &; + using ConnectivityStorageConstRef = const ConnectivityStorage &; + + using ConnectivityType = ConnType; + using ConnectivityView = ConnectivityStorage; + + /*! + * \brief Constructor + * + * \param shapeId The shape id that describes the points. + * \param ids The ids that describe the shape. + */ + AXOM_HOST_DEVICE + VariableShape(int shapeId, ConnectivityStorageConstRef ids) + : m_shapeId(shapeId) + , m_ids(ids) + { + assert(shapeId >= Point_ShapeID && shapeId <= Hex_ShapeID); + } + + /*! + * \brief Returns the shape id of the actual shape represented by the variable shape. + * \return The actual shape represented. + */ + AXOM_HOST_DEVICE int id() const { return m_shapeId; } + + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return false; } + AXOM_HOST_DEVICE constexpr static bool is_variable_size() { return true; } + + AXOM_HOST_DEVICE IndexType dimension() const + { + IndexType dim = 2; + switch(m_shapeId) + { + case Line_ShapeID: + dim = LineTraits::dimension(); + break; + case Tri_ShapeID: + dim = TriTraits::dimension(); + break; + case Quad_ShapeID: + dim = QuadTraits::dimension(); + break; + case Polygon_ShapeID: + dim = PolygonTraits::dimension(); + break; + case Tet_ShapeID: + dim = TetTraits::dimension(); + break; + case Pyramid_ShapeID: + dim = PyramidTraits::dimension(); + break; + case Wedge_ShapeID: + dim = WedgeTraits::dimension(); + break; + case Hex_ShapeID: + dim = HexTraits::dimension(); + break; + } + return dim; + } + + AXOM_HOST_DEVICE IndexType numberOfNodes() const { return m_ids.size(); } + + AXOM_HOST_DEVICE IndexType numberOfNodesInFace(int faceIndex) const + { + IndexType nnodes = 0; + switch(m_shapeId) + { + case Line_ShapeID: + nnodes = LineTraits::numberOfNodesInFace(faceIndex); + break; + case Tri_ShapeID: + nnodes = TriTraits::numberOfNodesInFace(faceIndex); + break; + case Quad_ShapeID: + nnodes = QuadTraits::numberOfNodesInFace(faceIndex); + break; + case Polygon_ShapeID: + nnodes = (faceIndex == 0) ? m_ids.size() : 0; + break; + case Tet_ShapeID: + nnodes = TetTraits::numberOfNodesInFace(faceIndex); + break; + case Pyramid_ShapeID: + nnodes = PyramidTraits::numberOfNodesInFace(faceIndex); + break; + case Wedge_ShapeID: + nnodes = WedgeTraits::numberOfNodesInFace(faceIndex); + break; + case Hex_ShapeID: + nnodes = HexTraits::numberOfNodesInFace(faceIndex); + break; + } + return nnodes; + } + + AXOM_HOST_DEVICE IndexType maxNodesInFace() const + { + IndexType nnodes = 0; + switch(m_shapeId) + { + case Line_ShapeID: + nnodes = LineTraits::maxNodesInFace(); + break; + case Tri_ShapeID: + nnodes = TriTraits::maxNodesInFace(); + break; + case Quad_ShapeID: + nnodes = QuadTraits::maxNodesInFace(); + break; + case Polygon_ShapeID: + nnodes = PolygonShape::maxNodesInFace(); + break; + case Tet_ShapeID: + nnodes = TetTraits::maxNodesInFace(); + break; + case Pyramid_ShapeID: + nnodes = PyramidTraits::maxNodesInFace(); + break; + case Wedge_ShapeID: + nnodes = WedgeTraits::maxNodesInFace(); + break; + case Hex_ShapeID: + nnodes = HexTraits::maxNodesInFace(); + break; + } + return nnodes; + } + + AXOM_HOST_DEVICE IndexType numberOfFaces() const + { + IndexType nfaces = 0; + switch(m_shapeId) + { + case Line_ShapeID: + nfaces = LineTraits::numberOfFaces(); + break; + case Tri_ShapeID: + nfaces = TriTraits::numberOfFaces(); + break; + case Quad_ShapeID: + nfaces = QuadTraits::numberOfFaces(); + break; + case Polygon_ShapeID: + nfaces = 1; + break; + case Tet_ShapeID: + nfaces = TetTraits::numberOfFaces(); + break; + case Pyramid_ShapeID: + nfaces = PyramidTraits::numberOfFaces(); + break; + case Wedge_ShapeID: + nfaces = WedgeTraits::numberOfFaces(); + break; + case Hex_ShapeID: + nfaces = HexTraits::numberOfFaces(); + break; + } + return nfaces; + } + + AXOM_HOST_DEVICE IndexType numberOfEdges() const + { + IndexType nedges = 0; + switch(m_shapeId) + { + case Line_ShapeID: + nedges = LineTraits::numberOfEdges(); + break; + case Tri_ShapeID: + nedges = TriTraits::numberOfEdges(); + break; + case Quad_ShapeID: + nedges = QuadTraits::numberOfEdges(); + break; + case Polygon_ShapeID: + nedges = m_ids.size(); + break; + case Tet_ShapeID: + nedges = TetTraits::numberOfEdges(); + break; + case Pyramid_ShapeID: + nedges = PyramidTraits::numberOfEdges(); + break; + case Wedge_ShapeID: + nedges = WedgeTraits::numberOfEdges(); + break; + case Hex_ShapeID: + nedges = HexTraits::numberOfEdges(); + break; + } + return nedges; + } + + AXOM_HOST_DEVICE axom::StackArray getEdge(int edgeIndex) const + { + axom::StackArray edge; + switch(m_shapeId) + { + case Line_ShapeID: + edge = LineTraits::getEdge(edgeIndex); + break; + case Tri_ShapeID: + edge = TriTraits::getEdge(edgeIndex); + break; + case Quad_ShapeID: + edge = QuadTraits::getEdge(edgeIndex); + break; + case Polygon_ShapeID: + { + const auto n = m_ids.size(); + edge[0] = edgeIndex % n; + edge[1] = (edgeIndex + 1) % n; + break; + } + case Tet_ShapeID: + edge = TetTraits::getEdge(edgeIndex); + break; + case Pyramid_ShapeID: + edge = PyramidTraits::getEdge(edgeIndex); + break; + case Wedge_ShapeID: + edge = WedgeTraits::getEdge(edgeIndex); + break; + case Hex_ShapeID: + edge = HexTraits::getEdge(edgeIndex); + break; + } + return edge; + } + + /*! + * \brief Get a specific id that makes up this shape. + * + * \return The i'th id that makes up this shape. + */ + AXOM_HOST_DEVICE ConnectivityType getId(IndexType index) const + { + return m_ids[index]; + } + + /*! + * \brief Get the ids that make up this shape. + * + * \return A view containing the ids that make up this shape. + */ + AXOM_HOST_DEVICE ConnectivityView getIds() const { return m_ids; } + + AXOM_HOST_DEVICE constexpr static const char *name() { return "mixed"; } + +private: + int m_shapeId; + ConnectivityStorage m_ids; +}; + +/*! + * \brief Given a shape name (matches Blueprint shape name), return the Shape id() value. + * + * \param name The shape name. + * + * \return The shape id that matches the name, or 0 if there is no match. + */ +inline int shapeNameToID(const std::string &name) +{ + int id = Invalid_ShapeID; + if(name == LineTraits::name()) + id = Line_ShapeID; + else if(name == TriTraits::name()) + id = Tri_ShapeID; + else if(name == QuadTraits::name()) + id = Quad_ShapeID; + else if(name == PolygonTraits::name()) + id = Polygon_ShapeID; + else if(name == TetTraits::name()) + id = Tet_ShapeID; + else if(name == PyramidTraits::name()) + id = Pyramid_ShapeID; + else if(name == WedgeTraits::name()) + id = Wedge_ShapeID; + else if(name == HexTraits::name()) + id = Hex_ShapeID; + else if(name == "polyhedral") + id = Polyhedron_ShapeID; + else if(name == "mixed") + id = Mixed_ShapeID; + return id; +} + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/StridedStructuredIndexing.hpp b/src/axom/mir/views/StridedStructuredIndexing.hpp new file mode 100644 index 0000000000..568c00ce92 --- /dev/null +++ b/src/axom/mir/views/StridedStructuredIndexing.hpp @@ -0,0 +1,396 @@ +// 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) + +#ifndef AXOM_MIR_STRIDED_STRUCTURED_INDEXING_HPP_ +#define AXOM_MIR_STRIDED_STRUCTURED_INDEXING_HPP_ + +#include "axom/core/StackArray.hpp" +#include "axom/core/ArrayView.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \accelerated + * \class StridedStructuredIndexing + * + * \brief This class encapsulates data for strided structured indexing and provides methods for creating/manipulating indices. + * + * \tparam NDIMS The number of dimensions. + * + * + * Strided structured indexing lets us index part of a larger overall indexing space. + * We can index it using indices local to the selected window and the class provides a + * mechanism to return indices in the overall indexing space. + * + * Example: + * + * x---x---x---x---x---x---x + * | | | | | | | + * x---x---*---*---*---*---x *=real node, x=ignored node, O=origin node 16 + * | | | | | | | + * x---x---*---*---*---*---x dims={4,3} + * | | | | | | | origin={2,2} + * x---x---O---*---*---*---x stride={1,7} + * | | | | | | | + * x---x---x---x---x---x---x + * | | | | | | | + * x---x---x---x---x---x---x + * + */ +template +struct StridedStructuredIndexing +{ + using IndexType = IndexT; + using LogicalIndex = axom::StackArray; + + AXOM_HOST_DEVICE constexpr static int dimension() { return NDIMS; } + + /*! + * \brief Return whether the view supports strided structured indexing. + * \return true + */ + AXOM_HOST_DEVICE static constexpr bool supports_strided_structured_indexing() + { + return true; + } + + /*! + * \brief constructor + */ + AXOM_HOST_DEVICE + StridedStructuredIndexing() : m_dimensions(), m_offsets(), m_strides() + { + for(int i = 0; i < NDIMS; i++) + { + m_dimensions[i] = 1; + m_offsets[i] = 0; + m_strides[i] = 1; + } + } + + /*! + * \brief Constructor + * + * \param dims The number of zones in each logical dimension. + * \param offsets The offset of the first zone from the mesh origin in each logical dimension. + * \param strides The amount to stride when moving to the next element for each logical dimension. + */ + AXOM_HOST_DEVICE + StridedStructuredIndexing(const LogicalIndex &dims, + const LogicalIndex &offsets, + const LogicalIndex &strides) + : m_dimensions(dims) + , m_offsets(offsets) + , m_strides(strides) + { } + + /*! + * \brief Return the number of values in the index space. + * + * \return The number of values in the index space. + */ + AXOM_HOST_DEVICE + IndexType size() const + { + IndexType sz = 1; + for(int i = 0; i < NDIMS; i++) sz *= m_dimensions[i]; + return sz; + } + + /*! + * \brief Return the logical dimensions. + * + * \return The logical dimensions. + */ + AXOM_HOST_DEVICE + const LogicalIndex &logicalDimensions() const { return m_dimensions; } + + /*! + * \brief Return the j stride. + * + * \return The j stride to move up a row. + */ + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims >= 2, IndexType>::type jStride() const + { + return m_strides[1]; + } + + /*! + * \brief Return the k stride. + * + * \return The k stride to move forward a "page". + */ + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 3, IndexType>::type kStride() const + { + return m_strides[2]; + } + + /*! + * \brief Turn a global logical index into an index. + * \param global The global logical index to convert. + * \return The global index. + */ + AXOM_HOST_DEVICE + IndexType GlobalToGlobal(const LogicalIndex &global) const + { + IndexType gl {}; + for(int i = 0; i < NDIMS; i++) + { + gl += global[i] * m_strides[i]; + } + return gl; + } + + /*! + * \brief Turn a global index into a global logical index. + * + * \param global The index to convert. + * + * \return The local index that corresponds to the \a local. + */ + /// @{ + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 1, LogicalIndex>::type + GlobalToGlobal(IndexType global) const + { + LogicalIndex gl; + gl[0] = global; + return global; + } + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 2, LogicalIndex>::type + GlobalToGlobal(IndexType global) const + { + LogicalIndex gl; + gl[0] = global % m_strides[1]; + gl[1] = global / m_strides[1]; + return gl; + } + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 3, LogicalIndex>::type + GlobalToGlobal(IndexType global) const + { + LogicalIndex gl; + gl[0] = global % m_strides[1]; + gl[1] = (global % m_strides[2]) / m_strides[1]; + gl[2] = global / m_strides[2]; + return gl; + } + /// @} + + /*! + * \brief Convert global logical index to a local one. + * \param local The local logical index. + * \return local logical index. + */ + AXOM_HOST_DEVICE + LogicalIndex GlobalToLocal(const LogicalIndex &global) const + { + LogicalIndex local(global); + for(int i = 0; i < NDIMS; i++) + { + local[i] -= m_offsets[i]; + } + return local; + } + + /*! + * \brief Turn a global index into a local index. + * + * \param global The index to convert. + * + * \return The local index that corresponds to the \a local. + */ + AXOM_HOST_DEVICE + IndexType GlobalToLocal(IndexType global) const + { + return LogicalIndexToIndex(GlobalToLocal(GlobalToGlobal(global))); + } + + /*! + * \brief Convert local logical index to a global one. + * \param local The local logical index. + * \return global logical index. + */ + AXOM_HOST_DEVICE + LogicalIndex LocalToGlobal(const LogicalIndex &local) const + { + LogicalIndex global(local); + for(int i = 0; i < NDIMS; i++) + { + global[i] += m_offsets[i]; + } + return global; + } + + /*! + * \brief Convert local logical index to a global one. + * \param local The local logical index. + * \return local logical index. + */ + AXOM_HOST_DEVICE + IndexType LocalToGlobal(IndexType local) const + { + return GlobalToGlobal(LocalToGlobal(IndexToLogicalIndex(local))); + } + + /*! + * \brief Turn a local index into a local logical index. + * + * \param index The index to convert. + * + * \return The local logical index that corresponds to the \a index. + */ + /// @{ + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 1, LogicalIndex>::type + IndexToLogicalIndex(IndexType index) const + { + LogicalIndex logical; + logical[0] = index; + return logical; + } + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 2, LogicalIndex>::type + IndexToLogicalIndex(IndexType index) const + { + LogicalIndex logical; + const auto nx = m_dimensions[0]; + logical[0] = index % nx; + logical[1] = index / nx; + return logical; + } + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 3, LogicalIndex>::type + IndexToLogicalIndex(IndexType index) const + { + LogicalIndex logical; + const auto nx = m_dimensions[0]; + const auto nxy = nx * m_dimensions[1]; + logical[0] = index % nx; + logical[1] = (index % nxy) / nx; + logical[2] = index / nxy; + return logical; + } + /// @} + + /*! + * \brief Turn a local logical index into a local flat index. + * + * \param logical The logical indexto convert to a flat index. + * + * \return The index that corresponds to the \a logical index. + */ + AXOM_HOST_DEVICE + IndexType LogicalIndexToIndex(const LogicalIndex &logical) const + { + IndexType index {}; + IndexType stride {1}; + for(int i = 0; i < NDIMS; i++) + { + index += logical[i] * stride; + stride *= m_dimensions[i]; + } + return index; + } + + /*! + * \brief Determines whether the indexing contains the supplied logical index. + * + * \param logical The logical index being tested. + * + * \return True if the logical index is within the index, false otherwise. + */ + AXOM_HOST_DEVICE + bool contains(const LogicalIndex &logical) const + { + bool retval = true; + for(int i = 0; i < dimension(); i++) + { + retval &= (logical[i] >= 0 && logical[i] < m_dimensions[i]); + } + return retval; + } + + /*! + * \brief Determines whether the indexing contains the supplied index. + * + * \param index The index being tested. + * + * \return True if the index is within the index, false otherwise. + */ + AXOM_HOST_DEVICE + bool contains(const IndexType index) const + { + return contains(IndexToLogicalIndex(index)); + } + + /*! + * \brief Expand the current StridedStructuredIndexing by one in each dimension. + * + * \return An expanded StridedStructuredIndexing. + */ + /// @{ + template + AXOM_HOST_DEVICE + typename std::enable_if<_ndims == 1, StridedStructuredIndexing>::type + expand() const + { + StridedStructuredIndexing retval(*this); + retval.m_dimensions[0]++; + return retval; + } + + template + AXOM_HOST_DEVICE + typename std::enable_if<_ndims == 2, StridedStructuredIndexing>::type + expand() const + { + StridedStructuredIndexing retval(*this); + retval.m_dimensions[0]++; + retval.m_dimensions[1]++; + retval.m_strides[1]++; + return retval; + } + + template + AXOM_HOST_DEVICE + typename std::enable_if<_ndims == 3, StridedStructuredIndexing>::type + expand() const + { + StridedStructuredIndexing retval(*this); + retval.m_dimensions[0]++; + retval.m_dimensions[1]++; + retval.m_dimensions[2]++; + const auto nx = retval.m_strides[1]; + const auto ny = retval.m_strides[2] / nx; + retval.m_strides[1] = nx + 1; + retval.m_strides[2] = (ny + 1) * (nx + 1); + return retval; + } + + /// @} + + LogicalIndex m_dimensions {}; + LogicalIndex m_offsets {}; + LogicalIndex m_strides {}; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/StructuredIndexing.hpp b/src/axom/mir/views/StructuredIndexing.hpp new file mode 100644 index 0000000000..1050c9dd79 --- /dev/null +++ b/src/axom/mir/views/StructuredIndexing.hpp @@ -0,0 +1,291 @@ +// 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) + +#ifndef AXOM_MIR_STRUCTURED_INDEXING_HPP_ +#define AXOM_MIR_STRUCTURED_INDEXING_HPP_ + +#include "axom/core/StackArray.hpp" +#include "axom/core/ArrayView.hpp" +#include "axom/primal/geometry/Point.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief This class encapsulates a structured mesh size and contains methods to + * help with indexing into it. + * + * \tparam NDIMS The number of dimensions. + */ +template +class StructuredIndexing +{ +public: + using IndexType = IndexT; + using LogicalIndex = axom::StackArray; + + AXOM_HOST_DEVICE constexpr static int dimension() { return NDIMS; } + + /*! + * \brief Return whether the view supports strided structured indexing. + * \return false + */ + AXOM_HOST_DEVICE static constexpr bool supports_strided_structured_indexing() + { + return false; + } + + /*! + * \brief constructor + * + * \param dims The dimensions we're indexing. + */ + AXOM_HOST_DEVICE + StructuredIndexing() : m_dimensions() { } + + AXOM_HOST_DEVICE + StructuredIndexing(const LogicalIndex &dims) : m_dimensions(dims) { } + + /*! + * \brief Return the number of points in the index space. + * + * \return The number of points in the index space. + */ + AXOM_HOST_DEVICE + IndexType size() const + { + IndexType sz = 1; + for(int i = 0; i < NDIMS; i++) + { + sz *= m_dimensions[i]; + } + return sz; + } + + /*! + * \brief Return the logical dimensions. + * + * \return The logical dimensions. + */ + AXOM_HOST_DEVICE + const LogicalIndex &logicalDimensions() const { return m_dimensions; } + + /*! + * \brief Return the j stride. + * + * \return The j stride to move up a row. + */ + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims >= 2, IndexType>::type jStride() const + { + return m_dimensions[0]; + } + + /*! + * \brief Return the k stride. + * + * \return The k stride to move forward a "page". + */ + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 3, IndexType>::type kStride() const + { + return m_dimensions[0] * m_dimensions[1]; + } + + /*! + * \brief Turn a global logical index into an index. + * \param global The global logical index to convert. + * \return The global index. + */ + AXOM_HOST_DEVICE + inline IndexType GlobalToGlobal(const LogicalIndex &global) const + { + return LogicalIndexToIndex(global); + } + + /*! + * \brief Turn a global index into a global logical index. + * \param global The global index to convert. + * \return The global logical index. + */ + AXOM_HOST_DEVICE + inline LogicalIndex GlobalToGlobal(IndexType global) const + { + return IndexToLogicalIndex(global); + } + + /*! + * \brief Turn global logical index to local logical index. no-op. + * \param index The index to convert. + * \return Same as the input in this case. + */ + AXOM_HOST_DEVICE + inline LogicalIndex GlobalToLocal(const LogicalIndex &index) const + { + return index; + } + + /*! + * \brief Turn global index to local index. no-op. + * \param index The index to convert. + * \return Same as the input in this case. + */ + AXOM_HOST_DEVICE + inline IndexType GlobalToLocal(IndexType index) const { return index; } + + /*! + * \brief Turn local logical index to global logical index. no-op. + * \param index The index to convert. + * \return Same as the input in this case. + */ + AXOM_HOST_DEVICE + inline LogicalIndex LocalToGlobal(const LogicalIndex &index) const + { + return index; + } + + /*! + * \brief Turn local index to global index. no-op. + * \param index The index to convert. + * \return Same as the input in this case. + */ + AXOM_HOST_DEVICE + inline IndexType LocalToGlobal(IndexType index) const { return index; } + + /*! + * \brief Turn an index into a logical index. + * + * \param index The index to convert. + * + * \return The logical index that corresponds to the \a index. + */ + /// @{ + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 1, LogicalIndex>::type + IndexToLogicalIndex(IndexType index) const + { + LogicalIndex logical; + logical[0] = index; + return logical; + } + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 2, LogicalIndex>::type + IndexToLogicalIndex(IndexType index) const + { + LogicalIndex logical; + const auto nx = m_dimensions[0]; + logical[0] = index % nx; + logical[1] = index / nx; + return logical; + } + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 3, LogicalIndex>::type + IndexToLogicalIndex(IndexType index) const + { + LogicalIndex logical; + const auto nx = m_dimensions[0]; + const auto nxy = nx * m_dimensions[1]; + logical[0] = index % nx; + logical[1] = (index % nxy) / nx; + logical[2] = index / nxy; + return logical; + } + + /// @} + + /*! + * \brief Turn a logical index into a flat index. + * + * \param logical The logical indexto convert to a flat index. + * + * \return The index that corresponds to the \a logical index. + */ + /// @{ + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 1, IndexType>::type + LogicalIndexToIndex(const LogicalIndex &logical) const + { + return logical[0]; + } + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 2, IndexType>::type + LogicalIndexToIndex(const LogicalIndex &logical) const + { + return logical[1] * m_dimensions[0] + logical[0]; + } + + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 3, IndexType>::type + LogicalIndexToIndex(const LogicalIndex &logical) const + { + return (logical[2] * m_dimensions[1] * m_dimensions[0]) + + (logical[1] * m_dimensions[0]) + logical[0]; + } + + /// @} + + /*! + * \brief Determines whether the indexing contains the supplied logical index. + * + * \param logical The logical index being tested. + * + * \return True if the logical index is within the index, false otherwise. + */ + AXOM_HOST_DEVICE + bool contains(const LogicalIndex &logical) const + { + bool retval = true; + for(int i = 0; i < dimension(); i++) + { + retval &= (logical[i] >= 0 && logical[i] < m_dimensions[i]); + } + return retval; + } + + /*! + * \brief Determines whether the indexing contains the supplied index. + * + * \param index The index being tested. + * + * \return True if the index is within the index, false otherwise. + */ + AXOM_HOST_DEVICE + bool contains(const IndexType index) const + { + return contains(IndexToLogicalIndex(index)); + } + + /*! + * \brief Expand the current StructuredIndexing by one in each dimension. + * + * \return An expanded StructuredIndexing. + */ + AXOM_HOST_DEVICE + StructuredIndexing expand() const + { + StructuredIndexing retval(*this); + for(int i = 0; i < dimension(); i++) + { + retval.m_dimensions[i]++; + } + return retval; + } + +private: + LogicalIndex m_dimensions {}; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/StructuredTopologyView.hpp b/src/axom/mir/views/StructuredTopologyView.hpp new file mode 100644 index 0000000000..2cd6e4c2b9 --- /dev/null +++ b/src/axom/mir/views/StructuredTopologyView.hpp @@ -0,0 +1,237 @@ +// 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) + +#ifndef AXOM_MIR_VIEWS_STRUCTURED_TOPOLOGY_VIEW_HPP_ +#define AXOM_MIR_VIEWS_STRUCTURED_TOPOLOGY_VIEW_HPP_ + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/mir/views/Shapes.hpp" + +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief This class provides a view for Conduit/Blueprint structured grid types. + * + * \tparam IndexPolicy The policy for making/using indices. + */ +template +class StructuredTopologyView +{ +public: + using IndexingPolicy = IndexPolicy; + using IndexType = typename IndexingPolicy::IndexType; + using LogicalIndex = typename IndexingPolicy::LogicalIndex; + using ConnectivityType = IndexType; + using Shape1D = LineShape>; + using Shape2D = QuadShape>; + using Shape3D = HexShape>; + using ShapeType = typename std::conditional< + IndexingPolicy::dimension() == 3, + Shape3D, + typename std::conditional::type>::type; + + /*! + * \brief Return the number of dimensions. + * + * \return The number of dimensions. + */ + AXOM_HOST_DEVICE constexpr static int dimension() + { + return IndexingPolicy::dimension(); + } + + /*! + * \brief Constructor + */ + AXOM_HOST_DEVICE StructuredTopologyView() : m_zoneIndexing(), m_nodeIndexing() + { } + + /*! + * \brief Constructor + * + * \param indexing The indexing policy for the topology (num zones in each dimension). + */ + AXOM_HOST_DEVICE StructuredTopologyView(const IndexingPolicy &indexing) + : m_zoneIndexing(indexing) + , m_nodeIndexing(indexing.expand()) + { } + + /*! + * \brief Return the number of zones. + * + * \return The number of zones. + */ + AXOM_HOST_DEVICE IndexType size() const { return m_zoneIndexing.size(); } + + /*! + * \brief Return the number of zones. + * + * \return The number of zones. + */ + AXOM_HOST_DEVICE IndexType numberOfZones() const { return size(); } + + /*! + * \brief Return the size of the connectivity. + * + * \return The size of the connectivity. + */ + AXOM_HOST_DEVICE IndexType connectivitySize() const + { + IndexType nodesPerElem = 1; + for(int d = 0; d < dimension(); d++) + { + nodesPerElem *= 2; + } + return numberOfZones() * nodesPerElem; + } + + /*! + * \brief Return the mesh logical dimensions. + * + * \return The mesh logical dimensions. + */ + AXOM_HOST_DEVICE const LogicalIndex &logicalDimensions() const + { + return m_zoneIndexing.logicalDimensions(); + } + + /*! + * \brief Return indexing object. + * + * \return The indexing object. + */ + AXOM_HOST_DEVICE IndexingPolicy &indexing() { return m_zoneIndexing; } + + /*! + * \brief Return indexing object. + * + * \return The indexing object. + */ + AXOM_HOST_DEVICE const IndexingPolicy &indexing() const + { + return m_zoneIndexing; + } + + /*! + * \brief Return a zone. + * + * \param zoneIndex The index of the zone to return. + * + * \return The requested zone. + * + * \note 3D implementation. + */ + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 3, Shape3D>::type zone( + axom::IndexType zoneIndex) const + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(zoneIndex < numberOfZones()); + #else + SLIC_ASSERT(zoneIndex < numberOfZones()); + #endif +#endif + const auto localLogical = m_zoneIndexing.IndexToLogicalIndex(zoneIndex); + const auto jp = m_nodeIndexing.jStride(); + const auto kp = m_nodeIndexing.kStride(); + + Shape3D shape; + auto &data = shape.getIdsStorage(); + data[0] = + m_nodeIndexing.GlobalToGlobal(m_nodeIndexing.LocalToGlobal(localLogical)); + data[1] = data[0] + 1; + data[2] = data[1] + jp; + data[3] = data[2] - 1; + data[4] = data[0] + kp; + data[5] = data[1] + kp; + data[6] = data[2] + kp; + data[7] = data[3] + kp; + + return shape; + } + + /*! + * \brief Return a zone. + * + * \param zoneIndex The index of the zone to return. + * + * \return The requested zone. + * + * \note 2D implementation. + */ + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 2, Shape2D>::type zone( + axom::IndexType zoneIndex) const + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(zoneIndex < numberOfZones()); + #else + SLIC_ASSERT(zoneIndex < numberOfZones()); + #endif +#endif + const auto localLogical = m_zoneIndexing.IndexToLogicalIndex(zoneIndex); + const auto jp = m_nodeIndexing.jStride(); + + Shape2D shape; + auto &data = shape.getIdsStorage(); + data[0] = + m_nodeIndexing.GlobalToGlobal(m_nodeIndexing.LocalToGlobal(localLogical)); + data[1] = data[0] + 1; + data[2] = data[1] + jp; + data[3] = data[2] - 1; + + return shape; + } + + /*! + * \brief Return a zone. + * + * \param index The index of the zone to return. + * + * \return The requested zone. + * + * \note 1D implementation. + */ + template + AXOM_HOST_DEVICE typename std::enable_if<_ndims == 1, Shape1D>::type zone( + axom::IndexType zoneIndex) const + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(zoneIndex < numberOfZones()); + #else + SLIC_ASSERT(zoneIndex < numberOfZones()); + #endif +#endif + const auto localLogical = m_zoneIndexing.IndexToLogicalIndex(zoneIndex); + + Shape1D shape; + auto &data = shape.getIdsStorage(); + data[0] = + m_nodeIndexing.GlobalToGlobal(m_nodeIndexing.LocalToGlobal(localLogical)); + data[1] = data[0] + 1; + + return shape; + } + +private: + IndexingPolicy m_zoneIndexing; + IndexingPolicy m_nodeIndexing; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/UniformCoordsetView.hpp b/src/axom/mir/views/UniformCoordsetView.hpp new file mode 100644 index 0000000000..56fb829faf --- /dev/null +++ b/src/axom/mir/views/UniformCoordsetView.hpp @@ -0,0 +1,120 @@ +// 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) + +#ifndef AXOM_MIR_UNIFORM_COORDSET_VIEW_HPP_ +#define AXOM_MIR_UNIFORM_COORDSET_VIEW_HPP_ + +#include "axom/core/StackArray.hpp" +#include "axom/core/ArrayView.hpp" +#include "axom/primal/geometry/Point.hpp" +#include "axom/mir/views/StructuredIndexing.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \class This class provides a view for Conduit/Blueprint uniform coordsets. + * + * \tparam DataType The underlying type used for the coordinates. + * \tparam NDIMS The number of dimensions in each point. + * + */ +template +class UniformCoordsetView +{ +public: + using LogicalIndex = axom::StackArray; + using ExtentsType = axom::StackArray; + + using IndexType = axom::IndexType; + using value_type = DataType; + using PointType = axom::primal::Point; + + constexpr static int dimension() { return NDIMS; } + + /*! + * \brief Constructor + * + * \param dims The logical dimensions of the coordset. + * \param origin The origin of the coordset's coordinate system. + * \param spacing The spacing inbetween points. + */ + AXOM_HOST_DEVICE + UniformCoordsetView(const LogicalIndex &dims, + const ExtentsType &origin, + const ExtentsType &spacing) + : m_indexing(dims) + , m_origin(origin) + , m_spacing(spacing) + { } + + /*! + * \brief Return the number of points in the coordset. + * + * \return The number of points in the coordset. + */ + /// @{ + AXOM_HOST_DEVICE + IndexType size() const { return m_indexing.size(); } + + AXOM_HOST_DEVICE + IndexType numberOfNodes() const { return m_indexing.size(); } + /// @} + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The logical index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType getPoint(const LogicalIndex &vertex_index) const + { + PointType pt; + for(int i = 0; i < NDIMS; i++) + pt[i] = m_origin[i] + vertex_index[i] * m_spacing[i]; + return pt; + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The logical index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType operator[](const LogicalIndex &vertex_index) const + { + return getPoint(vertex_index); + } + + /*! + * \brief Return the requested point from the coordset. + * + * \param vertex_index The index of the point to return. + * + * \return A point that corresponds to \a vertex_index. + */ + AXOM_HOST_DEVICE + PointType operator[](IndexType vertex_index) const + { + return getPoint(m_indexing.IndexToLogicalIndex(vertex_index)); + } + + StructuredIndexing m_indexing; + ExtentsType m_origin; + ExtentsType m_spacing; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/UnstructuredTopologyMixedShapeView.cpp b/src/axom/mir/views/UnstructuredTopologyMixedShapeView.cpp new file mode 100644 index 0000000000..e2f256928d --- /dev/null +++ b/src/axom/mir/views/UnstructuredTopologyMixedShapeView.cpp @@ -0,0 +1,55 @@ +// 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/mir/views/UnstructuredTopologyMixedShapeView.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +ShapeMap buildShapeMap(const conduit::Node &n_topo, + axom::Array &values, + axom::Array &ids, + int allocatorID) +{ + // Make the map from the Conduit shape_map. Use std::map to sort the key values. + // The shape_map nodes should be in host memory since the int values can fit + // in a Conduit::Node. + std::map sm; + const conduit::Node &n_shape_map = + n_topo.fetch_existing("elements/shape_map"); + for(conduit::index_t i = 0; i < n_shape_map.number_of_children(); i++) + { + const auto value = static_cast(n_shape_map[i].to_int()); + sm[value] = axom::mir::views::shapeNameToID(n_shape_map[i].name()); + } + + // Store the map in 2 vectors so data are contiguous. + std::vector valuesvec, idsvec; + valuesvec.reserve(sm.size()); + idsvec.reserve(sm.size()); + for(auto it = sm.begin(); it != sm.end(); it++) + { + valuesvec.push_back(it->first); + idsvec.push_back(it->second); + } + + // Copy the map values to the device memory. + const axom::IndexType n = static_cast(sm.size()); + values = + axom::Array(axom::ArrayOptions::Uninitialized(), n, n, allocatorID); + ids = + axom::Array(axom::ArrayOptions::Uninitialized(), n, n, allocatorID); + axom::copy(values.data(), valuesvec.data(), n * sizeof(IndexType)); + axom::copy(ids.data(), idsvec.data(), n * sizeof(IndexType)); + + return ShapeMap(values.view(), ids.view()); +} + +} // end namespace views +} // end namespace mir +} // end namespace axom diff --git a/src/axom/mir/views/UnstructuredTopologyMixedShapeView.hpp b/src/axom/mir/views/UnstructuredTopologyMixedShapeView.hpp new file mode 100644 index 0000000000..29be91d0a8 --- /dev/null +++ b/src/axom/mir/views/UnstructuredTopologyMixedShapeView.hpp @@ -0,0 +1,207 @@ +// 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) + +#ifndef AXOM_MIR_VIEWS_UNSTRUCTURED_TOPOLOGY_MIXED_SHAPE_VIEW_HPP_ +#define AXOM_MIR_VIEWS_UNSTRUCTURED_TOPOLOGY_MIXED_SHAPE_VIEW_HPP_ + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/mir/views/Shapes.hpp" +#include "axom/mir/utilities/utilities.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief Given a shape value, we can get the Shape::id() that is used internally. + * + * \note If the view was to renumber the shapes array to use the Shape::id() values + * then operator[] could return its input value and skip bsearch. + */ +class ShapeMap +{ +public: + /*! + * \brief Constructor + */ + AXOM_HOST_DEVICE ShapeMap() : m_shape_values(), m_shape_ids() { } + + /*! + * \brief Constructor + * + * \param shape_values A view of sorted values used in the Conduit data. + * \param shape_ids A view of shape ids that correspond to the values from Conduit. + */ + AXOM_HOST_DEVICE ShapeMap(const axom::ArrayView &shape_values, + const axom::ArrayView &shape_ids) + : m_shape_values(shape_values) + , m_shape_ids(shape_ids) + { } + + /*! + * \brief Return the size of the shape map. + * \return The number of entries in the shape map. + */ + AXOM_HOST_DEVICE IndexType size() const { return m_shape_values.size(); } + + /*! + * \brief Return whether the shape map is empty. + * \return True if the map is empty; False otherwise. + */ + AXOM_HOST_DEVICE bool empty() const { return m_shape_values.empty(); } + + /*! + * \brief Given a shape value (as in the Conduit shapes array), return the shape id. + * + * \param value A value from the shapes array that we want to map to a shape id. + * + * \return A shape id. + */ + AXOM_HOST_DEVICE IndexType operator[](IndexType value) const + { + const auto index = axom::mir::utilities::bsearch(value, m_shape_values); + return (index >= 0) ? m_shape_ids[index] : 0; + } + +private: + axom::ArrayView m_shape_values; + axom::ArrayView m_shape_ids; +}; + +/*! + * \brief Populate the shape map values/ids arrays using data in the topology's shape_map. + * + * \param n_topo The topology that contains the shape map. + * \param[out] values The sorted values used for shapes in the topology. + * \param[out] ids The Shape ids that correspond to the shape values. + * \param allocatorID The allocator to use when creating the arrays. + */ +ShapeMap buildShapeMap(const conduit::Node &n_topo, + axom::Array &values, + axom::Array &ids, + int allocatorID); +/*! + * \brief This class provides a view for Conduit/Blueprint mixed shape unstructured grids. + * + * \tparam IndexT The index type that will be used for connectivity, etc. + * \tparam ShapeT The shape type. + * + * \note This view does not support topologies that also contain polyhedral elements. + */ +template +class UnstructuredTopologyMixedShapeView +{ +public: + using ConnectivityType = ConnT; + using ConnectivityView = axom::ArrayView; + using ShapeType = VariableShape; + + /*! + * \brief Constructor + * + * \param conn The mesh connectivity. + * \param shapes The shape in each zone. + * \param sizes The number of nodes in each zone. + * \param offsets The offset to each zone in the connectivity. + */ + AXOM_HOST_DEVICE + UnstructuredTopologyMixedShapeView(const ConnectivityView &conn, + const ConnectivityView &shapes, + const ConnectivityView &sizes, + const ConnectivityView &offsets, + const ShapeMap &shapemap) + : m_connectivity(conn) + , m_shapes(shapes) + , m_sizes(sizes) + , m_offsets(offsets) + , m_shapeMap(shapemap) + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(m_shapes.size() != 0); + assert(m_sizes.size() != 0); + assert(m_offsets.size() != 0); + assert(m_offsets.size() == m_sizes.size() && + m_offsets.size() == m_shapes.size()); + #else + SLIC_ASSERT(m_shapes.size() != 0); + SLIC_ASSERT(m_sizes.size() != 0); + SLIC_ASSERT(m_offsets.size() != 0); + SLIC_ASSERT(m_offsets.size() == m_sizes.size() && + m_offsets.size() == m_shapes.size()); + #endif +#endif + } + + /*! + * \brief Return the dimension of the shape. + * + * \return -1 for unknown dimension. We'd have to look at the shapes. + */ + AXOM_HOST_DEVICE static constexpr int dimension() { return -1; } + + /*! + * \brief Return the number of zones. + * + * \return The number of zones. + */ + AXOM_HOST_DEVICE IndexType numberOfZones() const { return m_sizes.size(); } + + /*! + * \brief Return the size of the connectivity. + * + * \return The size of the connectivity. + */ + AXOM_HOST_DEVICE IndexType connectivitySize() const + { + return m_connectivity.size(); + } + + /*! + * \brief Return a zone. + * + * \param zoneIndex The index of the zone to return. + * + * \return The requested zone. + */ + AXOM_HOST_DEVICE ShapeType zone(axom::IndexType zoneIndex) const + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(zoneIndex < numberOfZones()); + #else + SLIC_ASSERT(zoneIndex < numberOfZones()); + #endif +#endif + const ConnectivityView shapeData(m_connectivity.data() + m_offsets[zoneIndex], + m_sizes[zoneIndex]); + const auto shapeID = m_shapeMap[m_shapes[zoneIndex]]; +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(shapeID >= Point_ShapeID && shapeID <= Mixed_ShapeID); + #else + SLIC_ASSERT(shapeID >= Point_ShapeID && shapeID <= Mixed_ShapeID); + #endif +#endif + + return ShapeType(shapeID, shapeData); + } + +private: + ConnectivityView m_connectivity; + ConnectivityView m_shapes; + ConnectivityView m_sizes; + ConnectivityView m_offsets; + ShapeMap m_shapeMap; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/UnstructuredTopologyPolyhedralView.hpp b/src/axom/mir/views/UnstructuredTopologyPolyhedralView.hpp new file mode 100644 index 0000000000..647b177250 --- /dev/null +++ b/src/axom/mir/views/UnstructuredTopologyPolyhedralView.hpp @@ -0,0 +1,259 @@ +// 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) + +#ifndef AXOM_MIR_VIEWS_UNSTRUCTURED_TOPOLOGY_POLYHEDRAL_VIEW_HPP_ +#define AXOM_MIR_VIEWS_UNSTRUCTURED_TOPOLOGY_POLYHEDRAL_VIEW_HPP_ + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/mir/views/Shapes.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief This class implements a view for Blueprint polyhedral topologies. + */ +template +class UnstructuredTopologyPolyhedralView +{ +public: + using ConnectivityType = ConnType; + using ConnectivityView = axom::ArrayView; + + /*! + * \brief This struct contains views that hold polyhedral connectivity. + */ + struct PolyhedronData + { + /// Constructor + AXOM_HOST_DEVICE + PolyhedronData(const ConnectivityView &subelement_conn, + const ConnectivityView &subelement_sizes, + const ConnectivityView &subelement_offsets, + const ConnectivityView &element_conn, + const ConnectivityView &element_sizes, + const ConnectivityView &element_offsets) + : m_subelement_conn(subelement_conn) + , m_subelement_sizes(subelement_sizes) + , m_subelement_offsets(subelement_offsets) + , m_element_conn(element_conn) + , m_element_sizes(element_sizes) + , m_element_offsets(element_offsets) + { } + + /// Constructor + AXOM_HOST_DEVICE + PolyhedronData(const PolyhedronData &obj) + : m_subelement_conn(obj.m_subelement_conn) + , m_subelement_sizes(obj.m_subelement_sizes) + , m_subelement_offsets(obj.m_subelement_offsets) + , m_element_conn(obj.m_element_conn) + , m_element_sizes(obj.m_element_sizes) + , m_element_offsets(obj.m_element_offsets) + { } + + ConnectivityView m_subelement_conn; + ConnectivityView m_subelement_sizes; + ConnectivityView m_subelement_offsets; + ConnectivityView m_element_conn; + ConnectivityView m_element_sizes; + ConnectivityView m_element_offsets; + }; + + /*! + * \brief This struct provides data about Zone i's shape. + */ + struct PolyhedronShape + { + constexpr static IndexType MaximumNumberOfIds = 20 * 3; + + AXOM_HOST_DEVICE constexpr static bool is_polyhedral() { return true; } + AXOM_HOST_DEVICE constexpr static int id() { return Polyhedron_ShapeID; } + + /// Constructor. + AXOM_HOST_DEVICE PolyhedronShape(const PolyhedronData &obj, axom::IndexType zi) + : m_data(obj) + , m_zoneIndex(zi) + , m_ids() + { } + + /// This implementation does not return unique number of nodes. + AXOM_HOST_DEVICE IndexType numberOfNodes() const + { + axom::IndexType nnodes = 0; + const auto nFaces = numberOfFaces(); + for(axom::IndexType f = 0; f < nFaces; f++) + { + nnodes += getFace(f).size(); + } + return nnodes; + } + + AXOM_HOST_DEVICE IndexType numberOfFaces() const + { + return m_data.m_element_sizes[m_zoneIndex]; + } + + AXOM_HOST_DEVICE IndexType numberOfNodesInFace(int faceIndex) const + { + return getFace(faceIndex).size(); + } + + AXOM_HOST_DEVICE ConnectivityView getIds() const + { + axom::IndexType nnodes = 0; + const auto nFaces = numberOfFaces(); + for(axom::IndexType f = 0; f < nFaces; f++) + { + const auto faceIds = getFace(f); + for(axom::IndexType i = 0; i < faceIds.size(); i++) + { + if(nnodes + 1 <= MaximumNumberOfIds) + m_ids[nnodes++] = faceIds[i]; + else + { +#if !defined(AXOM_DEVICE_CODE) + SLIC_ERROR("m_ids is not large enough to hold all node ids."); +#endif + break; + } + } + } + return ConnectivityView(m_ids.m_data, nnodes); + } + + AXOM_HOST_DEVICE ConnectivityView getUniqueIds() const + { + axom::IndexType nnodes = 0; + const auto nFaces = numberOfFaces(); + for(axom::IndexType f = 0; f < nFaces; f++) + { + const auto faceIds = getFace(f); + for(axom::IndexType i = 0; i < faceIds.size(); i++) + { + if(!find(m_ids.m_data, nnodes, faceIds[i])) + { + if(nnodes + 1 <= MaximumNumberOfIds) + m_ids[nnodes++] = faceIds[i]; + else + { +#if !defined(AXOM_DEVICE_CODE) + SLIC_ERROR("m_ids is not large enough to hold all node ids."); +#endif + break; + } + } + } + } + return ConnectivityView(m_ids.m_data, nnodes); + } + + AXOM_HOST_DEVICE ConnectivityView getFace(int faceIndex) const + { + const ConnectivityView element_face_ids( + m_data.m_element_conn.data() + m_data.m_element_offsets[m_zoneIndex], + m_data.m_element_sizes[m_zoneIndex]); + const auto faceId = element_face_ids[faceIndex]; + + return ConnectivityView( + m_data.m_subelement_conn.data() + m_data.m_subelement_offsets[faceId], + m_data.m_subelement_sizes[faceId]); + } + + private: + AXOM_HOST_DEVICE bool find(const ConnectivityType *arr, + axom::IndexType n, + ConnectivityType value) const + { + bool found = false; + for(axom::IndexType i = 0; i < n && !found; i++) + { + found = arr[i] == value; + } + return found; + } + + PolyhedronData m_data; + IndexType m_zoneIndex {0}; + mutable axom::StackArray m_ids; + }; + //---------------------------------------------------------------------------- + + using ShapeType = PolyhedronShape; + + /*! + * \brief Constructor. + */ + AXOM_HOST_DEVICE + UnstructuredTopologyPolyhedralView(const ConnectivityView &subelement_conn, + const ConnectivityView &subelement_sizes, + const ConnectivityView &subelement_offsets, + const ConnectivityView &element_conn, + const ConnectivityView &element_sizes, + const ConnectivityView &element_offsets) + : m_data(subelement_conn, + subelement_sizes, + subelement_offsets, + element_conn, + element_sizes, + element_offsets) + { } + + /*! + * \brief Return the number of zones in the mesh. + * + * \return The number of zones. + */ + AXOM_HOST_DEVICE + IndexType numberOfZones() const { return m_data.m_element_sizes.size(); } + + /*! + * \brief Return the size of the connectivity. + * + * \return The size of the connectivity. + */ + AXOM_HOST_DEVICE + IndexType connectivitySize() const { return m_data.element_conn.size(); } + + /*! + * \brief Return the dimension of the shape. + * + * \return The dimension of the shape. + */ + AXOM_HOST_DEVICE static constexpr int dimension() { return 3; } + + /*! + * \brief Return a zone. + * + * \param zoneIndex The requested zone. + * + * \return The requested zone. + */ + AXOM_HOST_DEVICE ShapeType zone(axom::IndexType zoneIndex) const + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(zoneIndex < numberOfZones()); + #else + SLIC_ASSERT(zoneIndex < numberOfZones()); + #endif +#endif + + return ShapeType(m_data, zoneIndex); + } + +private: + PolyhedronData m_data; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/UnstructuredTopologySingleShapeView.hpp b/src/axom/mir/views/UnstructuredTopologySingleShapeView.hpp new file mode 100644 index 0000000000..9bf9db4bce --- /dev/null +++ b/src/axom/mir/views/UnstructuredTopologySingleShapeView.hpp @@ -0,0 +1,165 @@ +// 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) + +#ifndef AXOM_MIR_VIEWS_UNSTRUCTURED_TOPOLOGY_SINGLE_SHAPE_VIEW_HPP_ +#define AXOM_MIR_VIEWS_UNSTRUCTURED_TOPOLOGY_SINGLE_SHAPE_VIEW_HPP_ + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/mir/views/Shapes.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief This class provides a view for Conduit/Blueprint single shape unstructured grids. + * + * \tparam IndexT The index type that will be used for connectivity, etc. + * \tparam ShapeT The shape type. + */ +template +class UnstructuredTopologySingleShapeView +{ +public: + using ShapeType = ShapeT; + using ConnectivityType = typename ShapeType::ConnectivityType; + using ConnectivityView = axom::ArrayView; + + /*! + * \brief Constructor + * + * \param conn The mesh connectivity. + */ + AXOM_HOST_DEVICE + UnstructuredTopologySingleShapeView(const ConnectivityView &conn) + : m_connectivityView(conn) + , m_sizesView() + , m_offsetsView() + { } + + /*! + * \brief Constructor + * + * \param conn The mesh connectivity. + * \param sizes The number of nodes in each zone. + * \param offsets The offset to each zone in the connectivity. + */ + AXOM_HOST_DEVICE + UnstructuredTopologySingleShapeView(const ConnectivityView &conn, + const ConnectivityView &sizes, + const ConnectivityView &offsets) + : m_connectivityView(conn) + , m_sizesView(sizes) + , m_offsetsView(offsets) + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(m_offsetsView.size() == m_sizesView.size()); + #else + SLIC_ASSERT(m_offsetsView.size() == m_sizesView.size()); + #endif +#endif + } + + /*! + * \brief Return the dimension of the shape. + * + * \return The dimension of the shape. + */ + AXOM_HOST_DEVICE static constexpr int dimension() + { + return ShapeT::dimension(); + } + + /*! + * \brief Return the number of zones. + * + * \return The number of zones. + */ + AXOM_HOST_DEVICE IndexType numberOfZones() const + { + return (m_sizesView.size() != 0) + ? m_sizesView.size() + : (m_connectivityView.size() / ShapeType::numberOfNodes()); + } + + /*! + * \brief Return the size of the connectivity. + * + * \return The size of the connectivity. + */ + AXOM_HOST_DEVICE IndexType connectivitySize() const + { + return m_connectivityView.size(); + } + + /*! + * \brief Return a zone. + * + * \param zoneIndex The requested zone. + * + * \return The requested zone. + */ + /// @{ + template + AXOM_HOST_DEVICE typename std::enable_if<_variable_size, ShapeType>::type zone( + axom::IndexType zoneIndex) const + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(zoneIndex < numberOfZones()); + #else + SLIC_ASSERT(zoneIndex < numberOfZones()); + #endif +#endif + + return ShapeType( + ConnectivityView(m_connectivityView.data() + m_offsetsView[zoneIndex], + m_sizesView[zoneIndex])); + } + + template + AXOM_HOST_DEVICE typename std::enable_if::type zone( + axom::IndexType zoneIndex) const + { +#if defined(AXOM_DEBUG) + #if defined(AXOM_DEVICE_CODE) + assert(zoneIndex < numberOfZones()); + #else + SLIC_ASSERT(zoneIndex < numberOfZones()); + #endif +#endif + + ConnectivityView shapeIdsView {}; + if(m_sizesView.empty()) + { + shapeIdsView = ConnectivityView( + m_connectivityView.data() + ShapeType::zoneOffset(zoneIndex), + ShapeType::numberOfNodes()); + } + else + { + shapeIdsView = + ConnectivityView(m_connectivityView.data() + m_offsetsView[zoneIndex], + m_sizesView[zoneIndex]); + } + return ShapeType(shapeIdsView); + } + /// @} + +private: + ConnectivityView m_connectivityView; + ConnectivityView m_sizesView; + ConnectivityView m_offsetsView; +}; + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/dispatch_coordset.hpp b/src/axom/mir/views/dispatch_coordset.hpp new file mode 100644 index 0000000000..b5bca61007 --- /dev/null +++ b/src/axom/mir/views/dispatch_coordset.hpp @@ -0,0 +1,338 @@ +// 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) + +#ifndef AXOM_MIR_DISPATCH_COORDSET_HPP_ +#define AXOM_MIR_DISPATCH_COORDSET_HPP_ + +#include "axom/mir/views/ExplicitCoordsetView.hpp" +#include "axom/mir/views/UniformCoordsetView.hpp" +#include "axom/mir/views/RectilinearCoordsetView.hpp" +#include "axom/mir/views/NodeArrayView.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief Base template for creating a rectilinear coordset view. + */ +template +struct make_rectilinear_coordset +{ }; + +/*! + * \brief Partial specialization for creating 3D rectilinear coordset view. + */ +template +struct make_rectilinear_coordset +{ + using CoordsetView = axom::mir::views::RectilinearCoordsetView3; + + /*! + * \brief Create the coordset view and initialize it from the coordset. + * \param topo The node containing the coordset. + * \return The coordset view. + */ + static CoordsetView view(const conduit::Node &coordset) + { + namespace bputils = axom::mir::utilities::blueprint; + const conduit::Node &values = coordset.fetch_existing("values"); + auto xView = bputils::make_array_view(values[0]); + auto yView = bputils::make_array_view(values[1]); + auto zView = bputils::make_array_view(values[2]); + return CoordsetView(xView, yView, zView); + } +}; + +/*! + * \brief Partial specialization for creating 2D rectilinear coordset view. + */ +template +struct make_rectilinear_coordset +{ + using CoordsetView = axom::mir::views::RectilinearCoordsetView2; + + /*! + * \brief Create the coordset view and initialize it from the coordset. + * \param topo The node containing the coordset. + * \return The coordset view. + */ + static CoordsetView view(const conduit::Node &coordset) + { + namespace bputils = axom::mir::utilities::blueprint; + const conduit::Node &values = coordset.fetch_existing("values"); + auto xView = bputils::make_array_view(values[0]); + auto yView = bputils::make_array_view(values[1]); + return CoordsetView(xView, yView); + } +}; + +/*! + * \brief Base template for creating a rectilinear coordset view. + */ +template +struct make_uniform_coordset +{ }; + +/*! + * \brief Partial specialization for creating 3D uniform coordset view. + */ +template <> +struct make_uniform_coordset<3> +{ + using CoordsetView = axom::mir::views::UniformCoordsetView; + + /*! + * \brief Create the coordset view and initialize it from the coordset. + * \param topo The node containing the coordset. + * \return The coordset view. + */ + static CoordsetView view(const conduit::Node &coordset) + { + const std::string keys[] = {"i", "j", "k"}; + const conduit::Node &n_dims = coordset["dims"]; + axom::StackArray dims; + axom::StackArray origin {0., 0., 0.}, spacing {1., 1., 1.}; + for(int i = 0; i < 3; i++) + { + dims[i] = n_dims.fetch_existing(keys[i]).to_int(); + if(coordset.has_child("origin")) + origin[i] = coordset["origin"][i].to_double(); + if(coordset.has_child("spacing")) + spacing[i] = coordset["spacing"][i].to_double(); + } + return CoordsetView(dims, origin, spacing); + } +}; + +/*! + * \brief Partial specialization for creating 2D rectilinear coordset view. + */ +template <> +struct make_uniform_coordset<2> +{ + using CoordsetView = axom::mir::views::UniformCoordsetView; + + /*! + * \brief Create the coordset view and initialize it from the coordset. + * \param topo The node containing the coordset. + * \return The coordset view. + */ + static CoordsetView view(const conduit::Node &coordset) + { + const std::string keys[] = {"i", "j"}; + const conduit::Node &n_dims = coordset["dims"]; + axom::StackArray dims; + axom::StackArray origin {0., 0.}, spacing {1., 1.}; + for(int i = 0; i < 2; i++) + { + dims[i] = n_dims.fetch_existing(keys[i]).to_int(); + if(coordset.has_child("origin")) + origin[i] = coordset["origin"][i].to_double(); + if(coordset.has_child("spacing")) + spacing[i] = coordset["spacing"][i].to_double(); + } + return CoordsetView(dims, origin, spacing); + } +}; + +/*! + * \brief Dispatch an uniform coordset to a function. + * + * \tparam FuncType The type of the function / lambda to invoke. It is expected + * that the callable accepts an auto argument for a coordset view. + * + * \param coordset The Conduit node that contains the coordset. + * \param func The function/lambda to invoke using the coordset view. + */ +template +void dispatch_uniform_coordset(const conduit::Node &coordset, FuncType &&func) +{ + const conduit::Node &n_dims = coordset["dims"]; + const conduit::index_t ndims = n_dims.number_of_children(); + if(ndims == 2) + { + auto coordsetView = make_uniform_coordset<2>::view(coordset); + func(coordsetView); + } + else if(ndims == 3) + { + auto coordsetView = make_uniform_coordset<3>::view(coordset); + func(coordsetView); + } +} + +/*! + * \brief Dispatch a rectilinear coordset to a function. + * + * \tparam FuncType The type of the function / lambda to invoke. It is expected + * that the callable accepts an auto argument for a coordset view. + * + * \param coordset The Conduit node that contains the coordset. + * \param func The function/lambda to invoke using the coordset view. + */ +template +void dispatch_rectilinear_coordset(const conduit::Node &coordset, FuncType &&func) +{ + const conduit::Node &values = coordset["values"]; + if(values.number_of_children() == 2) + { + axom::mir::views::FloatNode_to_ArrayView_same( + values[0], + values[1], + [&](auto xView, auto yView) { + RectilinearCoordsetView2 coordView( + xView, + yView); + func(coordView); + }); + } + else if(values.number_of_children() == 3) + { + axom::mir::views::FloatNode_to_ArrayView_same( + values[0], + values[1], + values[2], + [&](auto xView, auto yView, auto zView) { + RectilinearCoordsetView3 coordView( + xView, + yView, + zView); + func(coordView); + }); + } +} + +/*! + * \brief Base template for creating a explicit coordset view. + */ +template +struct make_explicit_coordset +{ }; + +/*! + * \brief Partial specialization for creating 3D explicit coordset view. + */ +template +struct make_explicit_coordset +{ + using CoordsetView = axom::mir::views::ExplicitCoordsetView; + + /*! + * \brief Create the coordset view and initialize it from the coordset. + * \param topo The node containing the coordset. + * \return The coordset view. + */ + static CoordsetView view(const conduit::Node &coordset) + { + namespace bputils = axom::mir::utilities::blueprint; + const conduit::Node &values = coordset.fetch_existing("values"); + auto x = bputils::make_array_view(values[0]); + auto y = bputils::make_array_view(values[1]); + auto z = bputils::make_array_view(values[2]); + return CoordsetView(x, y, z); + } +}; + +/*! + * \brief Partial specialization for creating 2D explicit coordset view. + */ +template +struct make_explicit_coordset +{ + using CoordsetView = axom::mir::views::ExplicitCoordsetView; + + /*! + * \brief Create the coordset view and initialize it from the coordset. + * \param topo The node containing the coordset. + * \return The coordset view. + */ + static CoordsetView view(const conduit::Node &coordset) + { + namespace bputils = axom::mir::utilities::blueprint; + const conduit::Node &values = coordset.fetch_existing("values"); + auto x = bputils::make_array_view(values[0]); + auto y = bputils::make_array_view(values[1]); + return CoordsetView(x, y); + } +}; + +/*! + * \brief Dispatch an explicit coordset to a function. + * + * \tparam FuncType The type of the function / lambda to invoke. It is expected + * that the callable accepts an auto argument for a coordset view. + * + * \param coordset The Conduit node that contains the coordset. + * \param func The function/lambda to invoke using the coordset view. + */ +template +void dispatch_explicit_coordset(const conduit::Node &coordset, FuncType &&func) +{ + const conduit::Node &values = coordset["values"]; + if(values.number_of_children() == 2) + { + axom::mir::views::FloatNode_to_ArrayView_same( + values[0], + values[1], + [&](auto xView, auto yView) { + ExplicitCoordsetView coordView( + xView, + yView); + func(coordView); + }); + } + else if(values.number_of_children() == 3) + { + axom::mir::views::FloatNode_to_ArrayView_same( + values[0], + values[1], + values[2], + [&](auto xView, auto yView, auto zView) { + ExplicitCoordsetView coordView( + xView, + yView, + zView); + func(coordView); + }); + } +} + +/*! + * \brief Given a Conduit/Blueprint coordset, create an appropriate view and + * call the supplied function, passing the coordset view to it. + * + * \tparam FuncType The type of the function / lambda to invoke. It is expected + * that the callable accepts an auto argument for a coordset view. + * + * \param coordset The Conduit node that contains the coordset. + * \param func The function/lambda to invoke using the coordset view. + */ +template +void dispatch_coordset(const conduit::Node &coordset, FuncType &&func) +{ + const std::string cstype = coordset["type"].as_string(); + if(cstype == "uniform") + { + dispatch_uniform_coordset(coordset, func); + } + else if(cstype == "rectilinear") + { + dispatch_rectilinear_coordset(coordset, func); + } + else if(cstype == "explicit") + { + // TODO: get the axis names. + dispatch_explicit_coordset(coordset, func); + } +} + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/dispatch_material.hpp b/src/axom/mir/views/dispatch_material.hpp new file mode 100644 index 0000000000..284178b2ec --- /dev/null +++ b/src/axom/mir/views/dispatch_material.hpp @@ -0,0 +1,147 @@ +// 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) + +#ifndef AXOM_MIR_DISPATCH_MATERIAL_HPP_ +#define AXOM_MIR_DISPATCH_MATERIAL_HPP_ + +#include "axom/mir/views/MaterialView.hpp" +#include "axom/mir/views/NodeArrayView.hpp" + +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief Dispatch a Conduit node containing a matset to a function as the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param func The function/lambda that will operate on the matset view. + */ +template +void dispatch_material(const conduit::Node &matset, FuncType &&func) +{ + if(conduit::blueprint::mesh::matset::is_uni_buffer(matset)) + { + IndexNode_to_ArrayView_same( + matset["material_ids"], + matset["sizes"], + matset["offsets"], + matset["indices"], + [&](auto material_ids, auto sizes, auto offsets, auto indices) { + FloatNode_to_ArrayView(matset["volume_fractions"], [&](auto volume_fractions) { + using IndexType = typename decltype(material_ids)::value_type; + using FloatType = typename decltype(volume_fractions)::value_type; + + UnibufferMaterialView matsetView; + matsetView.set(material_ids, volume_fractions, sizes, offsets, indices); + func(matsetView); + }); + }); + } +#if 0 + else if(conduit::blueprint::mesh::matset::is_multi_buffer(matset)) + { + const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); + const conduit::Node &n_firstValues = volume_fractions[0].fetch_existing("values"); + const conduit::Node &n_firstIndices = volume_fractions[0].fetch_existing("indices"); + IndexNode_To_ArrayView(n_firstIndices, [&](auto firstIndices) + { + FloatNode_To_ArrayView(n_firstValues, [&](auto firstValues) + { + using IntView = decltype(firstIndices); + using IntElement = typename IntView::value_type; + using FloatView = decltype(firstValues); + using FloatElement = typename FloatView::value_type; + + MultiBufferMaterialView matsetView; + + for(conduit::index_t i = 0; i < volume_fractions.number_of_children(); i++) + { + const conduit::Node &values = volume_fractions[i].fetch_existing("values"); + const conduit::Node &indices = volume_fractions[i].fetch_existing("indices"); + + const IntElement *indices_ptr = indices.value(); + const FloatElement *values_ptr = values.value(); + + IntView indices_view(indices_ptr, indices.dtype().number_of_elements()); + FloatView values_view(values_ptr, values.dtype().number_of_elements()); + matsetView.add(indices_view, values_view); + } + + func(matsetView); + }); + }); + } + else if(conduit::blueprint::mesh::matset::is_element_dominant(matset)) + { + const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); + const conduit::Node &n_firstValues = volume_fractions[0]; + FloatNode_To_ArrayView(n_firstValues, [&](auto firstValues) + { + using FloatView = decltype(firstValues); + using FloatElement = typename FloatView::value_type; + + ElementDominantMaterialView matsetView; + + for(conduit::index_t i = 0; i < volume_fractions.number_of_children(); i++) + { + const conduit::Node &values = volume_fractions[i]; + const FloatElement *values_ptr = values.value(); + FloatView values_view(values_ptr, values.dtype().number_of_elements()); + matsetView.add(values_view); + } + + func(matsetView); + }); + } + else if(conduit::blueprint::mesh::matset::is_material_dominant(matset)) + { + const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); + const conduit::Node &element_ids = matset.fetch_existing("element_ids"); + const conduit::Node &n_firstValues = volume_fractions[0]; + const conduit::Node &n_firstIndices = element_ids[0]; + + IndexNode_To_ArrayView(n_firstIndices, [&](auto firstIndices) + { + FloatNode_To_ArrayView(n_firstValues, [&](auto firstValues) + { + using IntView = decltype(firstIndices); + using IntElement = typename IntView::value_type; + using FloatView = decltype(firstValues); + using FloatElement = typename FloatView::value_type; + + MaterialDominantMaterialView matsetView; + + for(conduit::index_t i = 0; i < volume_fractions.number_of_children(); i++) + { + const conduit::Node &indices = element_ids[i]; + const conduit::Node &values = volume_fractions[i]; + + const IntElement *indices_ptr = indices.value(); + const FloatElement *values_ptr = values.value(); + + IntView indices_view(indices_ptr, indices.dtype().number_of_elements()); + FloatView values_view(values_ptr, values.dtype().number_of_elements()); + matsetView.add(indices_view, values_view); + } + + func(matsetView); + }); + }); + } +#endif +} + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/dispatch_rectilinear_topology.hpp b/src/axom/mir/views/dispatch_rectilinear_topology.hpp new file mode 100644 index 0000000000..c1b4abb557 --- /dev/null +++ b/src/axom/mir/views/dispatch_rectilinear_topology.hpp @@ -0,0 +1,266 @@ +// 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) + +#ifndef AXOM_MIR_DISPATCH_RECTILINEAR_TOPOLOGY_HPP_ +#define AXOM_MIR_DISPATCH_RECTILINEAR_TOPOLOGY_HPP_ + +#include "axom/mir/views/StructuredTopologyView.hpp" +#include "axom/mir/views/StructuredIndexing.hpp" +#include "axom/mir/views/dispatch_utilities.hpp" +#include +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +/// Base template +template +struct make_rectilinear +{ }; + +/*! + * \brief Create a 3D structured topology view with normal structured indexing. + */ +template <> +struct make_rectilinear<3> +{ + using Indexing = views::StructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topo. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + const conduit::Node *coordset = + conduit::blueprint::mesh::utils::find_reference_node(topo, "coordset"); + SLIC_ASSERT(coordset != nullptr); + const auto axes = conduit::blueprint::mesh::utils::coordset::axes(*coordset); + const conduit::Node &values = coordset->fetch_existing("values"); + LogicalIndex zoneDims; + zoneDims[0] = values.fetch_existing(axes[0]).dtype().number_of_elements() - 1; + zoneDims[1] = values.fetch_existing(axes[1]).dtype().number_of_elements() - 1; + zoneDims[2] = values.fetch_existing(axes[2]).dtype().number_of_elements() - 1; + return Indexing(zoneDims); + } + + /*! + * \brief Create the topology view and initialize it from the topo. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +/*! + * \brief Create a 2D structured topology view with normal structured indexing. + */ +template <> +struct make_rectilinear<2> +{ + using Indexing = views::StructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + const conduit::Node *coordset = + conduit::blueprint::mesh::utils::find_reference_node(topo, "coordset"); + SLIC_ASSERT(coordset != nullptr); + const auto axes = conduit::blueprint::mesh::utils::coordset::axes(*coordset); + const conduit::Node &values = coordset->fetch_existing("values"); + LogicalIndex zoneDims; + zoneDims[0] = values.fetch_existing(axes[0]).dtype().number_of_elements() - 1; + zoneDims[1] = values.fetch_existing(axes[1]).dtype().number_of_elements() - 1; + return Indexing(zoneDims); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +/*! + * \brief Create a 1D structured topology view with normal structured indexing. + */ +template <> +struct make_rectilinear<1> +{ + using Indexing = views::StructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + const conduit::Node *coordset = + conduit::blueprint::mesh::utils::find_reference_node(topo, "coordset"); + SLIC_ASSERT(coordset != nullptr); + const auto axes = conduit::blueprint::mesh::utils::coordset::axes(*coordset); + const conduit::Node &values = coordset->fetch_existing("values"); + LogicalIndex zoneDims; + zoneDims[0] = values.fetch_existing(axes[0]).dtype().number_of_elements() - 1; + return Indexing(zoneDims); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +namespace internal +{ +/*! + * \brief Base template for dispatching rectilinear topology. + */ +template +struct dispatch_one_rectilinear_topology +{ + static void execute(const conduit::Node &AXOM_UNUSED_PARAM(topo), + FuncType &&AXOM_UNUSED_PARAM(func)) + { } +}; + +/*! + * \brief Partial specialization to dispatch 3D rectilinear topology. + */ +template +struct dispatch_one_rectilinear_topology +{ + /*! + * \brief Make a proper view type for the rectilinear topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + auto topoView = make_rectilinear<3>::view(topo); + const std::string shape("hex"); + func(shape, topoView); + } +}; + +/*! + * \brief Partial specialization to dispatch 2D rectilinear topology. + */ +template +struct dispatch_one_rectilinear_topology +{ + /*! + * \brief Make a proper view type for the rectilinear topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + auto topoView = make_rectilinear<2>::view(topo); + const std::string shape("quad"); + func(shape, topoView); + } +}; + +/*! + * \brief Partial specialization to dispatch 1D rectilinear topology. + */ +template +struct dispatch_one_rectilinear_topology +{ + /*! + * \brief Make a proper view type for the rectilinear topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + auto topoView = make_rectilinear<1>::view(topo); + const std::string shape("line"); + func(shape, topoView); + } +}; + +} // end namespace internal + +/*! + * \brief Creates a topology view compatible with rectilinear topologies and passes that view to the supplied function. + * + * \tparam FuncType The function/lambda type to invoke on the view. + * \tparam SelectedDimensions An integer whose bits indicate which dimensions are set. + * + * \param topo The node that contains the rectilinear topology. + * \param func The function to invoke using the view. + */ +template +void dispatch_rectilinear_topology(const conduit::Node &topo, FuncType &&func) +{ + const conduit::Node *coordset = + conduit::blueprint::mesh::utils::find_reference_node(topo, "coordset"); + SLIC_ASSERT(coordset != nullptr); + const auto axes = conduit::blueprint::mesh::utils::coordset::axes(*coordset); + switch(axes.size()) + { + case 3: + internal::dispatch_one_rectilinear_topology< + dimension_selected(SelectedDimensions, 3), + 3, + FuncType>::execute(topo, std::forward(func)); + break; + case 2: + internal::dispatch_one_rectilinear_topology< + dimension_selected(SelectedDimensions, 2), + 2, + FuncType>::execute(topo, std::forward(func)); + break; + case 1: + internal::dispatch_one_rectilinear_topology< + dimension_selected(SelectedDimensions, 1), + 1, + FuncType>::execute(topo, std::forward(func)); + break; + default: + break; + } +} + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/dispatch_structured_topology.hpp b/src/axom/mir/views/dispatch_structured_topology.hpp new file mode 100644 index 0000000000..aa04d1b438 --- /dev/null +++ b/src/axom/mir/views/dispatch_structured_topology.hpp @@ -0,0 +1,608 @@ +// 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) + +#ifndef AXOM_MIR_DISPATCH_STRUCTURED_TOPOLOGY_HPP_ +#define AXOM_MIR_DISPATCH_STRUCTURED_TOPOLOGY_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/views/StructuredTopologyView.hpp" +#include "axom/mir/views/StructuredIndexing.hpp" +#include "axom/mir/views/StridedStructuredIndexing.hpp" +#include "axom/mir/views/dispatch_utilities.hpp" +#include "axom/mir/views/dispatch_uniform_topology.hpp" +#include "axom/mir/views/dispatch_rectilinear_topology.hpp" +#include "axom/mir/utilities/blueprint_utilities.hpp" + +#include + +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief Base template for strided structured topology creation + */ +template +struct make_strided_structured +{ }; + +/*! + * \brief Create a 3D structured topology view with strided structured indexing. + */ +template <> +struct make_strided_structured<3> +{ + using Indexing = views::StridedStructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing + */ + static Indexing indexing(const conduit::Node &topo) + { + namespace bputils = axom::mir::utilities::blueprint; + const std::string offsetsKey("elements/dims/offsets"); + const std::string stridesKey("elements/dims/strides"); + + LogicalIndex zoneDims; + zoneDims[0] = topo.fetch_existing("elements/dims/i").as_int(); + zoneDims[1] = topo.fetch_existing("elements/dims/j").as_int(); + zoneDims[2] = topo.fetch_existing("elements/dims/k").as_int(); + + LogicalIndex offsets {{0, 0, 0}}, strides {{1, 1, 1}}; + bputils::fillFromNode(topo, offsetsKey, offsets, true); + if(bputils::fillFromNode(topo, stridesKey, strides, true)) + { + // Make zone striding. + strides[1]--; + strides[2] = strides[1] * (zoneDims[1] - 1); + } + else + { + strides[1] = zoneDims[0]; + strides[2] = zoneDims[0] * zoneDims[1]; + } + + return Indexing(zoneDims, offsets, strides); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +/*! + * \brief Create a 2D structured topology view with strided structured indexing. + */ +template <> +struct make_strided_structured<2> +{ + using Indexing = views::StridedStructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + namespace bputils = axom::mir::utilities::blueprint; + const std::string offsetsKey("elements/dims/offsets"); + const std::string stridesKey("elements/dims/strides"); + LogicalIndex zoneDims; + zoneDims[0] = topo.fetch_existing("elements/dims/i").as_int(); + zoneDims[1] = topo.fetch_existing("elements/dims/j").as_int(); + + LogicalIndex offsets {{0, 0}}, strides {{1, 1}}; + bputils::fillFromNode(topo, offsetsKey, offsets, true); + if(bputils::fillFromNode(topo, stridesKey, strides, true)) + { + // Make zone striding. + strides[1]--; + } + else + { + strides[1] = zoneDims[0]; + } + + return Indexing(zoneDims, offsets, strides); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +/*! + * \brief Create a 1D structured topology view with strided structured indexing. + */ +template <> +struct make_strided_structured<1> +{ + using Indexing = views::StridedStructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + namespace bputils = axom::mir::utilities::blueprint; + const std::string offsetsKey("elements/dims/offsets"); + const std::string stridesKey("elements/dims/strides"); + + LogicalIndex zoneDims; + zoneDims[0] = topo.fetch_existing("elements/dims/i").as_int(); + + LogicalIndex offsets {0}, strides {1}; + bputils::fillFromNode(topo, offsetsKey, offsets, true); + bputils::fillFromNode(topo, stridesKey, strides, true); + + return Indexing(zoneDims, offsets, strides); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +/*! + * \brief Base template for structured topology creation + */ +template +struct make_structured +{ }; + +/*! + * \brief Create a 3D structured topology view with normal structured indexing. + */ +template <> +struct make_structured<3> +{ + using Indexing = views::StructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + LogicalIndex zoneDims; + zoneDims[0] = topo.fetch_existing("elements/dims/i").as_int(); + zoneDims[1] = topo.fetch_existing("elements/dims/j").as_int(); + zoneDims[2] = topo.fetch_existing("elements/dims/k").as_int(); + + return Indexing(zoneDims); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +/*! + * \brief Create a 2D structured topology view with normal structured indexing. + */ +template <> +struct make_structured<2> +{ + using Indexing = views::StructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + LogicalIndex zoneDims; + zoneDims[0] = topo.fetch_existing("elements/dims/i").as_int(); + zoneDims[1] = topo.fetch_existing("elements/dims/j").as_int(); + return Indexing(zoneDims); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +/*! + * \brief Create a 1D structured topology view with normal structured indexing. + */ +template <> +struct make_structured<1> +{ + using Indexing = views::StructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + LogicalIndex zoneDims; + zoneDims[0] = topo.fetch_existing("elements/dims/i").as_int(); + + return Indexing(zoneDims); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +//------------------------------------------------------------------------------ +namespace internal +{ +/*! + * \brief Base template for dispatching structured topology. + */ +template +struct dispatch_only_structured_topology +{ + static void execute(const conduit::Node &AXOM_UNUSED_PARAM(topo), + FuncType &&AXOM_UNUSED_PARAM(func)) + { } +}; + +/*! + * \brief Partial specialization to dispatch 3D structured topology. + */ +template +struct dispatch_only_structured_topology +{ + /*! + * \brief Make a proper view type for the structured topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + const std::string offsetsKey("elements/dims/offsets"); + const std::string stridesKey("elements/dims/strides"); + const std::string shape("hex"); + if(topo.has_path(offsetsKey) || topo.has_path(stridesKey)) + { + auto topoView = make_strided_structured<3>::view(topo); + func(shape, topoView); + } + else + { + auto topoView = make_structured<3>::view(topo); + func(shape, topoView); + } + } +}; + +/*! + * \brief Partial specialization to dispatch 2D structured topology. + */ +template +struct dispatch_only_structured_topology +{ + /*! + * \brief Make a proper view type for the structured topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + const std::string offsetsKey("elements/dims/offsets"); + const std::string stridesKey("elements/dims/strides"); + const std::string shape("quad"); + if(topo.has_path(offsetsKey) || topo.has_path(stridesKey)) + { + auto topoView = make_strided_structured<2>::view(topo); + func(shape, topoView); + } + else + { + auto topoView = make_structured<2>::view(topo); + func(shape, topoView); + } + } +}; + +/*! + * \brief Partial specialization to dispatch 1D structured topology. + */ +template +struct dispatch_only_structured_topology +{ + /*! + * \brief Make a proper view type for the structured topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + const std::string offsetsKey("elements/dims/offsets"); + const std::string stridesKey("elements/dims/strides"); + const std::string shape("line"); + if(topo.has_path(offsetsKey) || topo.has_path(stridesKey)) + { + auto topoView = make_strided_structured<1>::view(topo); + func(shape, topoView); + } + else + { + auto topoView = make_structured<1>::view(topo); + func(shape, topoView); + } + } +}; + +//------------------------------------------------------------------------------ +/*! + * \brief Base template for dispatching structured topology. + */ +template +struct dispatch_any_structured_topology +{ + static void execute(const conduit::Node &AXOM_UNUSED_PARAM(topo), + FuncType &&AXOM_UNUSED_PARAM(func)) + { } +}; + +/*! + * \brief Partial specialization to dispatch 3D structured topologies. + */ +template +struct dispatch_any_structured_topology +{ + /*! + * \brief Make a proper view type for the structured topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + const std::string offsetsKey("offsets"), stridesKey("strides"); + const std::string type = topo.fetch_existing("type").as_string(); + const std::string shape("hex"); + + if(type == "structured" && topo.has_path(offsetsKey) && + topo.has_path(stridesKey)) + { + auto topoView = make_strided_structured<3>::view(topo); + func(shape, topoView); + } + else + { + // Make these topology types share the same func dispatch. + StructuredTopologyView> topoView; + if(type == "uniform") + topoView = make_uniform<3>::view(topo); + else if(type == "rectilinear") + topoView = make_rectilinear<3>::view(topo); + else if(type == "structured") + topoView = make_structured<3>::view(topo); + func(shape, topoView); + } + } +}; + +/*! + * \brief Partial specialization to dispatch 2D structured topologies. + */ +template +struct dispatch_any_structured_topology +{ + /*! + * \brief Make a proper view type for the structured topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + const std::string offsetsKey("offsets"), stridesKey("strides"); + const std::string type = topo.fetch_existing("type").as_string(); + const std::string shape("quad"); + if(type == "structured" && topo.has_path(offsetsKey) && + topo.has_path(stridesKey)) + { + auto topoView = make_strided_structured<2>::view(topo); + func(shape, topoView); + } + else + { + // Make these topology types share the same func dispatch. + StructuredTopologyView> topoView; + if(type == "uniform") + topoView = make_uniform<2>::view(topo); + else if(type == "rectilinear") + topoView = make_rectilinear<2>::view(topo); + else if(type == "structured") + topoView = make_structured<2>::view(topo); + func(shape, topoView); + } + } +}; + +/*! + * \brief Partial specialization to dispatch 1D structured topologies. + */ +template +struct dispatch_any_structured_topology +{ + /*! + * \brief Make a proper view type for the structured topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + const std::string offsetsKey("offsets"), stridesKey("strides"); + const std::string type = topo.fetch_existing("type").as_string(); + const std::string shape("line"); + + // Make these topology types share the same func dispatch. + StructuredTopologyView> topoView; + if(type == "uniform") + topoView = make_uniform<1>::view(topo); + else if(type == "rectilinear") + topoView = make_rectilinear<1>::view(topo); + else if(type == "structured") + topoView = make_structured<1>::view(topo); + func(shape, topoView); + } +}; + +} // end namespace internal + +/*! + * \brief Creates a topology view compatible with structured topologies and passes that view to the supplied function. + * + * \tparam FuncType The function/lambda type to invoke on the view. + * \tparam SelectedDimensions An integer whose bits indicate which dimensions are set. dimension + * + * \param topo The node that contains the rectilinear topology. + * \param func The function to invoke using the view. It should accept a string with the shape name and an auto parameter for the view. + */ +template +void dispatch_structured_topology(const conduit::Node &topo, FuncType &&func) +{ + int ndims = 1; + ndims += topo.has_path("elements/dims/j") ? 1 : 0; + ndims += topo.has_path("elements/dims/k") ? 1 : 0; + + switch(ndims) + { + case 3: + internal::dispatch_only_structured_topology< + dimension_selected(SelectedDimensions, 3), + 3, + FuncType>::execute(topo, std::forward(func)); + break; + case 2: + internal::dispatch_only_structured_topology< + dimension_selected(SelectedDimensions, 2), + 2, + FuncType>::execute(topo, std::forward(func)); + break; + case 1: + internal::dispatch_only_structured_topology< + dimension_selected(SelectedDimensions, 1), + 1, + FuncType>::execute(topo, std::forward(func)); + break; + default: + break; + } +} + +/*! + * \brief Creates a topology view compatible with various logically "structured" topologies (uniform, rectilinear, structured) and passes that view to the supplied function. + * + * \tparam FuncType The function/lambda type to invoke on the view. + * \tparam SelectedDimensions An integer whose bits indicate which dimensions are set. dimension + * + * \param topo The node that contains the topology. + * \param func The function to invoke using the view. It should accept a string with the shape name and an auto parameter for the view. + * + * \note We try to initialize the topoView for each dimension and share the dispatch. + */ +template +void dispatch_structured_topologies(const conduit::Node &topo, FuncType &&func) +{ + const auto ndims = conduit::blueprint::mesh::utils::topology::dims(topo); + switch(ndims) + { + case 3: + internal::dispatch_any_structured_topology< + dimension_selected(SelectedDimensions, 3), + 3, + FuncType>::execute(topo, std::forward(func)); + break; + case 2: + internal::dispatch_any_structured_topology< + dimension_selected(SelectedDimensions, 2), + 2, + FuncType>::execute(topo, std::forward(func)); + break; + case 1: + internal::dispatch_any_structured_topology< + dimension_selected(SelectedDimensions, 1), + 1, + FuncType>::execute(topo, std::forward(func)); + break; + default: + break; + } +} + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/dispatch_topology.hpp b/src/axom/mir/views/dispatch_topology.hpp new file mode 100644 index 0000000000..767a065951 --- /dev/null +++ b/src/axom/mir/views/dispatch_topology.hpp @@ -0,0 +1,55 @@ +// 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) + +#ifndef AXOM_MIR_DISPATCH_TOPOLOGY_HPP_ +#define AXOM_MIR_DISPATCH_TOPOLOGY_HPP_ + +#include "axom/mir/views/StructuredTopologyView.hpp" +#include "axom/mir/views/dispatch_uniform_topology.hpp" +#include "axom/mir/views/dispatch_rectilinear_topology.hpp" +#include "axom/mir/views/dispatch_structured_topology.hpp" +#include "axom/mir/views/dispatch_unstructured_topology.hpp" + +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief Creates a topology view and passes that view to the supplied function. + * + * \tparam FuncType The function/lambda type to invoke on the view. + * \tparam SelectedDimensions An integer whose bits indicate which dimensions are set. dimension + * \tparam ShapeTypes A bitset containing the shape types that will be supported for unstructured. + * + * \param topo The node that contains the rectilinear topology. + * \param func The function to invoke using the view. It should accept a string with the shape name and an auto parameter for the view. + */ +template +void dispatch_topology(const conduit::Node &topo, FuncType &&func) +{ + const auto type = topo.fetch_existing("type").as_string(); + + if(type == "unstructured") + { + dispatch_unstructured_topology(topo, func); + } + else if(type == "uniform" || type == "rectilinear" || type == "structured") + { + // Dispatch the structured topologies together because we can be smarter about the views. + dispatch_structured_topologies(topo, func); + } +} + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/dispatch_uniform_topology.hpp b/src/axom/mir/views/dispatch_uniform_topology.hpp new file mode 100644 index 0000000000..1bb4dc121a --- /dev/null +++ b/src/axom/mir/views/dispatch_uniform_topology.hpp @@ -0,0 +1,265 @@ +// 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) + +#ifndef AXOM_MIR_DISPATCH_UNIFORM_TOPOLOGY_HPP_ +#define AXOM_MIR_DISPATCH_UNIFORM_TOPOLOGY_HPP_ + +#include "axom/mir/views/StructuredTopologyView.hpp" +#include "axom/mir/views/dispatch_utilities.hpp" + +#include +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +/*! + * \brief Base template for uniform topology creation + */ +template +struct make_uniform +{ }; + +/*! + * \brief Create a 3D structured topology view with normal structured indexing. + */ +template <> +struct make_uniform<3> +{ + using Indexing = views::StructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + const conduit::Node *coordset = + conduit::blueprint::mesh::utils::find_reference_node(topo, "coordset"); + SLIC_ASSERT(coordset != nullptr); + const conduit::Node &n_dims = coordset->fetch_existing("dims"); + LogicalIndex zoneDims; + zoneDims[0] = n_dims[0].to_index_t() - 1; + zoneDims[1] = n_dims[1].to_index_t() - 1; + zoneDims[2] = n_dims[2].to_index_t() - 1; + return Indexing(zoneDims); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +/*! + * \brief Create a 2D structured topology view with normal structured indexing. + */ +template <> +struct make_uniform<2> +{ + using Indexing = views::StructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + const conduit::Node *coordset = + conduit::blueprint::mesh::utils::find_reference_node(topo, "coordset"); + SLIC_ASSERT(coordset != nullptr); + const conduit::Node &n_dims = coordset->fetch_existing("dims"); + LogicalIndex zoneDims; + zoneDims[0] = n_dims[0].to_index_t() - 1; + zoneDims[1] = n_dims[1].to_index_t() - 1; + return Indexing(zoneDims); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +/*! + * \brief Create a 1D structured topology view with normal structured indexing. + */ +template <> +struct make_uniform<1> +{ + using Indexing = views::StructuredIndexing; + using LogicalIndex = typename Indexing::LogicalIndex; + using TopoView = views::StructuredTopologyView; + + /*! + * \brief Create the indexing and initialize it from the topology. + * \param topo The node containing the topology. + * \return The indexing. + */ + static Indexing indexing(const conduit::Node &topo) + { + const conduit::Node *coordset = + conduit::blueprint::mesh::utils::find_reference_node(topo, "coordset"); + SLIC_ASSERT(coordset != nullptr); + const conduit::Node &n_dims = coordset->fetch_existing("dims"); + LogicalIndex zoneDims; + zoneDims[0] = n_dims[0].to_index_t() - 1; + return Indexing(zoneDims); + } + + /*! + * \brief Create the topology view and initialize it from the topology. + * \param topo The node containing the topology. + * \return The topology view. + */ + static TopoView view(const conduit::Node &topo) + { + return TopoView(indexing(topo)); + } +}; + +namespace internal +{ +/*! + * \brief Base template for dispatching uniform topology. + */ +template +struct dispatch_one_uniform_topology +{ + static void execute(const conduit::Node &AXOM_UNUSED_PARAM(topo), + FuncType &&AXOM_UNUSED_PARAM(func)) + { } +}; + +/*! + * \brief Partial specialization to dispatch 3D uniform topology. + */ +template +struct dispatch_one_uniform_topology +{ + /*! + * \brief Make a proper view type for the uniform topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + auto topoView = make_uniform<3>::view(topo); + const std::string shape("hex"); + func(shape, topoView); + } +}; + +/*! + * \brief Partial specialization to dispatch 2D uniform topology. + */ +template +struct dispatch_one_uniform_topology +{ + /*! + * \brief Make a proper view type for the uniform topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + auto topoView = make_uniform<2>::view(topo); + const std::string shape("quad"); + func(shape, topoView); + } +}; + +/*! + * \brief Partial specialization to dispatch 1D uniform topology. + */ +template +struct dispatch_one_uniform_topology +{ + /*! + * \brief Make a proper view type for the uniform topology and pass the + * view to the supplied kernel. + * + * \param topo The node that contains the topology. + * \param func The kernel to be invoked. + */ + static void execute(const conduit::Node &topo, FuncType &&func) + { + auto topoView = make_uniform<1>::view(topo); + const std::string shape("line"); + func(shape, topoView); + } +}; + +} // end namespace internal + +/*! + * \brief Creates a topology view compatible with uniform topologies and passes that view to the supplied function. + * + * \tparam FuncType The function/lambda type to invoke on the view. + * \tparam SelectedDimensions An integer whose bits indicate which dimensions are set. + * + * \param topo The node that contains the topology. + * \param func The function to invoke using the view. + */ +template +void dispatch_uniform_topology(const conduit::Node &topo, FuncType &&func) +{ + const conduit::Node *coordset = + conduit::blueprint::mesh::utils::find_reference_node(topo, "coordset"); + SLIC_ASSERT(coordset != nullptr); + const conduit::Node &n_dims = coordset->fetch_existing("dims"); + switch(n_dims.dtype().number_of_elements()) + { + case 3: + internal::dispatch_one_uniform_topology< + dimension_selected(SelectedDimensions, 3), + 3, + FuncType>::execute(topo, std::forward(func)); + break; + case 2: + internal::dispatch_one_uniform_topology< + dimension_selected(SelectedDimensions, 2), + 2, + FuncType>::execute(topo, std::forward(func)); + break; + case 1: + internal::dispatch_one_uniform_topology< + dimension_selected(SelectedDimensions, 1), + 1, + FuncType>::execute(topo, std::forward(func)); + break; + default: + break; + } +} + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/dispatch_unstructured_topology.hpp b/src/axom/mir/views/dispatch_unstructured_topology.hpp new file mode 100644 index 0000000000..349c19d547 --- /dev/null +++ b/src/axom/mir/views/dispatch_unstructured_topology.hpp @@ -0,0 +1,557 @@ +// 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) + +#ifndef AXOM_MIR_DISPATCH_UNSTRUCTURED_TOPOLOGY_HPP_ +#define AXOM_MIR_DISPATCH_UNSTRUCTURED_TOPOLOGY_HPP_ + +#include "axom/core.hpp" +#include "axom/mir/views/UnstructuredTopologySingleShapeView.hpp" +#include "axom/mir/views/UnstructuredTopologyPolyhedralView.hpp" +#include "axom/mir/views/UnstructuredTopologyMixedShapeView.hpp" +#include "axom/mir/views/NodeArrayView.hpp" +#include "axom/mir/views/Shapes.hpp" +#include "axom/mir/utilities/blueprint_utilities.hpp" + +#include + +namespace axom +{ +namespace mir +{ +namespace views +{ +// Turn on all bits so all shapes will be enabled. +constexpr int AnyShape = -1; + +/*! + * \brief This function dispatches a Conduit polyhedral unstructured topology. + * + * \tparam FuncType The function/lambda type that will be invoked on the view. + * + * \param topo The node that contains the topology. + * \param func The function/lambda to call with the topology view. + */ +// @{ +template +void dispatch_unstructured_polyhedral_topology(const conduit::Node &topo, + FuncType &&func) +{ + const std::string shape = topo["elements/shape"].as_string(); + if(shape == "polyhedral") + { + IndexNode_to_ArrayView_same( + topo["subelements/connectivity"], + topo["subelements/sizes"], + topo["subelements/offsets"], + topo["elements/connectivity"], + topo["elements/sizes"], + topo["elements/offsets"], + [&](auto seConnView, + auto seSizesView, + auto seOffsetsView, + auto connView, + auto sizesView, + auto offsetsView) { + using ConnType = typename decltype(seConnView)::value_type; + UnstructuredTopologyPolyhedralView ugView(seConnView, + seSizesView, + seOffsetsView, + connView, + sizesView, + offsetsView); + func(shape, ugView); + }); + } +} + +template +void typed_dispatch_unstructured_polyhedral_topology(const conduit::Node &topo, + FuncType &&func) +{ + namespace bputils = axom::mir::utilities::blueprint; + const std::string shape = topo["elements/shape"].as_string(); + if(shape == "polyhedral") + { + // _mir_views_ph_topoview_begin + auto seConnView = + bputils::make_array_view(topo["subelements/connectivity"]); + auto seSizesView = + bputils::make_array_view(topo["subelements/sizes"]); + auto seOffsetsView = + bputils::make_array_view(topo["subelements/offsets"]); + auto connView = + bputils::make_array_view(topo["elements/connectivity"]); + auto sizesView = bputils::make_array_view(topo["elements/sizes"]); + auto offsetsView = + bputils::make_array_view(topo["elements/offsets"]); + + UnstructuredTopologyPolyhedralView ugView(seConnView, + seSizesView, + seOffsetsView, + connView, + sizesView, + offsetsView); + // _mir_views_ph_topoview_end + func(shape, ugView); + } +} +// @} + +/*! + * \brief This function dispatches a Conduit mixed unstructured topology. + * + * \tparam FuncType The function/lambda type that will be invoked on the view. + * + * \param topo The node that contains the topology. + * \param func The function/lambda to call with the topology view. + * + * \note When this function makes the view, the view keeps a reference to + * the shape_map within the topology so we can build our own shape map + * later in the for_all_zones method. + */ +// @{ +template +void dispatch_unstructured_mixed_topology(const conduit::Node &topo, + FuncType &&func) +{ + const std::string shape = topo["elements/shape"].as_string(); + if(shape == "mixed") + { + IndexNode_to_ArrayView_same( + topo["elements/connectivity"], + topo["elements/shapes"], + topo["elements/sizes"], + topo["elements/offsets"], + [&](auto connView, auto shapesView, auto sizesView, auto offsetsView) { + using ConnType = typename decltype(connView)::value_type; + + // Get the allocator that allocated the connectivity. The shape map data + // need to go into the same memory space. + const int allocatorID = axom::getAllocatorIDForAddress( + topo["elements/connectivity"].data_ptr()); + + // Make the shape map. + axom::Array values, ids; + auto shapeMap = buildShapeMap(topo, values, ids, allocatorID); + + UnstructuredTopologyMixedShapeView ugView(connView, + shapesView, + sizesView, + offsetsView, + shapeMap); + func(shape, ugView); + }); + } +} + +template +void typed_dispatch_unstructured_mixed_topology(const conduit::Node &topo, + FuncType &&func) +{ + namespace bputils = axom::mir::utilities::blueprint; + const std::string shape = topo["elements/shape"].as_string(); + if(shape == "mixed") + { + auto connView = + bputils::make_array_view(topo["elements/connectivity"]); + auto shapesView = + bputils::make_array_view(topo["elements/shapes"]); + auto sizesView = bputils::make_array_view(topo["elements/sizes"]); + auto offsetsView = + bputils::make_array_view(topo["elements/offsets"]); + + // Get the allocator that allocated the connectivity. The shape map data + // need to go into the same memory space. + volatile int allocatorID = + axom::getAllocatorIDForAddress(topo["elements/connectivity"].data_ptr()); + + // Make the shape map. + axom::Array values, ids; + auto shapeMap = buildShapeMap(topo, values, ids, allocatorID); + + UnstructuredTopologyMixedShapeView ugView(connView, + shapesView, + sizesView, + offsetsView, + shapeMap); + func(shape, ugView); + } +} +// @} + +#if __cplusplus >= 201703L +// C++17 and later. +template +constexpr int encode_shapes(Args... args) +{ + return (... | args); +} +#else +template +constexpr int encode_shapes_impl(T arg) +{ + return arg; +} + +template +constexpr int encode_shapes_impl(T arg, Args... args) +{ + return (arg | encode_shapes_impl(args...)); +} + +template +constexpr int encode_shapes(Args... args) +{ + return encode_shapes_impl(args...); +} +#endif + +/*! + * \brief This function turns a list of shapeID values into a bitfield that + * encodes the shapes. We use this in templating to limit which + * shapes get supported in dispatch instantiation. + * + * \param args A template parameter pack that contains ShapeID values. + * + * \return An integer that encodes the shape ids. + */ +template +constexpr int select_shapes(Args... args) +{ + return encode_shapes((1 << args)...); +} + +//------------------------------------------------------------------------------ +namespace internal +{ +/*! + * \brief Base template for dispatching various shapes conditionally. + */ +template +struct dispatch_shape +{ + /*! + * \brief Execute method that gets generated when a shape is not enabled or supported. Do nothing. + */ + static void execute( + bool &AXOM_UNUSED_PARAM(eligible), + const std::string &AXOM_UNUSED_PARAM(shape), + const axom::ArrayView &AXOM_UNUSED_PARAM(connView), + const axom::ArrayView &AXOM_UNUSED_PARAM(sizesView), + const axom::ArrayView &AXOM_UNUSED_PARAM(offsetsView), + FuncType &&AXOM_UNUSED_PARAM(func)) + { } + + /*! + * \brief Execute method that gets generated when a shape is not enabled or supported. Do nothing. + */ + static void execute4(bool &AXOM_UNUSED_PARAM(eligible), + const std::string &AXOM_UNUSED_PARAM(shape), + const conduit::Node &AXOM_UNUSED_PARAM(topo), + FuncType &&AXOM_UNUSED_PARAM(func)) + { } +}; + +// Partial specializations that make views for various shape types. + +template +struct dispatch_shape, FuncType> +{ + static void execute(bool &eligible, + const std::string &shape, + const axom::ArrayView &connView, + const axom::ArrayView &sizesView, + const axom::ArrayView &offsetsView, + FuncType &&func) + { + if(eligible && shape == "tri") + { + UnstructuredTopologySingleShapeView> ugView(connView, + sizesView, + offsetsView); + func(shape, ugView); + eligible = false; + } + } +}; + +template +struct dispatch_shape, FuncType> +{ + static void execute(bool &eligible, + const std::string &shape, + const axom::ArrayView &connView, + const axom::ArrayView &sizesView, + const axom::ArrayView &offsetsView, + FuncType &&func) + { + if(eligible && shape == "quad") + { + UnstructuredTopologySingleShapeView> ugView( + connView, + sizesView, + offsetsView); + func(shape, ugView); + eligible = false; + } + } +}; + +template +struct dispatch_shape, FuncType> +{ + static void execute(bool &eligible, + const std::string &shape, + const axom::ArrayView &connView, + const axom::ArrayView &sizesView, + const axom::ArrayView &offsetsView, + FuncType &&func) + { + if(eligible && shape == "tet") + { + UnstructuredTopologySingleShapeView> ugView(connView, + sizesView, + offsetsView); + func(shape, ugView); + eligible = false; + } + } +}; + +template +struct dispatch_shape, FuncType> +{ + static void execute(bool &eligible, + const std::string &shape, + const axom::ArrayView &connView, + const axom::ArrayView &sizesView, + const axom::ArrayView &offsetsView, + FuncType &&func) + { + if(eligible && shape == "pyramid") + { + UnstructuredTopologySingleShapeView> ugView( + connView, + sizesView, + offsetsView); + func(shape, ugView); + eligible = false; + } + } +}; + +template +struct dispatch_shape, FuncType> +{ + static void execute(bool &eligible, + const std::string &shape, + const axom::ArrayView &connView, + const axom::ArrayView &sizesView, + const axom::ArrayView &offsetsView, + FuncType &&func) + { + if(eligible && shape == "wedge") + { + UnstructuredTopologySingleShapeView> ugView( + connView, + sizesView, + offsetsView); + func(shape, ugView); + eligible = false; + } + } +}; + +template +struct dispatch_shape, FuncType> +{ + static void execute(bool &eligible, + const std::string &shape, + const axom::ArrayView &connView, + const axom::ArrayView &sizesView, + const axom::ArrayView &offsetsView, + FuncType &&func) + { + if(eligible && shape == "hex") + { + UnstructuredTopologySingleShapeView> ugView(connView, + sizesView, + offsetsView); + func(shape, ugView); + eligible = false; + } + } +}; + +struct SelectMixedShape +{ }; + +template +struct dispatch_shape +{ + static void execute4(bool &eligible, + const std::string &shape, + const conduit::Node &topo, + FuncType &&func) + { + if(eligible && shape == "mixed") + { + typed_dispatch_unstructured_mixed_topology( + topo, + std::forward(func)); + eligible = false; + } + } +}; + +struct SelectPHShape +{ }; + +template +struct dispatch_shape +{ + static void execute4(bool &eligible, + const std::string &shape, + const conduit::Node &topo, + FuncType &&func) + { + if(eligible && shape == "polyhedral") + { + typed_dispatch_unstructured_polyhedral_topology( + topo, + std::forward(func)); + eligible = false; + } + } +}; + +} // end namespace internal +//------------------------------------------------------------------------------ + +/*! + * \brief This function dispatches a Conduit topology to the right view type + * and passes that view to the supplied function/lambda. + * + * \tparam ShapeTypes Allows us to limit which shape types get compiled in. + * \tparam FuncType The function/lambda type that will be invoked on the view. + * + * \param topo The node that contains the topology. + * \param func The function/lambda to call with the topology view. + */ +template +void typed_dispatch_unstructured_topology(const conduit::Node &topo, + FuncType &&func) +{ + namespace bputils = axom::mir::utilities::blueprint; + const std::string type = topo["type"].as_string(); + if(type == "unstructured") + { + const std::string shape = topo["elements/shape"].as_string(); + const auto connView = + bputils::make_array_view(topo["elements/connectivity"]); + bool eligible = true; + + // Conditionally add polyhedron support. + internal::dispatch_shape::execute4(eligible, + shape, + topo, + std::forward(func)); + + // Conditionally add mixed shape support. + internal::dispatch_shape::execute4(eligible, + shape, + topo, + std::forward(func)); + + // Make sizes / offsets views if the values are present. + axom::ArrayView sizesView, offsetsView; + if(topo.has_path("elements/sizes")) + sizesView = bputils::make_array_view( + topo.fetch_existing("elements/sizes")); + if(topo.has_path("elements/offsets")) + offsetsView = bputils::make_array_view( + topo.fetch_existing("elements/offsets")); + + // Conditionally add support for other shapes. + internal::dispatch_shape, + FuncType>::execute(eligible, + shape, + connView, + sizesView, + offsetsView, + std::forward(func)); + internal::dispatch_shape, + FuncType>::execute(eligible, + shape, + connView, + sizesView, + offsetsView, + std::forward(func)); + internal::dispatch_shape, + FuncType>::execute(eligible, + shape, + connView, + sizesView, + offsetsView, + std::forward(func)); + internal::dispatch_shape, + FuncType>::execute(eligible, + shape, + connView, + sizesView, + offsetsView, + std::forward(func)); + internal::dispatch_shape, + FuncType>::execute(eligible, + shape, + connView, + sizesView, + offsetsView, + std::forward(func)); + internal::dispatch_shape, + FuncType>::execute(eligible, + shape, + connView, + sizesView, + offsetsView, + std::forward(func)); + + // TODO: points, lines, polygon + } +} + +/// Dispatch in a way that does not care about the connectivity type. +template +void dispatch_unstructured_topology(const conduit::Node &topo, FuncType &&func) +{ + IndexNode_to_ArrayView(topo["elements/connectivity"], [&](auto connView) { + using ConnType = typename decltype(connView)::value_type; + typed_dispatch_unstructured_topology(topo, func); + }); +} + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/dispatch_utilities.hpp b/src/axom/mir/views/dispatch_utilities.hpp new file mode 100644 index 0000000000..cd52abc592 --- /dev/null +++ b/src/axom/mir/views/dispatch_utilities.hpp @@ -0,0 +1,57 @@ +// 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) + +#ifndef AXOM_MIR_DISPATCH_UTILITIES_HPP_ +#define AXOM_MIR_DISPATCH_UTILITIES_HPP_ + +namespace axom +{ +namespace mir +{ +namespace views +{ +#if __cplusplus >= 201703L +// C++17 and later. +template +constexpr int encode_dimensions(Dimensions... dims) +{ + return (... | dims); +} +#else +template +constexpr int encode_dimensions_impl(T arg) +{ + return arg; +} + +template +constexpr int encode_dimensions_impl(T arg, Dimensions... dims) +{ + return (arg | encode_dimensions_impl(dims...)); +} + +template +constexpr int encode_dimensions(Dimensions... dims) +{ + return encode_dimensions_impl(dims...); +} +#endif + +template +constexpr int select_dimensions(Dimensions... dims) +{ + return encode_dimensions((1 << dims)...); +} + +constexpr bool dimension_selected(int encoded_dims, int dim) +{ + return encoded_dims & (1 << dim); +} + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/mir/views/view_traits.hpp b/src/axom/mir/views/view_traits.hpp new file mode 100644 index 0000000000..176a9e3ad2 --- /dev/null +++ b/src/axom/mir/views/view_traits.hpp @@ -0,0 +1,126 @@ +// 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) + +#ifndef AXOM_MIR_VIEW_TRAITS_HPP_ +#define AXOM_MIR_VIEW_TRAITS_HPP_ + +#include "axom/core/utilities/BitUtilities.hpp" +#include "axom/mir/views/StructuredTopologyView.hpp" +#include "axom/mir/views/StructuredIndexing.hpp" +#include "axom/mir/views/StridedStructuredIndexing.hpp" +#include "axom/mir/views/Shapes.hpp" + +namespace axom +{ +namespace mir +{ +namespace views +{ +/// Return which shapes to expect for a given dimension. +static constexpr int shapes_for_dimension(int dimension) +{ + int shapes = 0; + switch(dimension) + { + case 2: + axom::utilities::setBitOn(shapes, Tri_ShapeID); + axom::utilities::setBitOn(shapes, Quad_ShapeID); + axom::utilities::setBitOn(shapes, Mixed_ShapeID); + break; + case 3: + axom::utilities::setBitOn(shapes, Tet_ShapeID); + axom::utilities::setBitOn(shapes, Pyramid_ShapeID); + axom::utilities::setBitOn(shapes, Wedge_ShapeID); + axom::utilities::setBitOn(shapes, Hex_ShapeID); + axom::utilities::setBitOn(shapes, Mixed_ShapeID); + break; + } + return shapes; +} + +/// General traits for topology views. +template +struct view_traits +{ + static constexpr bool supports_strided_structured() { return false; } + static constexpr int selected_shapes() + { + return shapes_for_dimension(TopologyView::dimension()); + } +}; + +/// If StructuredTopologyView was instantiated with StridedStructuredIndexing +/// (of varying dimensions) then say that strided structured is supported. +template +struct view_traits>> +{ + static constexpr bool supports_strided_structured() { return true; } + static constexpr int selected_shapes() { return shapes_for_dimension(3); } +}; + +template +struct view_traits>> +{ + static constexpr bool supports_strided_structured() { return true; } + static constexpr int selected_shapes() { return shapes_for_dimension(2); } +}; + +template +struct view_traits>> +{ + static constexpr bool supports_strided_structured() { return true; } + static constexpr int selected_shapes() { return shapes_for_dimension(1); } +}; + +/*! + * \brief Base template for some ArrayView traits. + */ +template +struct array_view_traits +{ }; + +/*! + * \brief This macro defines some template specializations that help us access + * ArrayView<> names. This can be helpful when the ArrayView comes into + * a lambda as an auto argument. + */ +#define AXOM_MAKE_TRAIT(TYPE) \ + template <> \ + struct array_view_traits> \ + { \ + using value_type = TYPE; \ + static constexpr const char* name() \ + { \ + return "axom::ArrayView<" #TYPE ">"; \ + } \ + }; \ + template <> \ + struct array_view_traits> \ + { \ + using value_type = const TYPE; \ + static constexpr const char* name() \ + { \ + return "axom::ArrayView"; \ + } \ + }; + +AXOM_MAKE_TRAIT(signed char) +AXOM_MAKE_TRAIT(char) +AXOM_MAKE_TRAIT(short) +AXOM_MAKE_TRAIT(long) +AXOM_MAKE_TRAIT(int) +AXOM_MAKE_TRAIT(unsigned char) +AXOM_MAKE_TRAIT(unsigned short) +AXOM_MAKE_TRAIT(unsigned long) +AXOM_MAKE_TRAIT(unsigned int) +AXOM_MAKE_TRAIT(float) +AXOM_MAKE_TRAIT(double) +#undef AXOM_MAKE_TRAIT + +} // end namespace views +} // end namespace mir +} // end namespace axom + +#endif diff --git a/src/axom/slam/Map.hpp b/src/axom/slam/Map.hpp index f214ea6711..5031eab1bf 100644 --- a/src/axom/slam/Map.hpp +++ b/src/axom/slam/Map.hpp @@ -15,7 +15,6 @@ #include #include -#include #include "axom/core.hpp" #include "axom/slic.hpp" @@ -863,21 +862,14 @@ bool Map::isValid(bool verboseOutput) const } } - if(verboseOutput) + if(verboseOutput && !bValid) { std::stringstream sstr; sstr << "\n*** Detailed results of isValid on the map.\n"; - if(bValid) - { - sstr << "Map was valid." << std::endl; - } - else - { - sstr << "Map was NOT valid.\n" << sstr.str() << std::endl; - } + sstr << "Map was NOT valid.\n" << sstr.str() << std::endl; - std::cout << sstr.str() << std::endl; + SLIC_DEBUG(sstr.str()); } return bValid; @@ -887,10 +879,11 @@ template ::print() const { bool valid = isValid(true); - std::stringstream sstr; if(valid) { + std::stringstream sstr; + if(!m_set.get()) { sstr << "** map is empty."; @@ -911,9 +904,13 @@ void Map::print() const } } } - } - std::cout << sstr.str() << std::endl; + SLIC_INFO(sstr.str()); + } + else + { + SLIC_INFO("Map was not valid."); + } } } // end namespace slam diff --git a/src/cmake/axom-config.cmake.in b/src/cmake/axom-config.cmake.in index 325581299a..2affdaf51d 100644 --- a/src/cmake/axom-config.cmake.in +++ b/src/cmake/axom-config.cmake.in @@ -42,6 +42,7 @@ if(NOT AXOM_FOUND) set(AXOM_ENABLE_KLEE "@AXOM_ENABLE_KLEE@") set(AXOM_ENABLE_LUMBERJACK "@AXOM_ENABLE_LUMBERJACK@") set(AXOM_ENABLE_MINT "@AXOM_ENABLE_MINT@") + set(AXOM_ENABLE_MIR "@AXOM_ENABLE_MIR@") set(AXOM_ENABLE_PRIMAL "@AXOM_ENABLE_PRIMAL@") set(AXOM_ENABLE_QUEST "@AXOM_ENABLE_QUEST@") set(AXOM_ENABLE_SIDRE "@AXOM_ENABLE_SIDRE@") diff --git a/src/conf.py b/src/conf.py index f662a92b0d..2deb8e8334 100644 --- a/src/conf.py +++ b/src/conf.py @@ -57,7 +57,8 @@ 'sphinxcontrib.jquery', 'sphinx.ext.todo', 'sphinx.ext.coverage', - 'sphinx.ext.mathjax' + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode' ] # Add any paths that contain templates here, relative to this directory. diff --git a/src/docs/dependencies.dot b/src/docs/dependencies.dot index 74eb633644..d0724d3b8e 100644 --- a/src/docs/dependencies.dot +++ b/src/docs/dependencies.dot @@ -4,6 +4,7 @@ digraph dependencies { {inlet klee mint primal quest slam spin} -> {slic core}; mint -> slam mint -> sidre [style="dashed"]; + mir -> {slic core slam primal}; multimat -> {slic slam}; spin -> {slam primal}; sidre -> {slic core}; diff --git a/src/docs/doxygen/Doxyfile.in b/src/docs/doxygen/Doxyfile.in index 9d892011b6..11c28db68e 100644 --- a/src/docs/doxygen/Doxyfile.in +++ b/src/docs/doxygen/Doxyfile.in @@ -786,9 +786,15 @@ INPUT = @PROJECT_SOURCE_DIR@/axom/doxygen_mainpage.md \ @PROJECT_SOURCE_DIR@/axom/mint/mesh \ @PROJECT_SOURCE_DIR@/axom/mint/utils \ @PROJECT_SOURCE_DIR@/axom/mint/execution \ + @PROJECT_SOURCE_DIR@/axom/mir/doxygen_mainpage.md \ + @PROJECT_SOURCE_DIR@/axom/mir/ \ + @PROJECT_SOURCE_DIR@/axom/mir/clipping \ + @PROJECT_SOURCE_DIR@/axom/mir/utilities \ + @PROJECT_SOURCE_DIR@/axom/mir/views \ @PROJECT_SOURCE_DIR@/axom/multimat/doxygen_mainpage.md \ @PROJECT_SOURCE_DIR@/axom/multimat \ @PROJECT_SOURCE_DIR@/axom/primal/doxygen_mainpage.md \ + @PROJECT_SOURCE_DIR@/axom/primal/README.md \ @PROJECT_SOURCE_DIR@/axom/primal/geometry \ @PROJECT_SOURCE_DIR@/axom/primal/operators \ @PROJECT_SOURCE_DIR@/axom/quest \ diff --git a/src/docs/sphinx/coding_guide/sec06_scope.rst b/src/docs/sphinx/coding_guide/sec06_scope.rst index ec3979827c..b0111c00fc 100644 --- a/src/docs/sphinx/coding_guide/sec06_scope.rst +++ b/src/docs/sphinx/coding_guide/sec06_scope.rst @@ -121,19 +121,43 @@ or "protected" data members **must** be scrutinized by other team members. direct access to class data enforces encapsulation and facilitates design changes through refactoring. +6.10 Guard protected/private access qualifiers when hiding methods that contain +kernel launches. + + When building algorithms that are templated for execution on the GPU, + methods that instantiate kernels via `axom::for_all()` cannot be marked + as protected or private when building for certain backends. In these + situations, add conditional compilation around the access qualifiers. + + For example:: + + class Algorithm + { + #if !defined(__CUDACC__) + private: + #endif + void helperMethod() + { + axom::for_all(100, + AXOM_LAMBDA(axom::IndexType index) + { + // do something + }); + } + }; --------------------------------------------------------- Use 'friend' and 'static' rarely --------------------------------------------------------- -6.10 "Friend" declarations **should** be used rarely. When used, they +6.11 "Friend" declarations **should** be used rarely. When used, they **must** appear within the body of a class definition before any class member declarations. This helps make the friend relationship obvious. Note that placing "friend" declarations before the "public:" keyword makes them private, which preserves encapsulation. -6.11 Static class members (methods or data) **must** be used rarely. In +6.12 Static class members (methods or data) **must** be used rarely. In every case, their usage **should** be carefully reviewed by the team. When it is determined that a static member is needed, it **must** appear @@ -148,7 +172,7 @@ every case, their usage **should** be carefully reviewed by the team. Hide nested classes when possible --------------------------------------------------------- -6.12 Nested classes **should** be private unless they are part of the +6.13 Nested classes **should** be private unless they are part of the enclosing class interface. For example:: @@ -190,7 +214,7 @@ enclosing class interface. Limit scope of local variables --------------------------------------------------------- -6.13 Local variables **should** be declared in the narrowest scope possible +6.14 Local variables **should** be declared in the narrowest scope possible and as close to first use as possible. Minimizing variable scope makes source code easier to comprehend and @@ -225,7 +249,7 @@ and as close to first use as possible. f.doSomethingCool(ii); } -6.14 A local reference to any item in the global namespace (which should be +6.15 A local reference to any item in the global namespace (which should be rare if needed at all) **should** use the scope operator ("::") to make the fact that it resides in the global namespace clear. diff --git a/src/docs/sphinx/dev_guide/gpu_porting.rst b/src/docs/sphinx/dev_guide/gpu_porting.rst index ac85a03488..73938f8268 100644 --- a/src/docs/sphinx/dev_guide/gpu_porting.rst +++ b/src/docs/sphinx/dev_guide/gpu_porting.rst @@ -124,7 +124,6 @@ the memory space where data in ``Dynamic`` allows you to define the location at run time, with some caveats (see :ref:`Core Containers` for more details and examples). - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Useful Links ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -161,6 +160,200 @@ GPU device, or on both a GPU device and a CPU host. For example:: When Axom is built without RAJA, ``axom::for_all`` becomes a ``for``-loop on host (CPU). +%%%%%%%%%%%%%%%% +Portability +%%%%%%%%%%%%%%%% + +Adherence to the GPU porting guidelines generally results in code that will compile and run on +multiple backends. However, backends such as CUDA require additional guidelines. + +**Do not use ``auto`` lambda parameters** with ``axom::for_all`` or the code will not +compile under nvcc. + +Do this: + + .. code-block:: cpp + + axom::for_all(n, AXOM_LAMBDA(axom::IndexType index) { /* body */}); + + +Do NOT do this: + + .. code-block:: cpp + + axom::for_all(n, AXOM_LAMBDA(auto index) { /* body */}); + + +**Pass ArrayView by value**. Data in Axom is often contained in useful containers such as ``axom::Array``. +Use ``axom::ArrayView`` to access array data from within kernels. Views contain +a pointer to the data and memory shape information and can be constructed within +device code to access data arrays. Views are captured by kernels and passed to +the device code; this copies the pointer and shape information into an object that +the device code can access. If the view was passed into a method where the ``axom::for_all`` +function is calling a kernel, be sure to pass the view by value so the compiler +does not capture a host reference to the view, causing the kernel to fail. + +Do this: + + .. code-block:: cpp + + template + void doSomething(axom::ArrayView dataView) + { + axom::for_all(dataView.size(), AXOM_LAMBDA(axom::IndexType index) + { + /* body uses dataView[index] */ + }); + } + +Do NOT do this: + + .. code-block:: cpp + + template + void doSomething(axom::ArrayView &dataView) + { + axom::for_all(dataView.size(), AXOM_LAMBDA(axom::IndexType index) + { + /* body uses dataView[index] */ + /* It will crash on GPU devices because the host reference was + captured rather than the object. + */ + }); + } + +If pass by reference is used, create a new view inside the method and then use that +"device" view in the kernel so the device code uses an object captured by value. + + +**Do not call for_all from protected or private methods**. +The nvcc compiler requires the method containing the kernel to be publicly accessible. +Conditional compilation can be used to make methods public when they should otherwise +be private or protected. + +Do this: + + .. code-block:: cpp + + class Algorithm + { + public: + // stuff + #if !defined(__CUDACC__) + private: + #endif + void helperMethod() + { + axom::for_all(n, AXOM_LAMBDA(axom::IndexType index) { /* body */}); + } + }; + +Do NOT do this: + + .. code-block:: cpp + + class Algorithm + { + public: + // stuff + private: + void helperMethod() + { + axom::for_all(n, AXOM_LAMBDA(axom::IndexType index) { /* body */}); + } + }; + +**Avoid for_all within a lambda**. When calling a kernel via ``axom::for_all`` from a surrounding lambda function, +consider calling ``axom::for_all`` from a class member method instead. The nvcc compiler will +not compile kernel invocations inside lambda functions. This pattern comes up when an intermediate +function is supplied a lambda that uses ``axom::for_all`` such as when handling many data types. + +Do this: + + .. code-block:: cpp + + template + class Algorithm + { + public: + void execute(conduit::Node &data) + { + // Handle any data type + Node_to_ArrayView(data, [&](auto dataView) + { + // Delegate operation to a template member method + handleData(dataView); + }); + } + template + void handleData(DataView dataView) + { + // Call the kernel here in the member method + axom::for_all(AXOM_LAMBDA(axom::IndexType) { /* body */ }); + } + }; + +Do NOT do this: + + .. code-block:: cpp + + template + class Algorithm + { + public: + void execute(conduit::Node &data) + { + // Handle any data type + Node_to_ArrayView(data, [&](auto dataView) + { + // nvcc will not compile this + axom::for_all(AXOM_LAMBDA(axom::IndexType) { /* body */ }); + }); + } + }; + +**Avoid calling lambdas from kernels.** This can work on some systems and not on others. +For best odds at a portable algorithm, design your kernel so it is "one level deep", +and does not result in calling other functions that are also marked ``AXOM_LAMBDA``. + +**Specialize templates outside other classes.** It is necessary to extract +a nested class/struct from the containing class before specializing it. + +Do this: + + .. code-block:: cpp + + namespace internal + { + template + struct B { static void method() { } }; + + template <> + struct B<2> { static void method() { /* 2D-specific method*/ } }; + } + + template + struct A + { + void method() { internal::B::method(); } + }; + +Do NOT do this: + + .. code-block:: cpp + + template + struct A + { + template + struct B { static void method() { } }; + + template <> + struct B<2> { static void method() { /* 2D-specific method*/ } }; + + void method() { B::method(); } + }; + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RAJA::kernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -337,6 +530,12 @@ General, Rough Porting Tips are Axom's equivalent functions/classes if it exists, or to add your own or rewrite the code to not use standard library. + * It may not be possible to remove all such warnings on some platforms that support + both CPU/GPU backends since AXOM_LAMBDA will expand to ``__host__ __device__`` and then + the compiler will issue warnings about host functions such as RAJA::ReduceSum::~ReduceSum + for the OpenMP backend being called from ``__host__ __device__`` code. This warning + can be ignored. + * With no more decorating complaints from the compiler, write the logically correct kernel: @@ -355,6 +554,15 @@ General, Rough Porting Tips * Utilize ``printf()`` for debugging output * Try using the ``SEQ_EXEC`` execution space +* If your kernel compiles/links but fails at runtime, the cause is often: + + * The data arrays referenced in the kernel are not in the correct memory space for the kernel. + * A reference to host memory has been captured for use in the kernel. + * There is nothing wrong with your code and the tooling has failed. In this case, try + separating the ``axom::for_all`` and your kernel into a separate method or function. + This can limit the available objects that the compile will attempt to capture and + increase the likelihood that the kernel will run correctly. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Useful Links ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/index.rst b/src/index.rst index f1526cf624..3453c23d17 100644 --- a/src/index.rst +++ b/src/index.rst @@ -51,6 +51,7 @@ are identified. * Klee: Shaping specification and implementation * Lumberjack: Scalable parallel message logging and filtering * Mint: Mesh data model + * Mir: (Material interface reconstruction) * Multimat: Managing multimaterial field data * Primal: Computational geometry primitives * Quest: Querying on surface tool @@ -87,6 +88,9 @@ User guides and source code documentation are always linked on this site. * - Mint - :doc:`User Guide ` - `Source documentation `__ + * - Mir + - :doc:`User Guide ` + - `Source documentation `__ * - Multimat - :doc:`User Guide ` - `Source documentation `__ @@ -123,6 +127,7 @@ Axom has the following inter-component dependencies: - Slic optionally depends on Lumberjack - Slam, Spin, Primal, Mint, Quest, and Sidre depend on Slic - Mint depends on Slam, and optionally Sidre +- Mir depends on Slic, Slam and Primal. - Multimat depends on Slic, and Slam - Inlet depends on Sidre, Slic, and Primal - Klee depends on Sidre, Slic, Inlet and Primal @@ -206,6 +211,7 @@ LLNL-CODE-741217 Klee (Shaping specification and implementation) Lumberjack (Scalable parallel message logging and filtering) Mint (Mesh data model) + Mir (Material Interface Reconstruction) Multimat (Multimaterial fields) Primal (Computational geometry primitives) Quest (Querying on surface tool)