From 04c397104ceec5771e7154983f05bb19922a1425 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 19 Jul 2024 08:57:53 -0700 Subject: [PATCH 01/43] Add const to input arguments to assure they are not changed. --- src/axom/quest/Discretize.cpp | 2 +- src/axom/quest/Discretize.hpp | 4 ++-- src/axom/quest/detail/Discretize_detail.hpp | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/axom/quest/Discretize.cpp b/src/axom/quest/Discretize.cpp index 065a6aec2f..41abb9600a 100644 --- a/src/axom/quest/Discretize.cpp +++ b/src/axom/quest/Discretize.cpp @@ -208,7 +208,7 @@ double octPolyVolume(const OctType& o) } // namespace //------------------------------------------------------------------------------ -int mesh_from_discretized_polyline(axom::ArrayView& octs, +int mesh_from_discretized_polyline(const axom::ArrayView& octs, int octcount, int segcount, mint::Mesh*& mesh) diff --git a/src/axom/quest/Discretize.hpp b/src/axom/quest/Discretize.hpp index e97efafd4e..ddf71aea7e 100644 --- a/src/axom/quest/Discretize.hpp +++ b/src/axom/quest/Discretize.hpp @@ -69,7 +69,7 @@ bool discretize(const SphereType& s, * This routine initializes an Array, \a out. */ template -bool discretize(axom::Array& polyline, +bool discretize(const axom::Array& polyline, int len, int levels, axom::Array& out, @@ -107,7 +107,7 @@ bool discretize(axom::Array& polyline, * the caller is responsible for properly deallocating the mesh object that * the return mesh pointer points to. */ -int mesh_from_discretized_polyline(axom::ArrayView& octs, +int mesh_from_discretized_polyline(const axom::ArrayView& octs, int octcount, int segcount, mint::Mesh*& mesh); diff --git a/src/axom/quest/detail/Discretize_detail.hpp b/src/axom/quest/detail/Discretize_detail.hpp index 9fdb582ba2..fa1fdc7d97 100644 --- a/src/axom/quest/detail/Discretize_detail.hpp +++ b/src/axom/quest/detail/Discretize_detail.hpp @@ -264,7 +264,7 @@ namespace quest * This routine initializes an Array pointed to by \a out. */ template -bool discretize(axom::Array &polyline, +bool discretize(const axom::Array &polyline, int pointcount, int levels, axom::Array &out, @@ -276,8 +276,8 @@ bool discretize(axom::Array &polyline, int segmentcount = pointcount - 1; for(int seg = 0; seg < segmentcount && stillValid; ++seg) { - Point2D &a = polyline[seg]; - Point2D &b = polyline[seg + 1]; + const Point2D &a = polyline[seg]; + const Point2D &b = polyline[seg + 1]; // invalid if a.x > b.x if(a[0] > b[0]) { From 7bd74e5e5f96f7f80798e650470e28ab89cf6e7b Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 30 Jul 2024 17:16:55 -0700 Subject: [PATCH 02/43] Preliminary ability to generate shape in memory. This commit adds test code for testing in-memory shape construction and shaping with those objects. --- src/axom/klee/Geometry.cpp | 18 + src/axom/klee/Geometry.hpp | 48 +- src/axom/mint/mesh/Mesh.hpp | 6 + src/axom/quest/IntersectionShaper.hpp | 18 +- src/axom/quest/MeshViewUtil.hpp | 2 +- src/axom/quest/Shaper.cpp | 26 +- src/axom/quest/Shaper.hpp | 4 +- src/axom/quest/examples/CMakeLists.txt | 25 + .../quest/examples/quest_shape_in_memory.cpp | 1015 +++++++++++++++++ src/axom/sidre/core/Group.cpp | 26 +- src/axom/sidre/core/Group.hpp | 8 +- src/axom/sidre/core/View.cpp | 2 +- 12 files changed, 1167 insertions(+), 31 deletions(-) create mode 100644 src/axom/quest/examples/quest_shape_in_memory.cpp diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index 35fe74940c..db5d8815f1 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -4,6 +4,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/klee/Geometry.hpp" +#include "axom/mint/mesh/Mesh.hpp" #include "axom/klee/GeometryOperators.hpp" @@ -29,6 +30,23 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, , m_operator(std::move(operator_)) { } +Geometry::Geometry(const TransformableGeometryProperties &startProperties, + axom::sidre::Group* simplexMesh, + const std::string& topology, + std::shared_ptr operator_) + : m_startProperties(startProperties) + , m_format("memory-blueprint") + , m_path() + , m_simplexMesh(simplexMesh) + , m_topology(topology) + , m_operator(std::move(operator_)) +{ + // axom::mint::Mesh* mintMesh = axom::mint::getMesh(simplexMesh, topo); + // SLIC_ASSERT(mintMesh->isUnstructured()); + // SLIC_ASSERT(!mintMesh->hasMixedCellTypes()); + // m_simplexMesh.reset(dynamic_cast(mintMesh)); +} + TransformableGeometryProperties Geometry::getEndProperties() const { if(m_operator) diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 2e8e63abe8..8f65cdc22d 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -11,6 +11,7 @@ #include "axom/klee/Dimensions.hpp" #include "axom/klee/Units.hpp" +#include "axom/mint/mesh/UnstructuredMesh.hpp" namespace axom { @@ -54,8 +55,11 @@ inline bool operator!=(const TransformableGeometryProperties &lhs, class Geometry { public: + //!@brief Type for geometry represented by a simplex mesh. + using SimplexMesh = axom::mint::UnstructuredMesh; + /** - * Create a new Geometry object. + * Create a new Geometry object based on a file representation. * * \param startProperties the transformable properties before any * operators are applied @@ -68,6 +72,23 @@ class Geometry std::string path, std::shared_ptr operator_); + /** + * Create a new Geometry object based on a bluieprint tetrahedra mesh. + * + * \param startProperties the transformable properties before any + * operators are applied + * \param simplexMesh a simplex geometry in blueprint format. + * The elements should be segments, triangles or tetrahedra. + * \param topology The \c simplexMesh topology to use. + * \param operator_ a possibly null operator to apply to the geometry. + * + * \internal TODO: Is this the simplex requirement overly restrictive? + */ + Geometry(const TransformableGeometryProperties &startProperties, + axom::sidre::Group *simplexMesh, + const std::string& topology, + std::shared_ptr operator_); + /** * Get the format in which the geometry is specified. * @@ -76,13 +97,24 @@ class Geometry const std::string &getFormat() const { return m_format; } /** - * Get the path at which to find the specification of the geometry + * Get the path at which to find the specification of the geometry, + * for geometries stored in files. * * \return the path to the geometry file */ const std::string &getPath() const { return m_path; } + axom::sidre::Group* getBlueprintMesh() const { return m_simplexMesh; } + + const std::string& getBlueprintTopology() const { return m_topology; } + /// Predicate that returns true when the shape has an associated geometry + /* This seems a poorly named function and probably should be renamed + to isFromFile. Especially true in light of my adding memor-based + geometry. Maybe this method was orginally from the Shape class. + But note that Kenny intentionally renamed it from hasPath(), + to "better match how it will be used.". Ask Kenny. + */ bool hasGeometry() const { return !m_path.empty(); } /** @@ -115,8 +147,20 @@ class Geometry private: TransformableGeometryProperties m_startProperties; + + //!@brief Geometry file format, if it's file-based. std::string m_format; + + //!@brief Geometry file path, if it's file-based. std::string m_path; + + //!@brief Geometry blueprint simplex mesh, if it's in memory. + axom::sidre::Group* m_simplexMesh; + // std::shared_ptr m_simplexMesh; + + //!@brief Topology of the blueprint simplex mesh, if it's in memory. + std::string m_topology; + std::shared_ptr m_operator; }; diff --git a/src/axom/mint/mesh/Mesh.hpp b/src/axom/mint/mesh/Mesh.hpp index f77e5e2279..d876329eb8 100644 --- a/src/axom/mint/mesh/Mesh.hpp +++ b/src/axom/mint/mesh/Mesh.hpp @@ -539,6 +539,12 @@ class Mesh */ inline sidre::Group* getSidreGroup() { return m_group; } + /*! + * \brief Return a const pointer to the sidre::Group associated with this Mesh + * instance or nullptr if none exists. + */ + inline const sidre::Group* getSidreGroup() const { return m_group; } + /*! * \brief Return the name of the topology associated with this Mesh instance, * the return value is undefined if the mesh is not in sidre. diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index b5b2604c44..2c9d2fd841 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -396,9 +396,9 @@ class IntersectionShaper : public Shaper #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - // Prepares the ProE mesh cells for the spatial index + // Prepares the tet mesh mesh cells for the spatial index template - void prepareProECells() + void prepareTetCells() { // Number of tets in mesh m_tetcount = m_surfaceMesh->getNumberOfCells(); @@ -609,8 +609,6 @@ class IntersectionShaper : public Shaper AXOM_UNUSED_VAR(shapeDimension); AXOM_UNUSED_VAR(shapeName); - SLIC_INFO(axom::fmt::format("Current shape is {}", shapeName)); - std::string shapeFormat = shape.getGeometry().getFormat(); if(shapeFormat == "c2c") @@ -620,8 +618,14 @@ class IntersectionShaper : public Shaper else if(shapeFormat == "proe") { - prepareProECells(); + prepareTetCells(); } + + else if(shapeFormat == "memory-blueprint") + { + prepareTetCells(); + } + else { SLIC_ERROR( @@ -983,7 +987,7 @@ class IntersectionShaper : public Shaper axom::deallocate(tet_indices); axom::setDefaultAllocator(current_allocator); - } // end of runShapeQuery() function + } // end of runShapeQueryImpl() function #endif #if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) @@ -1492,7 +1496,7 @@ class IntersectionShaper : public Shaper const std::string shapeFormat = shape.getGeometry().getFormat(); // Testing separate workflow for Pro/E - if(shapeFormat == "proe") + if(shapeFormat == "proe" || shapeFormat == "memory-blueprint") { switch(m_execPolicy) { diff --git a/src/axom/quest/MeshViewUtil.hpp b/src/axom/quest/MeshViewUtil.hpp index 7c3098c6e5..2cfc160c8d 100644 --- a/src/axom/quest/MeshViewUtil.hpp +++ b/src/axom/quest/MeshViewUtil.hpp @@ -197,7 +197,7 @@ static void stridesAndOffsetsToShapes(const axom::StackArray& realSh This class recognizes potential ghost (a.k.a. phony, image) data layers around the domain. Some methods and paramenters names refer to the data with ghosts, while others refer to the data without - hosts (a.k.a. real data). + ghosts (a.k.a. real data). TODO: Figure out if there's a better place for this utility. It's only in axom/quest because the initial need was there. diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index cbf6e5b594..02cafd60e5 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -144,7 +144,7 @@ void Shaper::setRefinementType(Shaper::RefinementType t) bool Shaper::isValidFormat(const std::string& format) const { return (format == "stl" || format == "proe" || format == "c2c" || - format == "none"); + format == "memory-blueprint" || format == "none"); } void Shaper::loadShape(const klee::Shape& shape) @@ -174,6 +174,30 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, this->isValidFormat(file_format), axom::fmt::format("Shape has unsupported format: '{}", file_format)); + const std::string& shapeFormat = shape.getGeometry().getFormat(); + + if (shapeFormat == "memory-blueprint") + { + // TODO: For readability, move most of this into a function. + // Put the in-memory geometry in m_surfaceMesh. + axom::sidre::Group* inputGroup = shape.getGeometry().getBlueprintMesh(); + axom::sidre::Group* rootGroup = inputGroup->getDataStore()->getRoot(); + + std::string modName = inputGroup->getName() + "_modified"; + while (rootGroup->hasGroup(modName)) { modName = modName + "-"; } + + axom::sidre::Group* modGroup = rootGroup->createGroup(modName); + int allocID = inputGroup->getDefaultAllocatorID(); + modGroup->deepCopyGroup(inputGroup, allocID); + + m_surfaceMesh = axom::mint::getMesh(modGroup->getGroup(inputGroup->getName()), + shape.getGeometry().getBlueprintTopology()); + + // Transform the coordinates of the linearized mesh. + applyTransforms(shape); + return; + } + if(!shape.getGeometry().hasGeometry()) { SLIC_DEBUG( diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index 01942de782..85f16fc967 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -108,8 +108,8 @@ class Shaper protected: /*! - * \brief Loads the shape from file into m_surfaceMesh and computes a revolvedVolume - * for the shape. + * \brief Loads the shape from file into m_surfaceMesh and, if its a C2D + * contour, computes a revolvedVolume for the shape. * \param shape The shape. * \param percentError A percent error to use when refining the shape. If it * positive then Axom will try to refine dynamically diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index d462ffff2a..4e19c2cf8c 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -237,6 +237,31 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI endif() endif() +# Shaping in-memory example ------------------------------------------------------ +if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI + AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_MFEM_SIDRE_DATACOLLECTION + AND AXOM_ENABLE_KLEE) + axom_add_executable( + NAME quest_shape_in_memory_ex + SOURCES quest_shape_in_memory.cpp + OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} + DEPENDS_ON ${quest_example_depends} mfem + FOLDER axom/quest/examples + ) + + if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) + # 3D shaping tests. No 2D shaping in this feature, at least for now. + set(_nranks 1) + set(_testname quest_shape_in_memory_3d) + axom_add_test( + NAME ${_testname} + COMMAND quest_shape_in_memory_ex + --method intersection + inline_mesh --min -1 -1 -1 --max 5 5 5 --res 20 20 20 -d 3 + NUM_MPI_TASKS ${_nranks}) + endif() +endif() + # Distributed closest point example ------------------------------------------- if(AXOM_ENABLE_MPI AND AXOM_ENABLE_SIDRE AND HDF5_FOUND) axom_add_executable( diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp new file mode 100644 index 0000000000..49c3040322 --- /dev/null +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -0,0 +1,1015 @@ +// 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 quest_shape_mint_mesh.cpp + * \brief Driver application for shaping material volume fractions onto a simulation mesh + * using a mint::UnstructuredMesh of tets. + * Modeled after shaping_driver.cpp. + */ + +// Axom includes +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/mint.hpp" +#include "axom/primal.hpp" +#include "axom/sidre.hpp" +#include "axom/klee.hpp" +#include "axom/quest.hpp" + +#include "axom/fmt.hpp" +#include "axom/CLI11.hpp" + +// NOTE: The shaping driver requires Axom to be configured with mfem as well as +// the AXOM_ENABLE_MFEM_SIDRE_DATACOLLECTION CMake option +#ifndef AXOM_USE_MFEM + #error Shaping functionality requires Axom to be configured with MFEM and the AXOM_ENABLE_MFEM_SIDRE_DATACOLLECTION option +#endif + +#include "mfem.hpp" + +#ifdef AXOM_USE_MPI + #include "mpi.h" +#endif + +// RAJA +#ifdef AXOM_USE_RAJA + #include "RAJA/RAJA.hpp" +#endif + +// C/C++ includes +#include +#include +#include + +namespace klee = axom::klee; +namespace primal = axom::primal; +namespace quest = axom::quest; +namespace slic = axom::slic; +namespace sidre = axom::sidre; + +using VolFracSampling = quest::shaping::VolFracSampling; + +//------------------------------------------------------------------------------ + +/// Struct to help choose if our shaping method: sampling or intersection for now +enum class ShapingMethod : int +{ + Sampling, + Intersection +}; + +using RuntimePolicy = axom::runtime_policy::Policy; + +/// Struct to parse and store the input parameters +struct Input +{ +public: + std::string meshFile; + std::string outputFile; + + // Inline mesh parameters + std::vector boxMins; + std::vector boxMaxs; + std::vector boxResolution; + int boxDim {-1}; + + std::string shapeFile; + // klee::ShapeSet shapeSet; + + ShapingMethod shapingMethod {ShapingMethod::Sampling}; + RuntimePolicy policy {RuntimePolicy::seq}; + int quadratureOrder {5}; + int outputOrder {2}; + int refinementLevel {7}; + double weldThresh {1e-9}; + double percentError {-1.}; + std::string annotationMode {"none"}; + + std::string backgroundMaterial; + + VolFracSampling vfSampling {VolFracSampling::SAMPLE_AT_QPTS}; + +private: + bool m_verboseOutput {false}; + +public: + bool isVerbose() const { return m_verboseOutput; } + + /// Generate an mfem Cartesian mesh, scaled to the bounding box range + mfem::Mesh* createBoxMesh() + { + mfem::Mesh* mesh = nullptr; + + switch(boxDim) + { + case 2: + { + using BBox2D = primal::BoundingBox; + using Pt2D = primal::Point; + auto res = primal::NumericArray(boxResolution.data()); + auto bbox = BBox2D(Pt2D(boxMins.data()), Pt2D(boxMaxs.data())); + + SLIC_INFO(axom::fmt::format( + "Creating inline box mesh of resolution {} and bounding box {}", + res, + bbox)); + + mesh = quest::util::make_cartesian_mfem_mesh_2D(bbox, res, outputOrder); + } + break; + case 3: + { + using BBox3D = primal::BoundingBox; + using Pt3D = primal::Point; + auto res = primal::NumericArray(boxResolution.data()); + auto bbox = BBox3D(Pt3D(boxMins.data()), Pt3D(boxMaxs.data())); + + SLIC_INFO(axom::fmt::format( + "Creating inline box mesh of resolution {} and bounding box {}", + res, + bbox)); + + mesh = quest::util::make_cartesian_mfem_mesh_3D(bbox, res, outputOrder); + } + break; + default: + SLIC_ERROR("Only 2D and 3D meshes are currently supported."); + break; + } + + // Handle conversion to parallel mfem mesh +#if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) + { + int* partitioning = nullptr; + int part_method = 0; + mfem::Mesh* parallelMesh = + new mfem::ParMesh(MPI_COMM_WORLD, *mesh, partitioning, part_method); + delete[] partitioning; + delete mesh; + mesh = parallelMesh; + } +#endif + + return mesh; + } + + std::unique_ptr loadComputationalMesh() + { + constexpr bool dc_owns_data = true; + mfem::Mesh* mesh = meshFile.empty() ? createBoxMesh() : nullptr; + std::string name = meshFile.empty() ? "mesh" : getDCMeshName(); + + auto dc = std::unique_ptr( + new sidre::MFEMSidreDataCollection(name, mesh, dc_owns_data)); + dc->SetComm(MPI_COMM_WORLD); + + if(!meshFile.empty()) + { + dc->Load(meshFile, "sidre_hdf5"); + } + + return dc; + } + + std::string getDCMeshName() const + { + using axom::utilities::string::removeSuffix; + + // Remove the parent directories and file suffix + std::string name = axom::Path(meshFile).baseName(); + name = removeSuffix(name, ".root"); + + return name; + } + + void parse(int argc, char** argv, axom::CLI::App& app) + { + app.add_option("-o,--output-file", outputFile) + ->description("Path to output file(s)"); + + app.add_flag("-v,--verbose,!--no-verbose", m_verboseOutput) + ->description("Enable/disable verbose output") + ->capture_default_str(); + + app.add_option("-t,--weld-threshold", weldThresh) + ->description("Threshold for welding") + ->check(axom::CLI::NonNegativeNumber) + ->capture_default_str(); + + app.add_option("-e,--percent-error", percentError) + ->description( + "Percent error used for calculating curve refinement and revolved " + "volume.\n" + "If this value is provided then dynamic curve refinement will be used\n" + "instead of segment-based curve refinement.") + ->check(axom::CLI::PositiveNumber) + ->capture_default_str(); + + std::map methodMap { + {"sampling", ShapingMethod::Sampling}, + {"intersection", ShapingMethod::Intersection}}; + app.add_option("--method", shapingMethod) + ->description( + "Determines the shaping method -- either sampling or intersection") + ->capture_default_str() + ->transform( + axom::CLI::CheckedTransformer(methodMap, axom::CLI::ignore_case)); + +#ifdef AXOM_USE_CALIPER + 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 + + // use either an input mesh file or a simple inline Cartesian mesh + { + auto* mesh_file = + app.add_option("-m,--mesh-file", meshFile) + ->description( + "Path to computational mesh (generated by MFEMSidreDataCollection)") + ->check(axom::CLI::ExistingFile); + + auto* inline_mesh_subcommand = + app.add_subcommand("inline_mesh") + ->description("Options for setting up a simple inline mesh") + ->fallthrough(); + + inline_mesh_subcommand->add_option("--min", boxMins) + ->description("Min bounds for box mesh (x,y[,z])") + ->expected(2, 3) + ->required(); + inline_mesh_subcommand->add_option("--max", boxMaxs) + ->description("Max bounds for box mesh (x,y[,z])") + ->expected(2, 3) + ->required(); + + inline_mesh_subcommand->add_option("--res", boxResolution) + ->description("Resolution of the box mesh (i,j[,k])") + ->expected(2, 3) + ->required(); + + auto* inline_mesh_dim = + inline_mesh_subcommand->add_option("-d,--dimension", boxDim) + ->description("Dimension of the box mesh") + ->check(axom::CLI::PositiveNumber) + ->required(); + + // we want either the mesh_file or an inline mesh + mesh_file->excludes(inline_mesh_dim); + inline_mesh_dim->excludes(mesh_file); + } + + app.add_option("--background-material", backgroundMaterial) + ->description("Sets the name of the background material"); + + // parameters that only apply to the sampling method + { + auto* sampling_options = + app.add_option_group("sampling", + "Options related to sampling-based queries"); + + sampling_options->add_option("-o,--order", outputOrder) + ->description("Order of the output grid function") + ->capture_default_str() + ->check(axom::CLI::NonNegativeNumber); + + sampling_options->add_option("-q,--quadrature-order", quadratureOrder) + ->description( + "Quadrature order for sampling the inout field. \n" + "Determines number of samples per element in determining " + "volume fraction field") + ->capture_default_str() + ->check(axom::CLI::PositiveNumber); + + std::map vfsamplingMap { + {"qpts", VolFracSampling::SAMPLE_AT_QPTS}, + {"dofs", VolFracSampling::SAMPLE_AT_DOFS}}; + sampling_options->add_option("-s,--sampling-type", vfSampling) + ->description( + "Sampling strategy. \n" + "Sampling either at quadrature points or collocated with " + "degrees of freedom") + ->capture_default_str() + ->transform( + axom::CLI::CheckedTransformer(vfsamplingMap, axom::CLI::ignore_case)); + } + + // parameters that only apply to the intersection method + { + auto* intersection_options = + app.add_option_group("intersection", + "Options related to intersection-based queries"); + + intersection_options->add_option("-r, --refinements", refinementLevel) + ->description("Number of refinements to perform for revolved contour") + ->capture_default_str() + ->check(axom::CLI::NonNegativeNumber); + + std::stringstream pol_sstr; + pol_sstr << "Set runtime policy for intersection-based sampling 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 + + intersection_options->add_option("-p, --policy", policy, pol_sstr.str()) + ->capture_default_str() + ->transform( + axom::CLI::CheckedTransformer(axom::runtime_policy::s_nameToPolicy)); + } + app.get_formatter()->column_width(50); + + // could throw an exception + app.parse(argc, argv); + + slic::setLoggingMsgLevel(m_verboseOutput ? slic::message::Debug + : slic::message::Info); + } +}; // struct Input + +/** + * \brief Print some info about the mesh + * + * \note In MPI-based configurations, this is a collective call, but + * only prints on rank 0 + */ +void printMeshInfo(mfem::Mesh* mesh, const std::string& prefixMessage = "") +{ + namespace primal = axom::primal; + + int myRank = 0; +#ifdef AXOM_USE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &myRank); +#endif + + int numElements = mesh->GetNE(); + + mfem::Vector mins, maxs; +#ifdef MFEM_USE_MPI + auto* parallelMesh = dynamic_cast(mesh); + if(parallelMesh != nullptr) + { + parallelMesh->GetBoundingBox(mins, maxs); + numElements = parallelMesh->ReduceInt(numElements); + myRank = parallelMesh->GetMyRank(); + } + else +#endif + { + mesh->GetBoundingBox(mins, maxs); + } + + if(myRank == 0) + { + switch(mesh->Dimension()) + { + case 2: + SLIC_INFO(axom::fmt::format( + axom::utilities::locale(), + "{} mesh has {:L} elements and (approximate) bounding box {}", + prefixMessage, + numElements, + primal::BoundingBox(primal::Point(mins.GetData()), + primal::Point(maxs.GetData())))); + break; + case 3: + SLIC_INFO(axom::fmt::format( + axom::utilities::locale(), + "{} mesh has {:L} elements and (approximate) bounding box {}", + prefixMessage, + numElements, + primal::BoundingBox(primal::Point(mins.GetData()), + primal::Point(maxs.GetData())))); + break; + } + } + + slic::flushStreams(); +} + +/// \brief Utility function to initialize the logger +void initializeLogger() +{ + // Initialize Logger + slic::initialize(); + slic::setLoggingMsgLevel(slic::message::Info); + + slic::LogStream* logStream {nullptr}; + +#ifdef AXOM_USE_MPI + int num_ranks = 1; + MPI_Comm_size(MPI_COMM_WORLD, &num_ranks); + if(num_ranks > 1) + { + std::string fmt = "[][]: \n"; + #ifdef AXOM_USE_LUMBERJACK + const int RLIMIT = 8; + logStream = + new slic::LumberjackStream(&std::cout, MPI_COMM_WORLD, RLIMIT, fmt); + #else + logStream = new slic::SynchronizedStream(&std::cout, MPI_COMM_WORLD, fmt); + #endif + } + else +#endif // AXOM_USE_MPI + { + std::string fmt = "[]: \n"; + logStream = new slic::GenericOutputStream(&std::cout, fmt); + } + + slic::addStreamToAllMsgLevels(logStream); +} + +/// \brief Utility function to finalize the logger +void finalizeLogger() +{ + if(slic::isInitialized()) + { + slic::flushStreams(); + slic::finalize(); + } +} + +// Single triangle ShapeSet. +axom::klee::ShapeSet create2DShapeSet(sidre::DataStore& ds) +{ + sidre::Group *meshGroup = ds.getRoot()->createGroup("triangleMesh"); + AXOM_UNUSED_VAR(meshGroup); // variable is only referenced in debug configs + const std::string topo = "mesh"; + const std::string coordset = "coords"; + axom::klee::Geometry::SimplexMesh triangleMesh( + 2, axom::mint::CellType::TRIANGLE, meshGroup, topo, coordset ); + + double lll = 2.0; + + // Insert tet at origin. + triangleMesh.appendNode( 0.0, 0.0 ); + triangleMesh.appendNode( lll, 0.0 ); + triangleMesh.appendNode( 0.0, lll ); + axom::IndexType conn[3] = { 0, 1, 2 }; + triangleMesh.appendCell( conn ); + + meshGroup->print(); + SLIC_ASSERT(axom::mint::blueprint::isValidRootGroup(meshGroup)); + + axom::klee::TransformableGeometryProperties prop{ + axom::klee::Dimensions::Two, + axom::klee::LengthUnit::unspecified}; + axom::klee::Geometry triangleGeom(prop, triangleMesh.getSidreGroup(), topo, nullptr); + + std::vector shapes; + axom::klee::Shape triangleShape( "triangle", "AL", {}, {}, axom::klee::Geometry{prop, triangleMesh.getSidreGroup(), topo, nullptr} ); + shapes.push_back(axom::klee::Shape{"triangle", "AL", {}, {}, axom::klee::Geometry{prop, triangleMesh.getSidreGroup(), topo, nullptr}}); + + axom::klee::ShapeSet shapeSet; + shapeSet.setShapes(shapes); + shapeSet.setDimensions(axom::klee::Dimensions::Two); + + return shapeSet; +} + +axom::klee::ShapeSet create3DShapeSet(sidre::DataStore& ds) +{ + // Shape a single tetrahedron. + sidre::Group *meshGroup = ds.getRoot()->createGroup("tetMesh"); + AXOM_UNUSED_VAR(meshGroup); // variable is only referenced in debug configs + const std::string topo = "mesh"; + const std::string coordset = "coords"; + axom::klee::Geometry::SimplexMesh tetMesh( + 3, axom::mint::CellType::TET, meshGroup, topo, coordset ); + + double lll = 4.0; + + // Insert tet at origin. + tetMesh.appendNode( 0.0, 0.0, 0.0 ); + tetMesh.appendNode( lll, 0.0, 0.0 ); + tetMesh.appendNode( 0.0, lll, 0.0 ); + tetMesh.appendNode( 0.0, 0.0, lll ); + tetMesh.appendNode( lll, lll, 0.0 ); + axom::IndexType conn0[4] = { 0, 1, 2, 3 }; + tetMesh.appendCell( conn0 ); + axom::IndexType conn1[4] = { 4, 1, 2, 3 }; + tetMesh.appendCell( conn1 ); + + SLIC_ASSERT(axom::mint::blueprint::isValidRootGroup(meshGroup)); + + axom::klee::TransformableGeometryProperties prop{ + axom::klee::Dimensions::Three, + axom::klee::LengthUnit::unspecified}; + axom::klee::Geometry tetGeom(prop, tetMesh.getSidreGroup(), topo, nullptr); + + std::vector shapes; + axom::klee::Shape tetShape( "tet", "AL", {}, {}, axom::klee::Geometry{prop, tetMesh.getSidreGroup(), topo, nullptr} ); + shapes.push_back(tetShape); + + axom::klee::ShapeSet shapeSet; + shapeSet.setShapes(shapes); + shapeSet.setDimensions(axom::klee::Dimensions::Three); + + return shapeSet; +} + +double volumeOfTetMesh(const axom::klee::Geometry::SimplexMesh& tetMesh) +{ + using TetType = axom::primal::Tetrahedron; +{std::ofstream os("tets.js"); tetMesh.getSidreGroup()->print(os);} + axom::StackArray nodesShape{tetMesh.getNumberOfNodes()}; + axom::ArrayView x(tetMesh.getCoordinateArray(0), nodesShape); + axom::ArrayView y(tetMesh.getCoordinateArray(1), nodesShape); + axom::ArrayView z(tetMesh.getCoordinateArray(2), nodesShape); + const axom::IndexType cellCount = tetMesh.getNumberOfCells(); + axom::Array tetVolumes(cellCount, cellCount); + double meshVolume = 0.0; + for ( axom::IndexType ic = 0; ic < cellCount; ++ic ) + { + const axom::IndexType* nodeIds = tetMesh.getCellNodeIDs(ic); + TetType tet; + for ( int j = 0; j < 4; ++j ) + { + auto cornerNodeId = nodeIds[j]; + tet[j][0] = x[cornerNodeId]; + tet[j][1] = y[cornerNodeId]; + tet[j][2] = z[cornerNodeId]; + } + meshVolume += tet.volume(); + } + return meshVolume; +} + +/*! + @brief Return the element volumes as a sidre::View. + + If it doesn't exist, allocate and compute it. + \post The volume data is in \c dc->GetNamedBuffer(volFieldName). + + Most of this is lifted from IntersectionShaper::runShapeQueryImpl. +*/ +template +axom::sidre::View* getElementVolumes( sidre::MFEMSidreDataCollection* dc, + const std::string& volFieldName = std::string("elementVolumes") ) +{ + using HexahedronType = axom::primal::Hexahedron; + axom::setDefaultAllocator(::getUmpireDeviceId()); + + axom::sidre::View* volSidreView = dc->GetNamedBuffer(volFieldName); + if (volSidreView == nullptr) + { + mfem::Mesh* mesh = dc->GetMesh(); + const axom::IndexType cellCount = mesh->GetNE(); + + constexpr int NUM_VERTS_PER_HEX = 8; + constexpr int NUM_COMPS_PER_VERT = 3; + constexpr double ZERO_THRESHOLD = 1.e-10; + + axom::Array vertCoords(cellCount * NUM_VERTS_PER_HEX, + cellCount * NUM_VERTS_PER_HEX); + auto vertCoordsView = vertCoords.view(); + + // This runs only only on host, because the mfem::Mesh only uses host memory, I think. + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType cellIdx) { + // Get the indices of this element's vertices + mfem::Array verts; + mesh->GetElementVertices(cellIdx, verts); + SLIC_ASSERT(verts.Size() == NUM_VERTS_PER_HEX); + + // Get the coordinates for the vertices + for(int j = 0; j < NUM_VERTS_PER_HEX; ++j) + { + int vertIdx = cellIdx * NUM_VERTS_PER_HEX + j; + for(int k = 0; k < NUM_COMPS_PER_VERT; k++) + { + vertCoordsView[vertIdx][k] = (mesh->GetVertex(verts[j]))[k]; + } + } + }); + + // Set vertex coords to zero if within threshold. + // (I don't know why we do this. I'm following examples.) + axom::ArrayView flatCoordsView((double*)vertCoords.data(), + vertCoords.size()*Point3D::dimension()); + assert(flatCoordsView.size() == cellCount * NUM_VERTS_PER_HEX * 3); + axom::for_all( + cellCount*3, + AXOM_LAMBDA(axom::IndexType i) { + if ( axom::utilities::isNearlyEqual(flatCoordsView[i], 0.0, ZERO_THRESHOLD) ) + { + flatCoordsView[i] = 0.0; + } + }); + + // Initialize hexahedral elements. + axom::Array hexes(cellCount, cellCount); + auto hexesView = hexes.view(); + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType cellIdx) { + // Set each hexahedral element vertices + hexesView[cellIdx] = HexahedronType(); + for(int j = 0; j < NUM_VERTS_PER_HEX; ++j) + { + int vertIndex = (cellIdx * NUM_VERTS_PER_HEX) + j; + auto& hex = hexesView[cellIdx]; + hex[j] = vertCoordsView[vertIndex]; + } + }); // end of loop to initialize hexahedral elements and bounding boxes + + // Allocate and populate cell volumes. + volSidreView = dc->AllocNamedBuffer(volFieldName, cellCount); + axom::ArrayView volView( volSidreView->getData(), volSidreView->getNumElements() ); + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType cellIdx) { + volView[cellIdx] = hexesView[cellIdx].volume(); + }); + } + + return volSidreView; +} + +/*! + @brief Return global sum of volume of the given material. +*/ +template +double sumMaterialVolumes( sidre::MFEMSidreDataCollection* dc, + const std::string& material ) +{ + mfem::Mesh* mesh = dc->GetMesh(); + int const cellCount = mesh->GetNE(); + + // Get cell volumes from dc. + axom::sidre::View* elementVols = getElementVolumes(dc); + axom::ArrayView elementVolsView(elementVols->getData(), elementVols->getNumElements()); + + // Get material volume fractions + const std::string materialFieldName = axom::fmt::format("vol_frac_{}", material); + mfem::GridFunction* volFracGf = dc->GetField(materialFieldName); + axom::quest::GridFunctionView volFracView(volFracGf); + + using ReducePolicy = typename axom::execution_space::reduce_policy; + RAJA::ReduceSum localVol(0); + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType i) { + localVol += volFracView[i] * elementVolsView[i]; + }); + + double globalVol = localVol.get(); +#ifdef AXOM_USE_MPI + MPI_Allreduce(MPI_IN_PLACE, &globalVol, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD); +#endif + return globalVol; +} + +//------------------------------------------------------------------------------ +int main(int argc, char** argv) +{ + axom::utilities::raii::MPIWrapper mpi_raii_wrapper(argc, argv); + const int my_rank = mpi_raii_wrapper.my_rank(); + + initializeLogger(); + + //--------------------------------------------------------------------------- + // Set up and parse command line arguments + //--------------------------------------------------------------------------- + Input params; + axom::CLI::App app {"Driver for Klee shaping query"}; + + try + { + params.parse(argc, argv, app); + } + catch(const axom::CLI::ParseError& e) + { + int retval = -1; + if(my_rank == 0) + { + retval = app.exit(e); + } + finalizeLogger(); + +#ifdef AXOM_USE_MPI + MPI_Bcast(&retval, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Finalize(); +#endif + exit(retval); + } + + axom::utilities::raii::AnnotationsWrapper annotations_raii_wrapper( + params.annotationMode); + + AXOM_ANNOTATE_BEGIN("quest example for shaping primals"); + AXOM_ANNOTATE_BEGIN("init"); + + // Storage for the shape geometry meshes. + sidre::DataStore ds; + + //--------------------------------------------------------------------------- + // Create simple ShapeSet for the example. + //--------------------------------------------------------------------------- + axom::klee::ShapeSet shapeSet; + switch (params.boxDim) { + case 2: + shapeSet = create2DShapeSet(ds); + break; + case 3: + shapeSet = create3DShapeSet(ds); + break; + } + + const klee::Dimensions shapeDim = shapeSet.getDimensions(); + + // Apply error checking +#ifndef AXOM_USE_C2C + SLIC_ERROR_IF(shapeDim == klee::Dimensions::Two, + "Shaping with contour files requires an Axom configured with " + "the C2C library"); +#endif + + AXOM_ANNOTATE_BEGIN("load mesh"); + //--------------------------------------------------------------------------- + // Load the computational mesh + // originalMeshDC is the input MFEM mesh + // It's converted to shapingDC below, and that is used for shaping calls. + //--------------------------------------------------------------------------- + auto originalMeshDC = params.loadComputationalMesh(); + + //--------------------------------------------------------------------------- + // Set up DataCollection for shaping + // shapingDC is a "copy" of originalMeshDC, the MFEM mesh. + // It's created empty then populated by the SetMesh call. + // shapingMesh and parallelMesh are some kind of temporary versions of originalMeshDC. + //--------------------------------------------------------------------------- + mfem::Mesh* shapingMesh = nullptr; + constexpr bool dc_owns_data = true; + sidre::MFEMSidreDataCollection shapingDC("shaping", shapingMesh, dc_owns_data); + { + shapingDC.SetMeshNodesName("positions"); + + // With MPI, loadComputationalMesh returns a parallel mesh. + mfem::ParMesh* parallelMesh = dynamic_cast(originalMeshDC->GetMesh()); + shapingMesh = (parallelMesh != nullptr) + ? new mfem::ParMesh(*parallelMesh) + : new mfem::Mesh(*originalMeshDC->GetMesh()); + shapingDC.SetMesh(shapingMesh); + } + AXOM_ANNOTATE_END("load mesh"); + printMeshInfo(shapingDC.GetMesh(), "After loading"); + + //--------------------------------------------------------------------------- + // Initialize the shaping query object + //--------------------------------------------------------------------------- + AXOM_ANNOTATE_BEGIN("setup shaping problem"); + quest::Shaper* shaper = nullptr; + switch(params.shapingMethod) + { + case ShapingMethod::Sampling: + shaper = new quest::SamplingShaper(shapeSet, &shapingDC); + break; + case ShapingMethod::Intersection: + shaper = new quest::IntersectionShaper(shapeSet, &shapingDC); + break; + } + SLIC_ASSERT_MSG(shaper != nullptr, "Invalid shaping method selected!"); + + // Set generic parameters for the base Shaper instance + shaper->setVertexWeldThreshold(params.weldThresh); + shaper->setVerbosity(params.isVerbose()); + if(params.percentError > 0.) + { + shaper->setPercentError(params.percentError); + shaper->setRefinementType(quest::Shaper::RefinementDynamic); + } + + // Associate any fields that begin with "vol_frac" with "material" so when + // the data collection is written, a matset will be created. + shaper->getDC()->AssociateMaterialSet("vol_frac", "material"); + + // Set specific parameters for a SamplingShaper, if appropriate + if(auto* samplingShaper = dynamic_cast(shaper)) + { + samplingShaper->setSamplingType(params.vfSampling); + samplingShaper->setQuadratureOrder(params.quadratureOrder); + samplingShaper->setVolumeFractionOrder(params.outputOrder); + + // register a point projector + if(shapingDC.GetMesh()->Dimension() == 3 && shapeDim == klee::Dimensions::Two) + { + samplingShaper->setPointProjector([](primal::Point pt) { + const double& x = pt[0]; + const double& y = pt[1]; + const double& z = pt[2]; + return primal::Point {z, sqrt(x * x + y * y)}; + }); + } + } + + // Set specific parameters here for IntersectionShaper + if(auto* intersectionShaper = dynamic_cast(shaper)) + { + intersectionShaper->setLevel(params.refinementLevel); + intersectionShaper->setExecPolicy(params.policy); + + if(!params.backgroundMaterial.empty()) + { + intersectionShaper->setFreeMaterialName(params.backgroundMaterial); + } + } + + //--------------------------------------------------------------------------- + // Project initial volume fractions, if applicable + //--------------------------------------------------------------------------- + if(auto* samplingShaper = dynamic_cast(shaper)) + { + AXOM_ANNOTATE_SCOPE("import initial volume fractions"); + std::map initial_grid_functions; + + // Generate a background material (w/ volume fractions set to 1) if user provided a name + if(!params.backgroundMaterial.empty()) + { + auto material = params.backgroundMaterial; + auto name = axom::fmt::format("vol_frac_{}", material); + + const int order = params.outputOrder; + const int dim = shapingMesh->Dimension(); + const auto basis = mfem::BasisType::Positive; + + auto* coll = new mfem::L2_FECollection(order, dim, basis); + auto* fes = new mfem::FiniteElementSpace(shapingDC.GetMesh(), coll); + const int sz = fes->GetVSize(); + + auto* view = shapingDC.AllocNamedBuffer(name, sz); + auto* volFrac = new mfem::GridFunction(fes, view->getArray()); + volFrac->MakeOwner(coll); + + (*volFrac) = 1.; + + shapingDC.RegisterField(name, volFrac); + + initial_grid_functions[material] = shapingDC.GetField(name); + } + + // Project provided volume fraction grid functions as quadrature point data + samplingShaper->importInitialVolumeFractions(initial_grid_functions); + } + AXOM_ANNOTATE_END("setup shaping problem"); + AXOM_ANNOTATE_END("init"); + + //--------------------------------------------------------------------------- + // Process each of the shapes + //--------------------------------------------------------------------------- + SLIC_INFO(axom::fmt::format("{:=^80}", "Shaping loop")); + AXOM_ANNOTATE_BEGIN("shaping"); + for(const auto& shape : shapeSet.getShapes()) + { + const std::string shapeFormat = shape.getGeometry().getFormat(); + SLIC_INFO(axom::fmt::format( + "{:-^80}", + axom::fmt::format("Processing shape '{}' of material '{}' (format '{}')", + shape.getName(), + shape.getMaterial(), + shapeFormat))); + + // Load the shape from file. This also applies any transformations. + shaper->loadShape(shape); + slic::flushStreams(); + + // Generate a spatial index over the shape + shaper->prepareShapeQuery(shapeDim, shape); + slic::flushStreams(); + + // Query the mesh against this shape +// { std::ofstream tmpos{"mfem.before.js"}; shaper->getDC()->GetBPGroup()->print(tmpos); } + shaper->runShapeQuery(shape); + slic::flushStreams(); +// { std::ofstream tmpos{"mfem.afterquery.js"}; shaper->getDC()->GetBPGroup()->print(tmpos); } + + // Apply the replacement rules for this shape against the existing materials + shaper->applyReplacementRules(shape); + slic::flushStreams(); +// { std::ofstream tmpos{"mfem.afterreplace.js"}; shaper->getDC()->GetBPGroup()->print(tmpos); } + + // Finalize data structures associated with this shape and spatial index + shaper->finalizeShapeQuery(); + slic::flushStreams(); + } + AXOM_ANNOTATE_END("shaping"); + +{ +axom::klee::Geometry::SimplexMesh shapeMesh(shapeSet.getShapes()[0].getGeometry().getBlueprintMesh(), + shapeSet.getShapes()[0].getGeometry().getBlueprintTopology()); +double vol = volumeOfTetMesh(shapeMesh); +std::cout<< "Vol of tet mesh is " << vol << std::endl; +} + //--------------------------------------------------------------------------- + // After shaping in all shapes, generate/adjust the material volume fractions + //--------------------------------------------------------------------------- + AXOM_ANNOTATE_BEGIN("adjust"); + SLIC_INFO( + axom::fmt::format("{:=^80}", + "Generating volume fraction fields for materials")); + + shaper->adjustVolumeFractions(); + + //--------------------------------------------------------------------------- + // Correctness test: shape volume in shapingMesh should match volume of the shape mesh. + //--------------------------------------------------------------------------- + for(const auto& shape : shapeSet.getShapes()) + { + axom::klee::Geometry::SimplexMesh shapeMesh(shape.getGeometry().getBlueprintMesh(), + shape.getGeometry().getBlueprintTopology()); + double shapeMeshVol = volumeOfTetMesh(shapeMesh); + + const std::string& materialName = shape.getMaterial(); + double shapeVol = sumMaterialVolumes( &shapingDC, materialName ); + double diff = shapeVol - shapeMeshVol; + + bool err = !axom::utilities::isNearlyEqual(shapeVol, shapeMeshVol); + + SLIC_INFO(axom::fmt::format( + "{:-^80}", + axom::fmt::format("Material '{}' in shape '{}' has volume {}, diff of {}, {}.", + materialName, + shape.getName(), + shapeVol, + diff, + (err ? "ERROR" : "OK") ))); + } + slic::flushStreams(); + + //--------------------------------------------------------------------------- + // Compute and print volumes of each material's volume fraction + //--------------------------------------------------------------------------- + using axom::utilities::string::startsWith; + for(auto& kv : shaper->getDC()->GetFieldMap()) + { + if(startsWith(kv.first, "vol_frac_")) + { + const auto mat_name = kv.first.substr(9); + auto* gf = kv.second; + + mfem::ConstantCoefficient one(1.0); + mfem::LinearForm vol_form(gf->FESpace()); + vol_form.AddDomainIntegrator(new mfem::DomainLFIntegrator(one)); + vol_form.Assemble(); + + const double volume = shaper->allReduceSum(*gf * vol_form); + + SLIC_INFO(axom::fmt::format(axom::utilities::locale(), + "Volume of material '{}' is {:.6Lf}", + mat_name, + volume)); + } + } + AXOM_ANNOTATE_END("adjust"); + + //--------------------------------------------------------------------------- + // Save meshes and fields + //--------------------------------------------------------------------------- + if(params.isVerbose()) + { + if(auto* samplingShaper = dynamic_cast(shaper)) + { + SLIC_INFO(axom::fmt::format("{:-^80}", "")); + samplingShaper->printRegisteredFieldNames(" -- after shaping"); + } + } + +#ifdef MFEM_USE_MPI + if (!params.outputFile.empty()) + { + shaper->getDC()->Save(params.outputFile, sidre::Group::getDefaultIOProtocol()); + SLIC_INFO(axom::fmt::format("{:=^80}", "Wrote output mesh " + params.outputFile)); + } +#endif + + delete shaper; + + //--------------------------------------------------------------------------- + // Cleanup and exit + //--------------------------------------------------------------------------- + SLIC_INFO(axom::fmt::format("{:-^80}", "")); + slic::flushStreams(); + + AXOM_ANNOTATE_END("quest shaping example"); + + finalizeLogger(); + + return 0; +} diff --git a/src/axom/sidre/core/Group.cpp b/src/axom/sidre/core/Group.cpp index ed5901c319..b66e3908ec 100644 --- a/src/axom/sidre/core/Group.cpp +++ b/src/axom/sidre/core/Group.cpp @@ -1407,49 +1407,49 @@ Group* Group::copyGroup(Group* group) /* ************************************************************************* * - * Create a deep copy of given Group and make it a child of this Group. + * Create a deep copy of given Group and make the copy a child of this Group. * * The deep copy of a Group will copy the group hierarchy and deep copy * all Views within the hierarchy. * ************************************************************************* */ -Group* Group::deepCopyGroup(Group* group, int allocID) +Group* Group::deepCopyGroup(Group* srcGroup, int allocID) { allocID = getValidAllocatorID(allocID); - if(group == nullptr || hasChildGroup(group->getName())) + if(srcGroup == nullptr || hasChildGroup(srcGroup->getName())) { SLIC_CHECK_MSG( - group != nullptr, + srcGroup != nullptr, SIDRE_GROUP_LOG_PREPEND << "Null pointer provided, no Group to copy."); - if(group != nullptr) + if(srcGroup != nullptr) { - SLIC_CHECK_MSG(!hasChildGroup(group->getName()), + SLIC_CHECK_MSG(!hasChildGroup(srcGroup->getName()), SIDRE_GROUP_LOG_PREPEND << "Invalid copy operation. Group already has " - << "a child named '" << group->getName() << "'."); + << "a child named '" << srcGroup->getName() << "'."); } return nullptr; } - Group* res = createGroup(group->getName()); + Group* dstGroup = createGroup(srcGroup->getName()); // copy child Groups to new Group - for(auto& grp : group->groups()) + for(auto& grp : srcGroup->groups()) { - res->deepCopyGroup(&grp, allocID); + dstGroup->deepCopyGroup(&grp, allocID); } // copy Views to new Group - for(auto& view : group->views()) + for(auto& view : srcGroup->views()) { - res->deepCopyView(&view, allocID); + dstGroup->deepCopyView(&view, allocID); } - return res; + return dstGroup; } /* diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index 1743675741..033a24c523 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -1253,7 +1253,7 @@ class Group /*! * \brief Create a (shallow) copy of Group hierarchy rooted at given - * Group and make it a child of this Group. + * Group and make the copy a child of this Group. * * Note that all Views in the Group hierarchy are copied as well. * @@ -1274,7 +1274,7 @@ class Group /*! * \brief Create a deep copy of Group hierarchy rooted at given Group and - * make it a child of this Group. + * make the copy a child of this Group. * * Note that all Views in the Group hierarchy are deep copied as well. * @@ -1290,12 +1290,12 @@ class Group * If given Group pointer is null or Group already has a child Group with * same name as given Group, method is a no-op. * - * \sa deepCopyGroup + * \sa copyGroup * * \return pointer to the new copied Group object or nullptr if a Group * is not copied into this Group. */ - Group* deepCopyGroup(Group* group, int allocID = INVALID_ALLOCATOR_ID); + Group* deepCopyGroup(Group* srcGroup, int allocID = INVALID_ALLOCATOR_ID); //@} diff --git a/src/axom/sidre/core/View.cpp b/src/axom/sidre/core/View.cpp index 18a8a05fed..21bbdbe6d2 100644 --- a/src/axom/sidre/core/View.cpp +++ b/src/axom/sidre/core/View.cpp @@ -1053,11 +1053,11 @@ void View::deepCopyView(View* copy, int allocID) const if(isDescribed()) { - copy->describe(m_schema.dtype()); if(hasBuffer() || m_state == EXTERNAL) { copy->allocate(getTypeID(), getNumElements(), allocID); } + copy->describe(getTypeID(), getNumDimensions(), m_shape.data()); } switch(m_state) From 88e8222519a6618ab1b6b4757efa76fdff63d85b Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 31 Jul 2024 08:15:56 -0700 Subject: [PATCH 03/43] Add 2 more checks and exit with number of failures found. --- .../quest/examples/quest_shape_in_memory.cpp | 119 ++++++++++++++---- 1 file changed, 92 insertions(+), 27 deletions(-) diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 49c3040322..cfdab128e5 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -772,6 +772,12 @@ int main(int argc, char** argv) AXOM_ANNOTATE_END("load mesh"); printMeshInfo(shapingDC.GetMesh(), "After loading"); + const axom::IndexType cellCount = shapingMesh->GetNE(); + + // TODO Port to GPUs. Shaper should be data-parallel, but data may not be on devices yet. + using ExecSpace = typename axom::SEQ_EXEC; + using ReducePolicy = typename axom::execution_space::reduce_policy; + //--------------------------------------------------------------------------- // Initialize the shaping query object //--------------------------------------------------------------------------- @@ -927,32 +933,6 @@ std::cout<< "Vol of tet mesh is " << vol << std::endl; shaper->adjustVolumeFractions(); - //--------------------------------------------------------------------------- - // Correctness test: shape volume in shapingMesh should match volume of the shape mesh. - //--------------------------------------------------------------------------- - for(const auto& shape : shapeSet.getShapes()) - { - axom::klee::Geometry::SimplexMesh shapeMesh(shape.getGeometry().getBlueprintMesh(), - shape.getGeometry().getBlueprintTopology()); - double shapeMeshVol = volumeOfTetMesh(shapeMesh); - - const std::string& materialName = shape.getMaterial(); - double shapeVol = sumMaterialVolumes( &shapingDC, materialName ); - double diff = shapeVol - shapeMeshVol; - - bool err = !axom::utilities::isNearlyEqual(shapeVol, shapeMeshVol); - - SLIC_INFO(axom::fmt::format( - "{:-^80}", - axom::fmt::format("Material '{}' in shape '{}' has volume {}, diff of {}, {}.", - materialName, - shape.getName(), - shapeVol, - diff, - (err ? "ERROR" : "OK") ))); - } - slic::flushStreams(); - //--------------------------------------------------------------------------- // Compute and print volumes of each material's volume fraction //--------------------------------------------------------------------------- @@ -979,6 +959,91 @@ std::cout<< "Vol of tet mesh is " << vol << std::endl; } AXOM_ANNOTATE_END("adjust"); + int failCounts = 0; + + auto* volFracGroups = shapingDC.GetBPGroup()->getGroup("matsets/material/volume_fractions"); + + //--------------------------------------------------------------------------- + // Correctness test: volume fractions should be in [0,1]. + //--------------------------------------------------------------------------- + RAJA::ReduceSum rangeViolationCount(0); + for (axom::sidre::Group& materialGroup : volFracGroups->groups()) + { + axom::sidre::View* values = materialGroup.getView("value"); + double* volFracData = values->getArray(); + axom::ArrayView volFracDataView(volFracData, cellCount); + axom::for_all(cellCount, + AXOM_LAMBDA(axom::IndexType i) { + bool bad = volFracDataView[i] < 0.0 || volFracDataView[i] > 1.0; + rangeViolationCount += bad; }); + } + + failCounts += (rangeViolationCount.get() != 0); + + SLIC_INFO(axom::fmt::format( + "{:-^80}", + axom::fmt::format("Count of volume fractions outside of [0,1]: {}.", + rangeViolationCount.get()))); + slic::flushStreams(); + + //--------------------------------------------------------------------------- + // Correctness test: volume fractions in each cell should sum to 1.0. + //--------------------------------------------------------------------------- + axom::Array volSums(cellCount); + volSums.fill(0.0); + axom::ArrayView volSumsView = volSums.view(); + for (axom::sidre::Group& materialGroup : volFracGroups->groups()) + { + axom::sidre::View* values = materialGroup.getView("value"); + double* volFracData = values->getArray(); + axom::ArrayView volFracDataView(volFracData, cellCount); + axom::for_all(cellCount, + AXOM_LAMBDA(axom::IndexType i) { + volSumsView[i] += volFracDataView[i]; }); + } + RAJA::ReduceSum nonUnitSums(0); + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType i) { + bool bad = !axom::utilities::isNearlyEqual(volSums[i], 0.0); + nonUnitSums += bad; + }); + + failCounts += (nonUnitSums.get() != 0); + + SLIC_INFO(axom::fmt::format( + "{:-^80}", + axom::fmt::format("Count non-unit volume fraction sums: {}.", + nonUnitSums.get()))); + slic::flushStreams(); + + //--------------------------------------------------------------------------- + // Correctness test: shape volume in shapingMesh should match volume of the shape mesh. + //--------------------------------------------------------------------------- + for(const auto& shape : shapeSet.getShapes()) + { + axom::klee::Geometry::SimplexMesh shapeMesh(shape.getGeometry().getBlueprintMesh(), + shape.getGeometry().getBlueprintTopology()); + double shapeMeshVol = volumeOfTetMesh(shapeMesh); + + const std::string& materialName = shape.getMaterial(); + double shapeVol = sumMaterialVolumes( &shapingDC, materialName ); + double diff = shapeVol - shapeMeshVol; + + bool err = !axom::utilities::isNearlyEqual(shapeVol, shapeMeshVol); + failCounts += err; + + SLIC_INFO(axom::fmt::format( + "{:-^80}", + axom::fmt::format("Material '{}' in shape '{}' has volume {}, diff of {}, {}.", + materialName, + shape.getName(), + shapeVol, + diff, + (err ? "ERROR" : "OK") ))); + } + slic::flushStreams(); + //--------------------------------------------------------------------------- // Save meshes and fields //--------------------------------------------------------------------------- @@ -1011,5 +1076,5 @@ std::cout<< "Vol of tet mesh is " << vol << std::endl; finalizeLogger(); - return 0; + return failCounts; } From 126934b9e0a3415165b4a0c5067aa9bde7320f2e Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 1 Aug 2024 16:07:03 -0700 Subject: [PATCH 04/43] Fix bug that fails to apply GeometryOperators that are not composites. --- src/axom/quest/Shaper.cpp | 43 ++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 02cafd60e5..b756e6627e 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -285,24 +285,39 @@ numerics::Matrix Shaper::getTransforms(const klee::Shape& shape) const const auto identity4x4 = numerics::Matrix::identity(4); numerics::Matrix transformation(identity4x4); auto& geometryOperator = shape.getGeometry().getGeometryOperator(); - auto composite = - std::dynamic_pointer_cast(geometryOperator); - if(composite) - { - // Concatenate the transformations - for(auto op : composite->getOperators()) + if (geometryOperator) { + auto composite = + std::dynamic_pointer_cast(geometryOperator); + if(composite) + { + // Concatenate the transformations + + // Why don't we multiply the matrices in CompositeOperator::addOperator()? + // Why keep the matrices factored and multiply them here repeatedly? + // Combining them would also avoid this if-else logic. BTNG + for(auto op : composite->getOperators()) + { + // Use visitor pattern to extract the affine matrix from supported operators + internal::AffineMatrixVisitor visitor; + op->accept(visitor); + if(!visitor.isValid()) + { + continue; + } + const auto& matrix = visitor.getMatrix(); + numerics::Matrix res(identity4x4); + numerics::matrix_multiply(matrix, transformation, res); + transformation = res; + } + } + else { - // Use visitor pattern to extract the affine matrix from supported operators internal::AffineMatrixVisitor visitor; - op->accept(visitor); - if(!visitor.isValid()) + geometryOperator->accept(visitor); + if (visitor.isValid()) { - continue; + transformation = visitor.getMatrix(); } - const auto& matrix = visitor.getMatrix(); - numerics::Matrix res(identity4x4); - numerics::matrix_multiply(matrix, transformation, res); - transformation = res; } } return transformation; From 03b3f07bc0c4c3782e7283eeb6ce6cb3d3c80622 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 1 Aug 2024 16:13:57 -0700 Subject: [PATCH 05/43] Add optional scaling to test the use of geometry operator. --- src/axom/quest/examples/CMakeLists.txt | 1 + .../quest/examples/quest_shape_in_memory.cpp | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 4e19c2cf8c..6cb741bc1a 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -257,6 +257,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI NAME ${_testname} COMMAND quest_shape_in_memory_ex --method intersection + --scale 0.75 0.75 0.75 inline_mesh --min -1 -1 -1 --max 5 5 5 --res 20 20 20 -d 3 NUM_MPI_TASKS ${_nranks}) endif() diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index cfdab128e5..e3eed61b53 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -71,6 +71,9 @@ struct Input std::string meshFile; std::string outputFile; + // Shape transformation parameters + std::vector scaleFactors; + // Inline mesh parameters std::vector boxMins; std::vector boxMaxs; @@ -228,6 +231,11 @@ struct Input ->check(axom::utilities::ValidCaliperMode); #endif + app.add_option("--scale", scaleFactors) + ->description("Scale factor to apply to shape (x,y[,z])") + ->expected(2, 3) + ->check(axom::CLI::PositiveNumber); + // use either an input mesh file or a simple inline Cartesian mesh { auto* mesh_file = @@ -341,6 +349,7 @@ struct Input : slic::message::Info); } }; // struct Input +Input params; /** * \brief Print some info about the mesh @@ -511,10 +520,23 @@ axom::klee::ShapeSet create3DShapeSet(sidre::DataStore& ds) axom::klee::TransformableGeometryProperties prop{ axom::klee::Dimensions::Three, axom::klee::LengthUnit::unspecified}; - axom::klee::Geometry tetGeom(prop, tetMesh.getSidreGroup(), topo, nullptr); + SLIC_ASSERT( params.scaleFactors.empty() || params.scaleFactors.size() == 3 ); + std::shared_ptr scaleOp; + if (!params.scaleFactors.empty()) + { + scaleOp = + std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + prop); + } + + axom::klee::Geometry tetGeom(prop, tetMesh.getSidreGroup(), topo, scaleOp); + + axom::klee::Geometry tetMeshGeometry( prop, tetMesh.getSidreGroup(), topo, {scaleOp} ); std::vector shapes; - axom::klee::Shape tetShape( "tet", "AL", {}, {}, axom::klee::Geometry{prop, tetMesh.getSidreGroup(), topo, nullptr} ); + axom::klee::Shape tetShape( "tet", "AL", {}, {}, tetMeshGeometry ); shapes.push_back(tetShape); axom::klee::ShapeSet shapeSet; @@ -688,7 +710,6 @@ int main(int argc, char** argv) //--------------------------------------------------------------------------- // Set up and parse command line arguments //--------------------------------------------------------------------------- - Input params; axom::CLI::App app {"Driver for Klee shaping query"}; try @@ -1025,6 +1046,10 @@ std::cout<< "Vol of tet mesh is " << vol << std::endl; axom::klee::Geometry::SimplexMesh shapeMesh(shape.getGeometry().getBlueprintMesh(), shape.getGeometry().getBlueprintTopology()); double shapeMeshVol = volumeOfTetMesh(shapeMesh); + for ( auto s : params.scaleFactors ) + { + shapeMeshVol *= s; + } const std::string& materialName = shape.getMaterial(); double shapeVol = sumMaterialVolumes( &shapingDC, materialName ); From 2cbfb3c02810fc6739c7332121223f1ed6a9ee07 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 6 Aug 2024 16:48:52 -0700 Subject: [PATCH 06/43] Add analytical sphere test and factor out code to generate discrete shape. --- src/axom/klee/Geometry.cpp | 53 ++- src/axom/klee/Geometry.hpp | 116 ++++- src/axom/klee/GeometryOperatorsIO.hpp | 2 +- src/axom/primal/operators/split.hpp | 52 ++ src/axom/quest/CMakeLists.txt | 2 + src/axom/quest/DiscreteShape.cpp | 445 ++++++++++++++++++ src/axom/quest/DiscreteShape.hpp | 164 +++++++ src/axom/quest/IntersectionShaper.hpp | 29 +- src/axom/quest/SamplingShaper.hpp | 13 +- src/axom/quest/Shaper.cpp | 74 +-- src/axom/quest/Shaper.hpp | 9 +- src/axom/quest/examples/CMakeLists.txt | 15 +- .../quest/examples/quest_shape_in_memory.cpp | 144 ++++-- src/axom/quest/examples/shaping_driver.cpp | 2 +- .../quest/tests/quest_intersection_shaper.cpp | 2 +- 15 files changed, 994 insertions(+), 128 deletions(-) create mode 100644 src/axom/quest/DiscreteShape.cpp create mode 100644 src/axom/quest/DiscreteShape.hpp diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index db5d8815f1..844eb36fca 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -4,7 +4,6 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/klee/Geometry.hpp" -#include "axom/mint/mesh/Mesh.hpp" #include "axom/klee/GeometryOperators.hpp" @@ -27,24 +26,48 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, : m_startProperties(startProperties) , m_format(std::move(format)) , m_path(std::move(path)) + , m_generationCount(0) , m_operator(std::move(operator_)) { } Geometry::Geometry(const TransformableGeometryProperties &startProperties, - axom::sidre::Group* simplexMesh, + axom::sidre::Group* meshGroup, const std::string& topology, std::shared_ptr operator_) : m_startProperties(startProperties) , m_format("memory-blueprint") , m_path() - , m_simplexMesh(simplexMesh) + , m_meshGroup(meshGroup) , m_topology(topology) + , m_generationCount(0) , m_operator(std::move(operator_)) { - // axom::mint::Mesh* mintMesh = axom::mint::getMesh(simplexMesh, topo); - // SLIC_ASSERT(mintMesh->isUnstructured()); - // SLIC_ASSERT(!mintMesh->hasMixedCellTypes()); - // m_simplexMesh.reset(dynamic_cast(mintMesh)); +} + +Geometry::Geometry(const TransformableGeometryProperties &startProperties, + const axom::primal::Sphere& sphere, + axom::IndexType generationCount, + std::shared_ptr operator_) + : m_startProperties(startProperties) + , m_format("sphere3D") + , m_path() + , m_meshGroup(nullptr) + , m_topology() + , m_sphere(sphere) + , m_generationCount(generationCount) + , m_operator(std::move(operator_)) +{ +} + +bool Geometry::hasGeometry() const +{ + bool isInMemory = m_format == "memory-blueprint" || m_format == "sphere3D" + || m_format == "cone3D" || m_format == "cylinder3D"; + if (isInMemory) + { + return true; + } + return !m_path.empty(); } TransformableGeometryProperties Geometry::getEndProperties() const @@ -56,5 +79,21 @@ TransformableGeometryProperties Geometry::getEndProperties() const return m_startProperties; } +axom::sidre::Group* Geometry::getBlueprintMesh() const +{ + SLIC_ASSERT_MSG(m_meshGroup, + axom::fmt::format("The Geometry format '{}' is not specified " + "as a blueprint mesh and/or has not been converted into one.", m_format)); + return m_meshGroup; +} + +const std::string& Geometry::getBlueprintTopology() const +{ + SLIC_ASSERT_MSG(m_meshGroup, + axom::fmt::format("The Geometry format '{}' is not specified " + "as a blueprint mesh and/or has not been converted into one.", m_format)); + return m_topology; +} + } // namespace klee } // namespace axom diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 8f65cdc22d..e284452fef 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -11,7 +11,8 @@ #include "axom/klee/Dimensions.hpp" #include "axom/klee/Units.hpp" -#include "axom/mint/mesh/UnstructuredMesh.hpp" +#include "axom/primal.hpp" +#include "axom/sidre.hpp" namespace axom { @@ -55,9 +56,6 @@ inline bool operator!=(const TransformableGeometryProperties &lhs, class Geometry { public: - //!@brief Type for geometry represented by a simplex mesh. - using SimplexMesh = axom::mint::UnstructuredMesh; - /** * Create a new Geometry object based on a file representation. * @@ -73,25 +71,52 @@ class Geometry std::shared_ptr operator_); /** - * Create a new Geometry object based on a bluieprint tetrahedra mesh. + * Create a new Geometry object based on a blueprint tetrahedral mesh. * * \param startProperties the transformable properties before any * operators are applied - * \param simplexMesh a simplex geometry in blueprint format. + * \param meshGroup a simplex geometry in blueprint format. * The elements should be segments, triangles or tetrahedra. - * \param topology The \c simplexMesh topology to use. + * \param topology The \c meshGroup topology to use. * \param operator_ a possibly null operator to apply to the geometry. * * \internal TODO: Is this the simplex requirement overly restrictive? */ Geometry(const TransformableGeometryProperties &startProperties, - axom::sidre::Group *simplexMesh, + axom::sidre::Group *meshGroup, const std::string& topology, std::shared_ptr operator_); + /** + * Create a new sphere Geometry object. + * + * \param startProperties the transformable properties before any + * operators are applied + * \param sphere Analytical sphere specifications + * \param generationCount Number of generations to use for + * discretizing the sphere. + * \param operator_ a possibly null operator to apply to the geometry. + * + * \internal TODO: Is this the simplex requirement overly restrictive? + */ + Geometry(const TransformableGeometryProperties &startProperties, + const axom::primal::Sphere& sphere, + axom::IndexType generationCount, + std::shared_ptr operator_); + /** * Get the format in which the geometry is specified. * + * Values are: + * - "c2c" = C2C file + * - "proe" = ProE file + * - "memory-blueprint" = Blueprint tetrahedral mesh in memory + * - "memory-xy" = discretized 2D, non-negative function as an + * array of points in memory + * - "sphere3D" = 3D sphere, as \c primal::Sphere + * - "cone3D" = 3D cone, as \c primal::Cone + * "cylinder3D" = 3D cylinder, as \c primal::Cylinder + * * \return the format of the shape */ const std::string &getFormat() const { return m_format; } @@ -104,18 +129,29 @@ class Geometry */ const std::string &getPath() const { return m_path; } - axom::sidre::Group* getBlueprintMesh() const { return m_simplexMesh; } + /** + * @brief Return the blueprint mesh, for formats that are specified + * by a blueprint mesh or have been converted to a blueprint mesh. + */ + axom::sidre::Group* getBlueprintMesh() const; + + /** + * @brief Return the blueprint mesh topology, for formats that are specified + * by a blueprint mesh or have been converted to a blueprint mesh. + */ + const std::string& getBlueprintTopology() const; - const std::string& getBlueprintTopology() const { return m_topology; } + /*! @brief Predicate that returns true when the shape has an associated geometry - /// Predicate that returns true when the shape has an associated geometry - /* This seems a poorly named function and probably should be renamed - to isFromFile. Especially true in light of my adding memor-based - geometry. Maybe this method was orginally from the Shape class. - But note that Kenny intentionally renamed it from hasPath(), - to "better match how it will be used.". Ask Kenny. + A false means that this is set up to determine volume fractions without + computing on the geometry. + + TODO: We should just create a new format to represent getting + volume fractions without geometries. Or move this logic into + Shape, because it's confusing to have a Geometry that has no + geometry. */ - bool hasGeometry() const { return !m_path.empty(); } + bool hasGeometry() const; /** * Get a GeometryOperator to apply to this geometry. Can be null. @@ -145,22 +181,60 @@ class Geometry */ TransformableGeometryProperties getEndProperties() const; + /** + @brief Return the number of generations for discretization of + analytical curves. + + This number is unused for geometries that are specified in discrete + form. + */ + axom::IndexType getGenerationCount() const + { + return m_generationCount; + } + + /** + @brief Return the sphere geometry, when the Geometry + represents an alalytical sphere. + */ + const axom::primal::Sphere& getSphere() const + { + return m_sphere; + } + private: TransformableGeometryProperties m_startProperties; - //!@brief Geometry file format, if it's file-based. + //!@brief Geometry file format. std::string m_format; //!@brief Geometry file path, if it's file-based. std::string m_path; - //!@brief Geometry blueprint simplex mesh, if it's in memory. - axom::sidre::Group* m_simplexMesh; - // std::shared_ptr m_simplexMesh; + //!@brief Geometry blueprint simplex mesh, when/if it's in memory. + axom::sidre::Group* m_meshGroup; //!@brief Topology of the blueprint simplex mesh, if it's in memory. std::string m_topology; + //!@brief The analytical sphere, if used. + axom::primal::Sphere m_sphere; + +#if 0 + //!@brief The analytical cylinder, if used. + axom::primal::Sphere m_cylinder; + + //!@brief The analytical cone, if used. + axom::primal::Sphere m_cone; + + //!@brief The discrete 2D curve, if used. + axom::Array m_discreteFunction; +#endif + + //!@brief Generations of refinement for discretizing analytical shapes + // and surfaces of revolutions. + axom::IndexType m_generationCount = 0; + std::shared_ptr m_operator; }; diff --git a/src/axom/klee/GeometryOperatorsIO.hpp b/src/axom/klee/GeometryOperatorsIO.hpp index 04bf55f138..c9af1b43ee 100644 --- a/src/axom/klee/GeometryOperatorsIO.hpp +++ b/src/axom/klee/GeometryOperatorsIO.hpp @@ -91,7 +91,7 @@ class GeometryOperatorData private: Path m_path; std::vector m_singleOperatorData; -}; +}; // GeometryOperatorData /** * Data for a named operator. diff --git a/src/axom/primal/operators/split.hpp b/src/axom/primal/operators/split.hpp index 0fea650b4f..37200ea1f4 100644 --- a/src/axom/primal/operators/split.hpp +++ b/src/axom/primal/operators/split.hpp @@ -78,6 +78,58 @@ void split(const Octahedron& oct, out.push_back(Tet(oct[Q], oct[S], oct[U], C)); }; +/*! + * \brief Splits an Octahedron into eight Tetrahedrons + * + * \tparam Tp the coordinate type, such double or float + * \tparam NDIMS the number of spatial dimensions (must be 3). + * \param [in] oct The Octahedron to split + * \param [out] out C array of 8 Tetrahedron objects; the fragments of + * oct are appended to out. + * + * \pre NDIMS == 3 + * + * The tets are produced by putting a vertex at the centroid of the oct + * and drawing an edge from each vertex to the centroid. + */ +template +AXOM_HOST_DEVICE void split(const Octahedron& oct, + Tetrahedron* outPtr) +{ + // Implemented for three dimensions + SLIC_ASSERT(NDIMS == 3); + + // Type aliases + using NumArray = NumericArray; + using Oct = Octahedron; + using Tet = Tetrahedron; + + // Step 1: Find the centroid + NumArray c; // ctor fills with 0 + for(int i = 0; i < Oct::NUM_VERTS; ++i) + { + c += oct[i].array(); + } + c = c / static_cast(Oct::NUM_VERTS); + typename Oct::PointType C(c); + + // Step 2: Now store the new tets. The documentation for the Octahedron class + // shows how the points are arranged to imply the faces. + + // clang-format off + enum OctVerts {P, Q, R, S, T, U}; + // clang-format on + + outPtr[0] = Tet(oct[P], oct[R], oct[Q], C); + outPtr[1] = Tet(oct[Q], oct[R], oct[S], C); + outPtr[2] = Tet(oct[R], oct[T], oct[S], C); + outPtr[3] = Tet(oct[S], oct[T], oct[U], C); + outPtr[4] = Tet(oct[T], oct[P], oct[U], C); + outPtr[5] = Tet(oct[U], oct[P], oct[Q], C); + outPtr[6] = Tet(oct[P], oct[T], oct[R], C); + outPtr[7] = Tet(oct[Q], oct[S], oct[U], C); +}; + } // namespace primal } // namespace axom diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index d52d50f66c..0a90e18cb7 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -134,8 +134,10 @@ if(MFEM_FOUND AND AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_MFEM_SI list(APPEND quest_headers Shaper.hpp SamplingShaper.hpp IntersectionShaper.hpp + DiscreteShape.hpp detail/shaping/shaping_helpers.hpp) list(APPEND quest_sources Shaper.cpp + DiscreteShape.cpp detail/shaping/shaping_helpers.cpp) list(APPEND quest_depends_on klee) endif() diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp new file mode 100644 index 0000000000..6366fd9195 --- /dev/null +++ b/src/axom/quest/DiscreteShape.cpp @@ -0,0 +1,445 @@ +// 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/quest/DiscreteShape.hpp" +#include "axom/quest/Discretize.hpp" +#include "axom/mint/mesh/UnstructuredMesh.hpp" +#include "axom/klee/GeometryOperators.hpp" +#include "axom/core/utilities/StringUtilities.hpp" +#include "axom/quest/interface/internal/QuestHelpers.hpp" + +#include +#include +#include + +namespace axom +{ +namespace quest +{ +namespace internal +{ +/*! + * \brief Implementation of a GeometryOperatorVisitor for processing klee shape operators + * + * This class extracts the matrix form of supported operators and marks the operator as unvalid otherwise + * To use, check the \a isValid() function after visiting and then call the \a getMatrix() function. + */ +class AffineMatrixVisitor : public klee::GeometryOperatorVisitor +{ +public: + AffineMatrixVisitor() : m_matrix(4, 4) { } + + void visit(const klee::Translation& translation) override + { + m_matrix = translation.toMatrix(); + m_isValid = true; + } + void visit(const klee::Rotation& rotation) override + { + m_matrix = rotation.toMatrix(); + m_isValid = true; + } + void visit(const klee::Scale& scale) override + { + m_matrix = scale.toMatrix(); + m_isValid = true; + } + void visit(const klee::UnitConverter& converter) override + { + m_matrix = converter.toMatrix(); + m_isValid = true; + } + + void visit(const klee::CompositeOperator&) override + { + SLIC_WARNING("CompositeOperator not supported for Shaper query"); + m_isValid = false; + } + void visit(const klee::SliceOperator&) override + { + SLIC_WARNING("SliceOperator not yet supported for Shaper query"); + m_isValid = false; + } + + const numerics::Matrix& getMatrix() const { return m_matrix; } + bool isValid() const { return m_isValid; } + +private: + bool m_isValid {false}; + numerics::Matrix m_matrix; +}; + +} // end namespace internal + +// These were needed for linking - but why? They are constexpr. +constexpr int DiscreteShape::DEFAULT_SAMPLES_PER_KNOT_SPAN; +constexpr double DiscreteShape::MINIMUM_PERCENT_ERROR; +constexpr double DiscreteShape::MAXIMUM_PERCENT_ERROR; +constexpr double DiscreteShape::DEFAULT_VERTEX_WELD_THRESHOLD; + +DiscreteShape::DiscreteShape(const axom::klee::Shape &shape, + const std::string& prefixPath, + axom::sidre::Group* parentGroup) + : m_shape(shape) + , m_sidreGroup(nullptr) +{ + setPrefixPath(prefixPath); + setParentGroup(parentGroup); +} + +void DiscreteShape::setParentGroup(axom::sidre::Group* parentGroup) +{ + if (parentGroup) + { + std::ostringstream os; + os << this; + std::string myGroupName = os.str(); + m_sidreGroup = parentGroup->createGroup(myGroupName); + } +} + +void DiscreteShape::clearInternalData() +{ + m_meshRep.reset(); + if (m_sidreGroup) + { + m_sidreGroup->getParent()->destroyGroupAndData(m_sidreGroup->getIndex()); + m_sidreGroup = nullptr; + } +} + +std::shared_ptr DiscreteShape::createMeshRepresentation() +{ + if (m_meshRep) { return m_meshRep; } + + const axom::klee::Geometry& geometry = m_shape.getGeometry(); + const auto& geometryFormat = geometry.getFormat(); + + if (geometryFormat == "memory-blueprint") + { + // TODO: For readability, move most of this into a function. + // Put the in-memory geometry in m_meshRep. + axom::sidre::Group* inputGroup = geometry.getBlueprintMesh(); + axom::sidre::Group* rootGroup = inputGroup->getDataStore()->getRoot(); + + std::string modName = inputGroup->getName() + "_modified"; + while (rootGroup->hasGroup(modName)) { modName = modName + "-"; } + + axom::sidre::Group* modGroup = rootGroup->createGroup(modName); + int allocID = inputGroup->getDefaultAllocatorID(); + modGroup->deepCopyGroup(inputGroup, allocID); + + m_meshRep.reset(axom::mint::getMesh(modGroup->getGroup(inputGroup->getName()), + m_shape.getGeometry().getBlueprintTopology())); + + // Transform the coordinates of the linearized mesh. + applyTransforms(); + } + else if ( geometryFormat == "sphere3D" ) + { + /* + Discretize the sphere into m_meshRep and apply transforms: + 1. Discretize the sphere into a set of Octahedra. + 2. Split each Octahedron into 8 tets. + 3. Insert tets into tet mesh. + 4. Transform the tet mesh. + We can reduce memory use by eliminating repeated points if it + is a problem. + */ + const auto& sphere = geometry.getSphere(); + using TetType = axom::primal::Tetrahedron; + axom::Array octs; + int octCount = 0; + axom::quest::discretize(sphere, geometry.getGenerationCount(), octs, octCount); + + constexpr int TETS_PER_OCT = 8; + constexpr int NODES_PER_TET = 4; + const axom::IndexType tetCount = octCount * TETS_PER_OCT; + const axom::IndexType nodeCount = tetCount * NODES_PER_TET; + + axom::Array nodeCoords(nodeCount, nodeCount); + axom::Array connectivity( + axom::StackArray{tetCount, NODES_PER_TET}); + + auto nodeCoordsView = nodeCoords.view(); + auto connectivityView = connectivity.view(); + axom::for_all( + octCount, + AXOM_LAMBDA(axom::IndexType octIdx) { + TetType tetsInOct[TETS_PER_OCT]; + axom::primal::split(octs[octIdx], tetsInOct); + for ( int iTet = 0; iTet < TETS_PER_OCT; ++iTet ) + { + axom::IndexType tetIdx = octIdx*TETS_PER_OCT + iTet; + for ( int iNode = 0; iNode < NODES_PER_TET; ++iNode ) + { + axom::IndexType nodeIdx = tetIdx*NODES_PER_TET + iNode; + nodeCoordsView[nodeIdx] = tetsInOct[iTet][iNode]; + connectivityView[tetIdx][iNode] = nodeIdx; + } + } + }); + + // TODO: Set this up with sidre::Group to have data on device. + axom::mint::UnstructuredMesh* tetMesh = nullptr; + if (m_sidreGroup != nullptr) + { + tetMesh = new axom::mint::UnstructuredMesh( + 3, + axom::mint::CellType::TET, + m_sidreGroup, + nodeCount, + tetCount); + } + else + { + tetMesh = new axom::mint::UnstructuredMesh( + 3, + axom::mint::CellType::TET, + nodeCount, + tetCount); + } + tetMesh->appendNodes((double*)nodeCoords.data(), nodeCount); + tetMesh->appendCells(connectivity.data(), tetCount); + m_meshRep.reset(tetMesh); + + applyTransforms(); + } + + if (m_meshRep) + { + return m_meshRep; + } + + if(!m_shape.getGeometry().hasGeometry()) + { + // If shape has no geometry, there's nothing to discretize. + SLIC_WARNING( + axom::fmt::format("Current shape '{}' of material '{}' has no geometry", + m_shape.getName(), + m_shape.getMaterial())); + return m_meshRep; + } + + // We handled all the non-file formats. The rest are file formats. + const std::string& file_format = geometryFormat; + + std::string shapePath = resolvePath(); + SLIC_INFO("Reading file: " << shapePath << "..."); + + // Initialize revolved volume. + m_revolvedVolume = 0.; + + if(utilities::string::endsWith(shapePath, ".stl")) + { + SLIC_ASSERT_MSG( + file_format == "stl", + axom::fmt::format(" '{}' format requires .stl file type", file_format)); + + axom::mint::Mesh* meshRep = nullptr; + quest::internal::read_stl_mesh(shapePath, meshRep, m_comm); + m_meshRep.reset(meshRep); + // Transform the coordinates of the linearized mesh. + applyTransforms(); + } + else if(utilities::string::endsWith(shapePath, ".proe")) + { + SLIC_ASSERT_MSG( + file_format == "proe", + axom::fmt::format(" '{}' format requires .proe file type", file_format)); + + axom::mint::Mesh* meshRep = nullptr; + quest::internal::read_pro_e_mesh(shapePath, meshRep, m_comm); + m_meshRep.reset(meshRep); + } +#ifdef AXOM_USE_C2C + else if(utilities::string::endsWith(shapePath, ".contour")) + { + SLIC_ASSERT_MSG( + file_format == "c2c", + axom::fmt::format(" '{}' format requires .contour file type", file_format)); + + // Get the transforms that are being applied to the mesh. Get them + // as a single concatenated matrix. + auto transform = getTransforms(); + + // Pass in the transform so any transformations can figure into + // computing the revolved volume. + axom::mint::Mesh* meshRep = nullptr; + if(m_refinementType == DiscreteShape::RefinementDynamic && + m_percentError > MINIMUM_PERCENT_ERROR) + { + quest::internal::read_c2c_mesh_non_uniform(shapePath, + transform, + m_percentError, + m_vertexWeldThreshold, + meshRep, + m_revolvedVolume, // output arg + m_comm); + } + else + { + quest::internal::read_c2c_mesh_uniform(shapePath, + transform, + m_samplesPerKnotSpan, + m_vertexWeldThreshold, + meshRep, + m_revolvedVolume, // output arg + m_comm); + } + m_meshRep.reset(meshRep); + + // Transform the coordinates of the linearized mesh. + applyTransforms(); + } +#endif + else + { + SLIC_ERROR( + axom::fmt::format("Unsupported filetype for this Axom configuration. " + "Provided file was '{}', with format '{}'", + shapePath, + file_format)); + } + + return m_meshRep; +} + +void DiscreteShape::applyTransforms() +{ + numerics::Matrix transformation = getTransforms(); + + // Apply transformation to coordinates of each vertex in mesh + if(!transformation.isIdentity()) + { + const int spaceDim = m_meshRep->getDimension(); + const int numSurfaceVertices = m_meshRep->getNumberOfNodes(); + double* x = m_meshRep->getCoordinateArray(mint::X_COORDINATE); + double* y = m_meshRep->getCoordinateArray(mint::Y_COORDINATE); + double* z = spaceDim > 2 + ? m_meshRep->getCoordinateArray(mint::Z_COORDINATE) + : nullptr; + + double xformed[4]; + for(int i = 0; i < numSurfaceVertices; ++i) + { + double coords[4] = {x[i], y[i], (z == nullptr ? 0. : z[i]), 1.}; + numerics::matrix_vector_multiply(transformation, coords, xformed); + x[i] = xformed[0]; + y[i] = xformed[1]; + if(z != nullptr) + { + z[i] = xformed[2]; + } + } + } +} + +numerics::Matrix DiscreteShape::getTransforms() const +{ + const auto identity4x4 = numerics::Matrix::identity(4); + numerics::Matrix transformation(identity4x4); + auto& geometryOperator = m_shape.getGeometry().getGeometryOperator(); + if (geometryOperator) { + auto composite = + std::dynamic_pointer_cast(geometryOperator); + if(composite) + { + // Concatenate the transformations + + // Why don't we multiply the matrices in CompositeOperator::addOperator()? + // Why keep the matrices factored and multiply them here repeatedly? + // Combining them would also avoid this if-else logic. BTNG + for(auto op : composite->getOperators()) + { + // Use visitor pattern to extract the affine matrix from supported operators + internal::AffineMatrixVisitor visitor; + op->accept(visitor); + if(!visitor.isValid()) + { + continue; + } + const auto& matrix = visitor.getMatrix(); + numerics::Matrix res(identity4x4); + numerics::matrix_multiply(matrix, transformation, res); + transformation = res; + } + } + else + { + internal::AffineMatrixVisitor visitor; + geometryOperator->accept(visitor); + if (visitor.isValid()) + { + transformation = visitor.getMatrix(); + } + } + } + return transformation; +} + +void DiscreteShape::setSamplesPerKnotSpan(int nSamples) +{ + using axom::utilities::clampLower; + SLIC_WARNING_IF( + nSamples < 1, + axom::fmt::format( + "Samples per knot span must be at least 1. Provided value was {}", + nSamples)); + + m_samplesPerKnotSpan = clampLower(nSamples, 1); +} + +void DiscreteShape::setVertexWeldThreshold(double threshold) +{ + SLIC_WARNING_IF( + threshold <= 0., + axom::fmt::format( + "Vertex weld threshold should be positive Provided value was {}", + threshold)); + + m_vertexWeldThreshold = threshold; +} + +void DiscreteShape::setPercentError(double percent) +{ + using axom::utilities::clampVal; + SLIC_WARNING_IF( + percent <= MINIMUM_PERCENT_ERROR, + axom::fmt::format("Percent error must be greater than {}. Provided value " + "was {}. Dynamic refinement will not be used.", + MINIMUM_PERCENT_ERROR, + percent)); + SLIC_WARNING_IF(percent > MAXIMUM_PERCENT_ERROR, + axom::fmt::format( + "Percent error must be less than {}. Provided value was {}", + MAXIMUM_PERCENT_ERROR, + percent)); + if(percent <= MINIMUM_PERCENT_ERROR) + { + m_refinementType = DiscreteShape::RefinementUniformSegments; + } + m_percentError = + clampVal(percent, MINIMUM_PERCENT_ERROR, MAXIMUM_PERCENT_ERROR); +} + +std::string DiscreteShape::resolvePath() const +{ + const std::string& geomPath = m_shape.getGeometry().getPath(); + if(geomPath[0] == '/') + { + return geomPath; + } + if(m_prefixPath.empty()) + { + throw std::logic_error("Relative geometry path requires a parent path."); + } + std::string dir; + utilities::filesystem::getDirName(dir, m_prefixPath); + return utilities::filesystem::joinPath(dir, geomPath); +} + +} // namespace klee +} // namespace axom diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp new file mode 100644 index 0000000000..78f6f4ffdf --- /dev/null +++ b/src/axom/quest/DiscreteShape.hpp @@ -0,0 +1,164 @@ +// 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_KLEE_DISCRETE_SHAPE_HPP +#define AXOM_KLEE_DISCRETE_SHAPE_HPP + +#include +#include + +#include "axom/klee/Shape.hpp" +#include "axom/mint/mesh/Mesh.hpp" + +#if defined(AXOM_USE_MPI) +#include "mpi.h" +#endif + +namespace axom +{ +namespace quest +{ +/*! + @brief Post-processed klee::Shape, with the geometry discretized and transformed + according to the Shape's operators. + + TODO: Move this class into internal namespace. +*/ +class DiscreteShape +{ +public: + + /// Refinement type. + using RefinementType = enum { RefinementUniformSegments, RefinementDynamic }; + + using Point3D = axom::primal::Point; + using TetType = axom::primal::Tetrahedron; + using OctType = axom::primal::Octahedron; + + static constexpr int DEFAULT_SAMPLES_PER_KNOT_SPAN {25}; + static constexpr double MINIMUM_PERCENT_ERROR {0.}; + static constexpr double MAXIMUM_PERCENT_ERROR {100.}; + static constexpr double DEFAULT_VERTEX_WELD_THRESHOLD {1e-9}; + + /*! + @brief Constructor. + + @param shape The Klee specifications for the shape. + @param prefixPath Path prefix for shape files specified + with a relative path. + @param parentGroup Group under which to put the discrete mesh. + If null, don't use sidre. + */ + DiscreteShape(const axom::klee::Shape& shape, + const std::string& prefixPath = {}, + axom::sidre::Group* parentGroup = nullptr); + + virtual ~DiscreteShape() { clearInternalData(); } + + //@{ + //! @name Functions to get and set shaping parameters + + void setPrefixPath(const std::string& prefixPath) + { + m_prefixPath = prefixPath; + } + + /*! + @brief Set the refinement type. + Refinement type is used for shaping with C2C contours. + */ + void setRefinementType(RefinementType refinementType) + { + m_refinementType = refinementType; + } + + void setSamplesPerKnotSpan(int nSamples); + void setVertexWeldThreshold(double threshold); + /*! + @brief Set the percentage error tolerance. + + If percent <= MINIMUM_PERCENT_ERROR, the refinement type + will change to DiscreteShape::RefinementUniformSegments. + */ + void setPercentError(double percent); + + //@} + +#if defined(AXOM_USE_MPI) + /** + @brief Set the MPI communicator used when reading C2C files. + */ + void setMPICommunicator(MPI_Comm comm) + { + m_comm = comm; + } +#endif + + /*! + Get the name of this shape. + \return the shape's name + */ + const axom::klee::Shape &getShape() const { return m_shape; } + + /*! + \brief Get the discrete mesh representation. + + If the discrete mesh isn't generated yet (for analytical shapes) + generate it. + */ + std::shared_ptr createMeshRepresentation(); + + //!@brief Get the discrete mesh representation. + std::shared_ptr getMeshRepresentation() const + { + return m_meshRep; + } + + /*! + \brief Get the revolved volume for volumes of revolution. + */ + double getRevolvedVolume() const { return m_revolvedVolume; } + +private: + const axom::klee::Shape& m_shape; + /*! + \brief Discrete mesh representation. + + This is either an internally generated mesh representing a + discretized analytical shape or a modifiable copy of m_geometry + (if the Geometry is specified as a discrete mesh). + */ + std::shared_ptr m_meshRep; + std::string m_prefixPath; + axom::sidre::Group* m_sidreGroup {nullptr}; + + RefinementType m_refinementType; + double m_percentError {MINIMUM_PERCENT_ERROR}; + int m_samplesPerKnotSpan {DEFAULT_SAMPLES_PER_KNOT_SPAN}; + double m_vertexWeldThreshold {DEFAULT_VERTEX_WELD_THRESHOLD}; + double m_revolvedVolume {0.0}; + +#if defined(AXOM_USE_MPI) + MPI_Comm m_comm {MPI_COMM_WORLD}; +#endif + + void applyTransforms(); + numerics::Matrix getTransforms() const; + + //! @brief Returns the full geometry path. + std::string resolvePath() const; + + /*! + @brief Set the parent group for this object to store data. + */ + void setParentGroup(axom::sidre::Group* parentGroup); + + void clearInternalData(); +}; + +} // namespace klee +} // namespace axom + +#endif diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 2c9d2fd841..41e8759e66 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -372,7 +372,7 @@ class IntersectionShaper : public Shaper */ double getApproximateRevolvedVolume() const { - return volume(m_surfaceMesh, m_level); + return volume(m_surfaceMesh.get(), m_level); } virtual void loadShape(const klee::Shape& shape) override @@ -384,9 +384,8 @@ class IntersectionShaper : public Shaper if(shape.getGeometry().getFormat() == "c2c") { SegmentMesh* newm = - filterMesh(dynamic_cast(m_surfaceMesh)); - delete m_surfaceMesh; - m_surfaceMesh = newm; + filterMesh(dynamic_cast(m_surfaceMesh.get())); + m_surfaceMesh.reset(newm); } } @@ -464,7 +463,7 @@ class IntersectionShaper : public Shaper num_degenerate.get())); // Dump tet mesh as a vtk mesh - axom::mint::write_vtk(m_surfaceMesh, "proe_tet.vtk"); + axom::mint::write_vtk(m_surfaceMesh.get(), "proe_tet.vtk"); } // end of verbose output for contour } @@ -621,7 +620,7 @@ class IntersectionShaper : public Shaper prepareTetCells(); } - else if(shapeFormat == "memory-blueprint") + else if(shapeFormat == "memory-blueprint" || shapeFormat == "sphere3D") { prepareTetCells(); } @@ -1430,9 +1429,7 @@ class IntersectionShaper : public Shaper AXOM_ANNOTATE_SCOPE("finalizeShapeQuery"); // Implementation here -- destroy BVH tree and other shape-based data structures - delete m_surfaceMesh; - - m_surfaceMesh = nullptr; + m_surfaceMesh.reset(); } //@} @@ -1496,7 +1493,7 @@ class IntersectionShaper : public Shaper const std::string shapeFormat = shape.getGeometry().getFormat(); // Testing separate workflow for Pro/E - if(shapeFormat == "proe" || shapeFormat == "memory-blueprint") + if(shapeFormat == "proe" || shapeFormat == "memory-blueprint" || shapeFormat == "sphere3D") { switch(m_execPolicy) { @@ -1824,7 +1821,7 @@ class IntersectionShaper : public Shaper { // If we are not refining dynamically, return. if(m_percentError <= MINIMUM_PERCENT_ERROR || - m_refinementType != RefinementDynamic) + m_refinementType != DiscreteShape::RefinementDynamic) { return; } @@ -1912,7 +1909,7 @@ class IntersectionShaper : public Shaper for(int level = m_level; level < MAX_LEVELS; level++) { // Compute the revolved volume of the surface mesh at level. - currentVol = volume(m_surfaceMesh, level); + currentVol = volume(m_surfaceMesh.get(), level); pct = 100. * (1. - currentVol / revolvedVolume); SLIC_INFO( @@ -1985,8 +1982,7 @@ class IntersectionShaper : public Shaper curvePercentError = ce; // Free the previous surface mesh. - delete m_surfaceMesh; - m_surfaceMesh = nullptr; + m_surfaceMesh.reset(); // Reload the shape using new curvePercentError. This will cause // a new m_surfaceMesh to be created. @@ -1999,9 +1995,8 @@ class IntersectionShaper : public Shaper // Filter the mesh, store in m_surfaceMesh. SegmentMesh* newm = - filterMesh(dynamic_cast(m_surfaceMesh)); - delete m_surfaceMesh; - m_surfaceMesh = newm; + filterMesh(dynamic_cast(m_surfaceMesh.get())); + m_surfaceMesh.reset(newm); } else { diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 9c4d6dffcb..fb66382fba 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -305,7 +305,7 @@ class InOutSampler GeometricBoundingBox m_bbox; mint::Mesh* m_surfaceMesh {nullptr}; InOutOctreeType* m_octree {nullptr}; -}; +}; // class InOutSampler } // end namespace shaping @@ -412,17 +412,15 @@ class SamplingShaper : public Shaper switch(shapeDimension) { case klee::Dimensions::Two: - m_inoutSampler2D = new shaping::InOutSampler<2>(shapeName, m_surfaceMesh); + m_inoutSampler2D = new shaping::InOutSampler<2>(shapeName, m_surfaceMesh.get()); m_inoutSampler2D->computeBounds(); m_inoutSampler2D->initSpatialIndex(this->m_vertexWeldThreshold); - m_surfaceMesh = m_inoutSampler2D->getSurfaceMesh(); break; case klee::Dimensions::Three: - m_inoutSampler3D = new shaping::InOutSampler<3>(shapeName, m_surfaceMesh); + m_inoutSampler3D = new shaping::InOutSampler<3>(shapeName, m_surfaceMesh.get()); m_inoutSampler3D->computeBounds(); m_inoutSampler3D->initSpatialIndex(this->m_vertexWeldThreshold); - m_surfaceMesh = m_inoutSampler3D->getSurfaceMesh(); break; default: @@ -446,7 +444,7 @@ class SamplingShaper : public Shaper "After welding, surface mesh has {} vertices and {} triangles.", nVerts, nCells)); - mint::write_vtk(m_surfaceMesh, + mint::write_vtk(m_surfaceMesh.get(), axom::fmt::format("meldedTriMesh_{}.vtk", shapeName)); } } @@ -584,8 +582,7 @@ class SamplingShaper : public Shaper delete m_inoutSampler3D; m_inoutSampler3D = nullptr; - delete m_surfaceMesh; - m_surfaceMesh = nullptr; + m_surfaceMesh.reset(); } //@} diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index b756e6627e..3a7268d0c2 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -7,7 +7,9 @@ #include "axom/config.hpp" #include "axom/core.hpp" +#include "axom/primal/operators/split.hpp" #include "axom/quest/interface/internal/QuestHelpers.hpp" +#include "axom/quest/DiscreteShape.hpp" #include "axom/fmt.hpp" @@ -21,6 +23,7 @@ namespace axom { namespace quest { +#if 1 namespace internal { /*! @@ -75,6 +78,7 @@ class AffineMatrixVisitor : public klee::GeometryOperatorVisitor }; } // end namespace internal +#endif // These were needed for linking - but why? They are constexpr. constexpr int Shaper::DEFAULT_SAMPLES_PER_KNOT_SPAN; @@ -130,7 +134,7 @@ void Shaper::setPercentError(double percent) percent)); if(percent <= MINIMUM_PERCENT_ERROR) { - m_refinementType = RefinementUniformSegments; + m_refinementType = DiscreteShape::RefinementUniformSegments; } m_percentError = clampVal(percent, MINIMUM_PERCENT_ERROR, MAXIMUM_PERCENT_ERROR); @@ -144,7 +148,8 @@ void Shaper::setRefinementType(Shaper::RefinementType t) bool Shaper::isValidFormat(const std::string& format) const { return (format == "stl" || format == "proe" || format == "c2c" || - format == "memory-blueprint" || format == "none"); + format == "memory-blueprint" || format == "sphere3D" || + format == "none"); } void Shaper::loadShape(const klee::Shape& shape) @@ -169,35 +174,31 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, "{:-^80}", axom::fmt::format(" Loading shape '{}' ", shape.getName()))); - const std::string& file_format = shape.getGeometry().getFormat(); + const axom::klee::Geometry& geometry = shape.getGeometry(); + const std::string& geometryFormat = geometry.getFormat(); SLIC_ASSERT_MSG( - this->isValidFormat(file_format), - axom::fmt::format("Shape has unsupported format: '{}", file_format)); - - const std::string& shapeFormat = shape.getGeometry().getFormat(); - - if (shapeFormat == "memory-blueprint") + this->isValidFormat(geometryFormat), + axom::fmt::format("Shape has unsupported format: '{}", geometryFormat)); + +#if 1 + // Code for discretizing shapes has been factored into DiscreteShape class. + DiscreteShape discreteShape(shape, m_shapeSet.getPath()); + discreteShape.setVertexWeldThreshold(m_vertexWeldThreshold); + discreteShape.setRefinementType(m_refinementType); + discreteShape.setPercentError(percentError); + m_surfaceMesh = discreteShape.createMeshRepresentation(); + revolvedVolume = discreteShape.getRevolvedVolume(); +#else + if (geometryFormat == "memory-blueprint" || geometryFormat == "sphere3D") { - // TODO: For readability, move most of this into a function. - // Put the in-memory geometry in m_surfaceMesh. - axom::sidre::Group* inputGroup = shape.getGeometry().getBlueprintMesh(); - axom::sidre::Group* rootGroup = inputGroup->getDataStore()->getRoot(); - - std::string modName = inputGroup->getName() + "_modified"; - while (rootGroup->hasGroup(modName)) { modName = modName + "-"; } - - axom::sidre::Group* modGroup = rootGroup->createGroup(modName); - int allocID = inputGroup->getDefaultAllocatorID(); - modGroup->deepCopyGroup(inputGroup, allocID); - - m_surfaceMesh = axom::mint::getMesh(modGroup->getGroup(inputGroup->getName()), - shape.getGeometry().getBlueprintTopology()); - - // Transform the coordinates of the linearized mesh. - applyTransforms(shape); - return; + // Code for discretizing shapes has been factored into DiscreteShape class. + DiscreteShape discreteShape(shape); + m_surfaceMesh = discreteShape.getMeshRepresentation(); } + // We handled all the non-file formats. The rest are file formats. + const std::string& file_format = geometryFormat; + if(!shape.getGeometry().hasGeometry()) { SLIC_DEBUG( @@ -219,7 +220,9 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, file_format == "stl", axom::fmt::format(" '{}' format requires .stl file type", file_format)); - quest::internal::read_stl_mesh(shapePath, m_surfaceMesh, m_comm); + axom::mint::Mesh* surfaceMesh = nullptr; + quest::internal::read_stl_mesh(shapePath, surfaceMesh, m_comm); + m_surfaceMesh.reset(surfaceMesh); // Transform the coordinates of the linearized mesh. applyTransforms(shape); } @@ -229,7 +232,9 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, file_format == "proe", axom::fmt::format(" '{}' format requires .proe file type", file_format)); - quest::internal::read_pro_e_mesh(shapePath, m_surfaceMesh, m_comm); + axom::mint::Mesh* surfaceMesh = nullptr; + quest::internal::read_pro_e_mesh(shapePath, surfaceMesh, m_comm); + m_surfaceMesh.reset(surfaceMesh); } #ifdef AXOM_USE_C2C else if(endsWith(shapePath, ".contour")) @@ -244,14 +249,15 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, // Pass in the transform so any transformations can figure into // computing the revolved volume. - if(m_refinementType == RefinementDynamic && + axom::mint::Mesh* surfaceMesh = nullptr; + if(m_refinementType == DiscreteShape::RefinementDynamic && percentError > MINIMUM_PERCENT_ERROR) { quest::internal::read_c2c_mesh_non_uniform(shapePath, transform, percentError, m_vertexWeldThreshold, - m_surfaceMesh, + surfaceMesh, revolvedVolume, // output arg m_comm); } @@ -261,10 +267,11 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, transform, m_samplesPerKnotSpan, m_vertexWeldThreshold, - m_surfaceMesh, + surfaceMesh, revolvedVolume, // output arg m_comm); } + m_surfaceMesh.reset(surfaceMesh); // Transform the coordinates of the linearized mesh. applyTransforms(transform); @@ -278,8 +285,10 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, shapePath, file_format)); } +#endif } +#if 1 numerics::Matrix Shaper::getTransforms(const klee::Shape& shape) const { const auto identity4x4 = numerics::Matrix::identity(4); @@ -360,6 +369,7 @@ void Shaper::applyTransforms(const numerics::Matrix& transformation) } } } +#endif // ---------------------------------------------------------------------------- diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index 85f16fc967..a05802f6e4 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -23,6 +23,7 @@ #include "axom/sidre.hpp" #include "axom/klee.hpp" #include "axom/mint.hpp" +#include "axom/quest/DiscreteShape.hpp" #include "axom/quest/interface/internal/mpicomm_wrapper.hpp" @@ -48,7 +49,7 @@ class Shaper static constexpr double DEFAULT_VERTEX_WELD_THRESHOLD {1e-9}; /// Refinement type. - using RefinementType = enum { RefinementUniformSegments, RefinementDynamic }; + using RefinementType = DiscreteShape::RefinementType; //@{ //! @name Functions to get and set shaping parameters @@ -64,7 +65,7 @@ class Shaper bool isVerbose() const { return m_verboseOutput; } sidre::MFEMSidreDataCollection* getDC() { return m_dc; } - mint::Mesh* getSurfaceMesh() const { return m_surfaceMesh; } + mint::Mesh* getSurfaceMesh() const { return m_surfaceMesh.get(); } /*! * \brief Predicate to determine if the specified format is valid @@ -154,11 +155,11 @@ class Shaper const klee::ShapeSet& m_shapeSet; sidre::MFEMSidreDataCollection* m_dc; - mint::Mesh* m_surfaceMesh {nullptr}; + std::shared_ptr m_surfaceMesh; int m_samplesPerKnotSpan {DEFAULT_SAMPLES_PER_KNOT_SPAN}; double m_percentError {MINIMUM_PERCENT_ERROR}; - RefinementType m_refinementType {RefinementUniformSegments}; + RefinementType m_refinementType {DiscreteShape::RefinementUniformSegments}; double m_vertexWeldThreshold {DEFAULT_VERTEX_WELD_THRESHOLD}; bool m_verboseOutput {false}; diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 6cb741bc1a..9cb5b2e05d 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -252,14 +252,17 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) # 3D shaping tests. No 2D shaping in this feature, at least for now. set(_nranks 1) - set(_testname quest_shape_in_memory_3d) - axom_add_test( + set(_testshapes "tet" "sphere") + foreach(_testshape ${_testshapes}) + set(_testname "quest_shape_in_memory_${_testshape}") + axom_add_test( NAME ${_testname} - COMMAND quest_shape_in_memory_ex - --method intersection - --scale 0.75 0.75 0.75 - inline_mesh --min -1 -1 -1 --max 5 5 5 --res 20 20 20 -d 3 + COMMAND quest_shape_in_memory_ex + --method intersection --refinements 2 + --scale 0.75 0.75 0.75 + inline_mesh --min -1 -1 -1 --max 5 5 5 --res 20 20 20 -d 3 NUM_MPI_TASKS ${_nranks}) + endforeach() endif() endif() diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index e3eed61b53..1a5388a9cd 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -23,6 +23,8 @@ #include "axom/fmt.hpp" #include "axom/CLI11.hpp" +#include "conduit_relay_io_blueprint.hpp" + // NOTE: The shaping driver requires Axom to be configured with mfem as well as // the AXOM_ENABLE_MFEM_SIDRE_DATACOLLECTION CMake option #ifndef AXOM_USE_MFEM @@ -71,6 +73,9 @@ struct Input std::string meshFile; std::string outputFile; + std::vector sphereCenter {1.0, 1.0, 1.0}; + double sphereRadius {1.0}; + // Shape transformation parameters std::vector scaleFactors; @@ -80,8 +85,10 @@ struct Input std::vector boxResolution; int boxDim {-1}; - std::string shapeFile; - // klee::ShapeSet shapeSet; + // The shape to run. + std::string shape {"tet"}; + // The shapes this example is set up to run. + const std::set availableShapes {"tet", "sphere"}; ShapingMethod shapingMethod {ShapingMethod::Sampling}; RuntimePolicy policy {RuntimePolicy::seq}; @@ -222,6 +229,10 @@ struct Input ->transform( axom::CLI::CheckedTransformer(methodMap, axom::CLI::ignore_case)); + app.add_option("-s,--shape", shape) + ->description("The shape to run") + ->check(axom::CLI::IsMember(availableShapes)); + #ifdef AXOM_USE_CALIPER app.add_option("--caliper", annotationMode) ->description( @@ -231,6 +242,14 @@ struct Input ->check(axom::utilities::ValidCaliperMode); #endif + app.add_option("--sphereCenter", sphereCenter) + ->description("Center of sphere (x,y[,z])") + ->expected(2, 3); + + app.add_option("--sphereRadius", sphereRadius) + ->description("Radius of sphere") + ->expected(2, 3); + app.add_option("--scale", scaleFactors) ->description("Scale factor to apply to shape (x,y[,z])") ->expected(2, 3) @@ -357,7 +376,7 @@ Input params; * \note In MPI-based configurations, this is a collective call, but * only prints on rank 0 */ -void printMeshInfo(mfem::Mesh* mesh, const std::string& prefixMessage = "") +void printMfemMeshInfo(mfem::Mesh* mesh, const std::string& prefixMessage = "") { namespace primal = axom::primal; @@ -461,7 +480,7 @@ axom::klee::ShapeSet create2DShapeSet(sidre::DataStore& ds) AXOM_UNUSED_VAR(meshGroup); // variable is only referenced in debug configs const std::string topo = "mesh"; const std::string coordset = "coords"; - axom::klee::Geometry::SimplexMesh triangleMesh( + axom::mint::UnstructuredMesh triangleMesh( 2, axom::mint::CellType::TRIANGLE, meshGroup, topo, coordset ); double lll = 2.0; @@ -492,14 +511,40 @@ axom::klee::ShapeSet create2DShapeSet(sidre::DataStore& ds) return shapeSet; } -axom::klee::ShapeSet create3DShapeSet(sidre::DataStore& ds) +axom::klee::Shape createShape_Sphere() +{ + axom::primal::Sphere sphere{params.sphereCenter.data(), params.sphereRadius}; + + axom::klee::TransformableGeometryProperties prop{ + axom::klee::Dimensions::Three, + axom::klee::LengthUnit::unspecified}; + + SLIC_ASSERT( params.scaleFactors.empty() || params.scaleFactors.size() == 3 ); + std::shared_ptr scaleOp; + if (!params.scaleFactors.empty()) + { + scaleOp = + std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + prop); + } + + const axom::IndexType generationCount = params.refinementLevel; + axom::klee::Geometry sphereGeometry(prop, sphere, generationCount, scaleOp); + axom::klee::Shape sphereShape( "sphere", "AU", {}, {}, sphereGeometry ); + + return sphereShape; +} + +axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) { // Shape a single tetrahedron. sidre::Group *meshGroup = ds.getRoot()->createGroup("tetMesh"); AXOM_UNUSED_VAR(meshGroup); // variable is only referenced in debug configs const std::string topo = "mesh"; const std::string coordset = "coords"; - axom::klee::Geometry::SimplexMesh tetMesh( + axom::mint::UnstructuredMesh tetMesh( 3, axom::mint::CellType::TET, meshGroup, topo, coordset ); double lll = 4.0; @@ -532,21 +577,23 @@ axom::klee::ShapeSet create3DShapeSet(sidre::DataStore& ds) prop); } - axom::klee::Geometry tetGeom(prop, tetMesh.getSidreGroup(), topo, scaleOp); - axom::klee::Geometry tetMeshGeometry( prop, tetMesh.getSidreGroup(), topo, {scaleOp} ); - std::vector shapes; axom::klee::Shape tetShape( "tet", "AL", {}, {}, tetMeshGeometry ); - shapes.push_back(tetShape); + return tetShape; +} + +//!@brief Create a ShapeSet with a single shape. +axom::klee::ShapeSet createShapeSet(const axom::klee::Shape& shape) +{ axom::klee::ShapeSet shapeSet; - shapeSet.setShapes(shapes); + shapeSet.setShapes(std::vector{shape}); shapeSet.setDimensions(axom::klee::Dimensions::Three); return shapeSet; } -double volumeOfTetMesh(const axom::klee::Geometry::SimplexMesh& tetMesh) +double volumeOfTetMesh(const axom::mint::UnstructuredMesh& tetMesh) { using TetType = axom::primal::Tetrahedron; {std::ofstream os("tets.js"); tetMesh.getSidreGroup()->print(os);} @@ -750,10 +797,43 @@ int main(int argc, char** argv) shapeSet = create2DShapeSet(ds); break; case 3: - shapeSet = create3DShapeSet(ds); + if (params.shape == "tet") + { + shapeSet = createShapeSet( createShape_TetMesh(ds) ); + } + else if (params.shape == "sphere") + { + shapeSet = createShapeSet( createShape_Sphere() ); + } break; } + // Save the discrete shapes for viz and testing. + auto* shapeMeshGroup = ds.getRoot()->createGroup("shapeMeshGroup"); + std::vector> discreteShapeMeshes; + for(const auto& shape : shapeSet.getShapes()) + { + axom::quest::DiscreteShape dShape(shape, "", shapeMeshGroup); + auto dMesh = + std::dynamic_pointer_cast>( + dShape.createMeshRepresentation()); + SLIC_INFO(axom::fmt::format( + "{:-^80}", + axom::fmt::format("Shape '{}' discrete geometry has {} cells", + shape.getName(), + dMesh->getNumberOfCells()))); + + discreteShapeMeshes.push_back(dMesh); + + if (!params.outputFile.empty()) + { + std::string shapeFileName = params.outputFile + "." + shape.getName(); + conduit::Node tmpNode, info; + dMesh->getSidreGroup()->createNativeLayout(tmpNode); + conduit::relay::io::blueprint::save_mesh(tmpNode, shapeFileName, "hdf5"); + } + } + const klee::Dimensions shapeDim = shapeSet.getDimensions(); // Apply error checking @@ -791,7 +871,7 @@ int main(int argc, char** argv) shapingDC.SetMesh(shapingMesh); } AXOM_ANNOTATE_END("load mesh"); - printMeshInfo(shapingDC.GetMesh(), "After loading"); + printMfemMeshInfo(shapingDC.GetMesh(), "After loading"); const axom::IndexType cellCount = shapingMesh->GetNE(); @@ -821,7 +901,7 @@ int main(int argc, char** argv) if(params.percentError > 0.) { shaper->setPercentError(params.percentError); - shaper->setRefinementType(quest::Shaper::RefinementDynamic); + shaper->setRefinementType(quest::DiscreteShape::RefinementDynamic); } // Associate any fields that begin with "vol_frac" with "material" so when @@ -922,15 +1002,12 @@ int main(int argc, char** argv) slic::flushStreams(); // Query the mesh against this shape -// { std::ofstream tmpos{"mfem.before.js"}; shaper->getDC()->GetBPGroup()->print(tmpos); } shaper->runShapeQuery(shape); slic::flushStreams(); -// { std::ofstream tmpos{"mfem.afterquery.js"}; shaper->getDC()->GetBPGroup()->print(tmpos); } // Apply the replacement rules for this shape against the existing materials shaper->applyReplacementRules(shape); slic::flushStreams(); -// { std::ofstream tmpos{"mfem.afterreplace.js"}; shaper->getDC()->GetBPGroup()->print(tmpos); } // Finalize data structures associated with this shape and spatial index shaper->finalizeShapeQuery(); @@ -938,12 +1015,6 @@ int main(int argc, char** argv) } AXOM_ANNOTATE_END("shaping"); -{ -axom::klee::Geometry::SimplexMesh shapeMesh(shapeSet.getShapes()[0].getGeometry().getBlueprintMesh(), - shapeSet.getShapes()[0].getGeometry().getBlueprintTopology()); -double vol = volumeOfTetMesh(shapeMesh); -std::cout<< "Vol of tet mesh is " << vol << std::endl; -} //--------------------------------------------------------------------------- // After shaping in all shapes, generate/adjust the material volume fractions //--------------------------------------------------------------------------- @@ -1041,14 +1112,25 @@ std::cout<< "Vol of tet mesh is " << vol << std::endl; //--------------------------------------------------------------------------- // Correctness test: shape volume in shapingMesh should match volume of the shape mesh. //--------------------------------------------------------------------------- + auto* meshVerificationGroup = ds.getRoot()->createGroup("meshVerification"); for(const auto& shape : shapeSet.getShapes()) { - axom::klee::Geometry::SimplexMesh shapeMesh(shape.getGeometry().getBlueprintMesh(), - shape.getGeometry().getBlueprintTopology()); - double shapeMeshVol = volumeOfTetMesh(shapeMesh); - for ( auto s : params.scaleFactors ) + axom::quest::DiscreteShape dShape(shape, "", meshVerificationGroup); + auto shapeMesh = + std::dynamic_pointer_cast>( + dShape.createMeshRepresentation()); + double shapeMeshVol = volumeOfTetMesh(*shapeMesh); + SLIC_INFO(axom::fmt::format( + "{:-^80}", + axom::fmt::format("Shape '{}' discrete geometry has {} cells", + shape.getName(), + shapeMesh->getNumberOfCells()))); + if (!params.outputFile.empty()) { - shapeMeshVol *= s; + std::string shapeFileName = params.outputFile + "." + shape.getName(); + conduit::Node tmpNode, info; + shapeMesh->getSidreGroup()->createNativeLayout(tmpNode); + conduit::relay::io::blueprint::save_mesh(tmpNode, shapeFileName, "hdf5"); } const std::string& materialName = shape.getMaterial(); @@ -1060,14 +1142,16 @@ std::cout<< "Vol of tet mesh is " << vol << std::endl; SLIC_INFO(axom::fmt::format( "{:-^80}", - axom::fmt::format("Material '{}' in shape '{}' has volume {}, diff of {}, {}.", + axom::fmt::format("Material '{}' in shape '{}' has volume {} vs {}, diff of {}, {}.", materialName, shape.getName(), shapeVol, + shapeMeshVol, diff, (err ? "ERROR" : "OK") ))); } slic::flushStreams(); + ds.getRoot()->destroyGroupAndData("meshVerification"); //--------------------------------------------------------------------------- // Save meshes and fields diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 77badcd544..31abd26041 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -578,7 +578,7 @@ int main(int argc, char** argv) if(params.percentError > 0.) { shaper->setPercentError(params.percentError); - shaper->setRefinementType(quest::Shaper::RefinementDynamic); + shaper->setRefinementType(quest::DiscreteShape::RefinementDynamic); } // Associate any fields that begin with "vol_frac" with "material" so when diff --git a/src/axom/quest/tests/quest_intersection_shaper.cpp b/src/axom/quest/tests/quest_intersection_shaper.cpp index 10d7373331..09be375050 100644 --- a/src/axom/quest/tests/quest_intersection_shaper.cpp +++ b/src/axom/quest/tests/quest_intersection_shaper.cpp @@ -462,7 +462,7 @@ void IntersectionWithErrorTolerances(const std::string &filebase, quest::IntersectionShaper shaper(shapeSet, &dc); shaper.setLevel(refinementLevel); shaper.setPercentError(targetPercentError); - shaper.setRefinementType(quest::Shaper::RefinementDynamic); + shaper.setRefinementType(quest::DiscreteShape::RefinementDynamic); shaper.setExecPolicy(policy); // Borrowed from shaping_driver (there should just be one shape) From 758b7abe8b9254fac1ae19c68efb3f2b3c36455a Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 6 Aug 2024 22:11:42 -0700 Subject: [PATCH 07/43] Remove obsolete code from Shaper. --- src/axom/quest/Shaper.cpp | 252 ++------------------------------------ 1 file changed, 7 insertions(+), 245 deletions(-) diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 3a7268d0c2..cbce6c7963 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -23,62 +23,6 @@ namespace axom { namespace quest { -#if 1 -namespace internal -{ -/*! - * \brief Implementation of a GeometryOperatorVisitor for processing klee shape operators - * - * This class extracts the matrix form of supported operators and marks the operator as unvalid otherwise - * To use, check the \a isValid() function after visiting and then call the \a getMatrix() function. - */ -class AffineMatrixVisitor : public klee::GeometryOperatorVisitor -{ -public: - AffineMatrixVisitor() : m_matrix(4, 4) { } - - void visit(const klee::Translation& translation) override - { - m_matrix = translation.toMatrix(); - m_isValid = true; - } - void visit(const klee::Rotation& rotation) override - { - m_matrix = rotation.toMatrix(); - m_isValid = true; - } - void visit(const klee::Scale& scale) override - { - m_matrix = scale.toMatrix(); - m_isValid = true; - } - void visit(const klee::UnitConverter& converter) override - { - m_matrix = converter.toMatrix(); - m_isValid = true; - } - - void visit(const klee::CompositeOperator&) override - { - SLIC_WARNING("CompositeOperator not supported for Shaper query"); - m_isValid = false; - } - void visit(const klee::SliceOperator&) override - { - SLIC_WARNING("SliceOperator not yet supported for Shaper query"); - m_isValid = false; - } - - const numerics::Matrix& getMatrix() const { return m_matrix; } - bool isValid() const { return m_isValid; } - -private: - bool m_isValid {false}; - numerics::Matrix m_matrix; -}; - -} // end namespace internal -#endif // These were needed for linking - but why? They are constexpr. constexpr int Shaper::DEFAULT_SAMPLES_PER_KNOT_SPAN; @@ -180,197 +124,15 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, this->isValidFormat(geometryFormat), axom::fmt::format("Shape has unsupported format: '{}", geometryFormat)); -#if 1 - // Code for discretizing shapes has been factored into DiscreteShape class. - DiscreteShape discreteShape(shape, m_shapeSet.getPath()); - discreteShape.setVertexWeldThreshold(m_vertexWeldThreshold); - discreteShape.setRefinementType(m_refinementType); - discreteShape.setPercentError(percentError); - m_surfaceMesh = discreteShape.createMeshRepresentation(); - revolvedVolume = discreteShape.getRevolvedVolume(); -#else - if (geometryFormat == "memory-blueprint" || geometryFormat == "sphere3D") - { - // Code for discretizing shapes has been factored into DiscreteShape class. - DiscreteShape discreteShape(shape); - m_surfaceMesh = discreteShape.getMeshRepresentation(); - } - - // We handled all the non-file formats. The rest are file formats. - const std::string& file_format = geometryFormat; - - if(!shape.getGeometry().hasGeometry()) - { - SLIC_DEBUG( - axom::fmt::format("Current shape '{}' of material '{}' has no geometry", - shape.getName(), - shape.getMaterial())); - return; - } - - std::string shapePath = m_shapeSet.resolvePath(shape.getGeometry().getPath()); - SLIC_INFO("Reading file: " << shapePath << "..."); - - // Initialize revolved volume. - revolvedVolume = 0.; - - if(endsWith(shapePath, ".stl")) - { - SLIC_ASSERT_MSG( - file_format == "stl", - axom::fmt::format(" '{}' format requires .stl file type", file_format)); - - axom::mint::Mesh* surfaceMesh = nullptr; - quest::internal::read_stl_mesh(shapePath, surfaceMesh, m_comm); - m_surfaceMesh.reset(surfaceMesh); - // Transform the coordinates of the linearized mesh. - applyTransforms(shape); - } - else if(endsWith(shapePath, ".proe")) - { - SLIC_ASSERT_MSG( - file_format == "proe", - axom::fmt::format(" '{}' format requires .proe file type", file_format)); - - axom::mint::Mesh* surfaceMesh = nullptr; - quest::internal::read_pro_e_mesh(shapePath, surfaceMesh, m_comm); - m_surfaceMesh.reset(surfaceMesh); - } -#ifdef AXOM_USE_C2C - else if(endsWith(shapePath, ".contour")) - { - SLIC_ASSERT_MSG( - file_format == "c2c", - axom::fmt::format(" '{}' format requires .contour file type", file_format)); - - // Get the transforms that are being applied to the mesh. Get them - // as a single concatenated matrix. - auto transform = getTransforms(shape); - - // Pass in the transform so any transformations can figure into - // computing the revolved volume. - axom::mint::Mesh* surfaceMesh = nullptr; - if(m_refinementType == DiscreteShape::RefinementDynamic && - percentError > MINIMUM_PERCENT_ERROR) - { - quest::internal::read_c2c_mesh_non_uniform(shapePath, - transform, - percentError, - m_vertexWeldThreshold, - surfaceMesh, - revolvedVolume, // output arg - m_comm); - } - else - { - quest::internal::read_c2c_mesh_uniform(shapePath, - transform, - m_samplesPerKnotSpan, - m_vertexWeldThreshold, - surfaceMesh, - revolvedVolume, // output arg - m_comm); - } - m_surfaceMesh.reset(surfaceMesh); - - // Transform the coordinates of the linearized mesh. - applyTransforms(transform); - } -#endif - else - { - SLIC_ERROR( - axom::fmt::format("Unsupported filetype for this Axom configuration. " - "Provided file was '{}', with format '{}'", - shapePath, - file_format)); - } -#endif -} - -#if 1 -numerics::Matrix Shaper::getTransforms(const klee::Shape& shape) const -{ - const auto identity4x4 = numerics::Matrix::identity(4); - numerics::Matrix transformation(identity4x4); - auto& geometryOperator = shape.getGeometry().getGeometryOperator(); - if (geometryOperator) { - auto composite = - std::dynamic_pointer_cast(geometryOperator); - if(composite) - { - // Concatenate the transformations - - // Why don't we multiply the matrices in CompositeOperator::addOperator()? - // Why keep the matrices factored and multiply them here repeatedly? - // Combining them would also avoid this if-else logic. BTNG - for(auto op : composite->getOperators()) - { - // Use visitor pattern to extract the affine matrix from supported operators - internal::AffineMatrixVisitor visitor; - op->accept(visitor); - if(!visitor.isValid()) - { - continue; - } - const auto& matrix = visitor.getMatrix(); - numerics::Matrix res(identity4x4); - numerics::matrix_multiply(matrix, transformation, res); - transformation = res; - } - } - else - { - internal::AffineMatrixVisitor visitor; - geometryOperator->accept(visitor); - if (visitor.isValid()) - { - transformation = visitor.getMatrix(); - } - } - } - return transformation; -} - -void Shaper::applyTransforms(const klee::Shape& shape) -{ - // Concatenate the transformations - numerics::Matrix transformation = getTransforms(shape); - // Transform the surface mesh coordinates. - applyTransforms(transformation); + // Code for discretizing shapes has been factored into DiscreteShape class. + DiscreteShape discreteShape(shape, m_shapeSet.getPath()); + discreteShape.setVertexWeldThreshold(m_vertexWeldThreshold); + discreteShape.setRefinementType(m_refinementType); + discreteShape.setPercentError(percentError); + m_surfaceMesh = discreteShape.createMeshRepresentation(); + revolvedVolume = discreteShape.getRevolvedVolume(); } -void Shaper::applyTransforms(const numerics::Matrix& transformation) -{ - AXOM_ANNOTATE_SCOPE("applyTransforms"); - - // Apply transformation to coordinates of each vertex in mesh - if(!transformation.isIdentity()) - { - const int spaceDim = m_surfaceMesh->getDimension(); - const int numSurfaceVertices = m_surfaceMesh->getNumberOfNodes(); - double* x = m_surfaceMesh->getCoordinateArray(mint::X_COORDINATE); - double* y = m_surfaceMesh->getCoordinateArray(mint::Y_COORDINATE); - double* z = spaceDim > 2 - ? m_surfaceMesh->getCoordinateArray(mint::Z_COORDINATE) - : nullptr; - - double xformed[4]; - for(int i = 0; i < numSurfaceVertices; ++i) - { - double coords[4] = {x[i], y[i], (z == nullptr ? 0. : z[i]), 1.}; - numerics::matrix_vector_multiply(transformation, coords, xformed); - x[i] = xformed[0]; - y[i] = xformed[1]; - if(z != nullptr) - { - z[i] = xformed[2]; - } - } - } -} -#endif - // ---------------------------------------------------------------------------- int Shaper::getRank() const From 711b29a9bc79bd6e0036961cf74421cf01d80434 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 7 Aug 2024 17:00:22 -0700 Subject: [PATCH 08/43] Some name changes to fit accepted terminology. --- src/axom/klee/Geometry.cpp | 8 +++---- src/axom/klee/Geometry.hpp | 12 +++++----- src/axom/quest/DiscreteShape.cpp | 24 +++++++++---------- src/axom/quest/DiscreteShape.hpp | 15 +++++++++--- src/axom/quest/Shaper.cpp | 2 -- .../quest/examples/quest_shape_in_memory.cpp | 4 ++-- 6 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index 844eb36fca..6f0feae646 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -26,7 +26,7 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, : m_startProperties(startProperties) , m_format(std::move(format)) , m_path(std::move(path)) - , m_generationCount(0) + , m_levelOfRefinement(0) , m_operator(std::move(operator_)) { } @@ -39,14 +39,14 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, , m_path() , m_meshGroup(meshGroup) , m_topology(topology) - , m_generationCount(0) + , m_levelOfRefinement(0) , m_operator(std::move(operator_)) { } Geometry::Geometry(const TransformableGeometryProperties &startProperties, const axom::primal::Sphere& sphere, - axom::IndexType generationCount, + axom::IndexType levelOfRefinement, std::shared_ptr operator_) : m_startProperties(startProperties) , m_format("sphere3D") @@ -54,7 +54,7 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, , m_meshGroup(nullptr) , m_topology() , m_sphere(sphere) - , m_generationCount(generationCount) + , m_levelOfRefinement(levelOfRefinement) , m_operator(std::move(operator_)) { } diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index e284452fef..4b6d28fae7 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -93,7 +93,7 @@ class Geometry * \param startProperties the transformable properties before any * operators are applied * \param sphere Analytical sphere specifications - * \param generationCount Number of generations to use for + * \param levelOfRefinement Number of refinement levels to use for * discretizing the sphere. * \param operator_ a possibly null operator to apply to the geometry. * @@ -101,7 +101,7 @@ class Geometry */ Geometry(const TransformableGeometryProperties &startProperties, const axom::primal::Sphere& sphere, - axom::IndexType generationCount, + axom::IndexType levelOfRefinement, std::shared_ptr operator_); /** @@ -188,9 +188,9 @@ class Geometry This number is unused for geometries that are specified in discrete form. */ - axom::IndexType getGenerationCount() const + axom::IndexType getLevelOfRefinement() const { - return m_generationCount; + return m_levelOfRefinement; } /** @@ -231,9 +231,9 @@ class Geometry axom::Array m_discreteFunction; #endif - //!@brief Generations of refinement for discretizing analytical shapes + //!@brief Level of refinement for discretizing analytical shapes // and surfaces of revolutions. - axom::IndexType m_generationCount = 0; + axom::IndexType m_levelOfRefinement = 0; std::shared_ptr m_operator; }; diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 6366fd9195..c22a447f47 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -89,17 +89,6 @@ DiscreteShape::DiscreteShape(const axom::klee::Shape &shape, setParentGroup(parentGroup); } -void DiscreteShape::setParentGroup(axom::sidre::Group* parentGroup) -{ - if (parentGroup) - { - std::ostringstream os; - os << this; - std::string myGroupName = os.str(); - m_sidreGroup = parentGroup->createGroup(myGroupName); - } -} - void DiscreteShape::clearInternalData() { m_meshRep.reset(); @@ -152,7 +141,7 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() using TetType = axom::primal::Tetrahedron; axom::Array octs; int octCount = 0; - axom::quest::discretize(sphere, geometry.getGenerationCount(), octs, octCount); + axom::quest::discretize(sphere, geometry.getLevelOfRefinement(), octs, octCount); constexpr int TETS_PER_OCT = 8; constexpr int NODES_PER_TET = 4; @@ -441,5 +430,16 @@ std::string DiscreteShape::resolvePath() const return utilities::filesystem::joinPath(dir, geomPath); } +void DiscreteShape::setParentGroup(axom::sidre::Group* parentGroup) +{ + if (parentGroup) + { + std::ostringstream os; + os << this; + std::string myGroupName = os.str(); + m_sidreGroup = parentGroup->createGroup(myGroupName); + } +} + } // namespace klee } // namespace axom diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index 78f6f4ffdf..3c4386668e 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -123,22 +123,31 @@ class DiscreteShape private: const axom::klee::Shape& m_shape; + /*! \brief Discrete mesh representation. This is either an internally generated mesh representing a - discretized analytical shape or a modifiable copy of m_geometry - (if the Geometry is specified as a discrete mesh). + discretized analytical shape or a modifiable copy of the + discrete input geometry for geometries specified as a + discrete mesh. */ std::shared_ptr m_meshRep; - std::string m_prefixPath; + + //!@brief Sidre store for m_meshRep. axom::sidre::Group* m_sidreGroup {nullptr}; + //!@brief Prefix for disc files with relative path. + std::string m_prefixPath; + + //@{ + //!@name Various parameters for discretization. RefinementType m_refinementType; double m_percentError {MINIMUM_PERCENT_ERROR}; int m_samplesPerKnotSpan {DEFAULT_SAMPLES_PER_KNOT_SPAN}; double m_vertexWeldThreshold {DEFAULT_VERTEX_WELD_THRESHOLD}; double m_revolvedVolume {0.0}; + //@} #if defined(AXOM_USE_MPI) MPI_Comm m_comm {MPI_COMM_WORLD}; diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index cbce6c7963..faf22577e4 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -109,8 +109,6 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, double percentError, double& revolvedVolume) { - using axom::utilities::string::endsWith; - internal::ScopedLogLevelChanger logLevelChanger( this->isVerbose() ? slic::message::Debug : slic::message::Warning); diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 1a5388a9cd..55035bb2a4 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -530,8 +530,8 @@ axom::klee::Shape createShape_Sphere() prop); } - const axom::IndexType generationCount = params.refinementLevel; - axom::klee::Geometry sphereGeometry(prop, sphere, generationCount, scaleOp); + const axom::IndexType levelOfRefinement = params.refinementLevel; + axom::klee::Geometry sphereGeometry(prop, sphere, levelOfRefinement, scaleOp); axom::klee::Shape sphereShape( "sphere", "AU", {}, {}, sphereGeometry ); return sphereShape; From 63358e9505c1a5a2c40ece10ec093df0f975a6b3 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Thu, 8 Aug 2024 16:00:54 -0700 Subject: [PATCH 09/43] Add SOR, cylinder and cone to in-memory shaping. --- src/axom/klee/Geometry.cpp | 22 +++- src/axom/klee/Geometry.hpp | 72 ++++++++--- src/axom/quest/DiscreteShape.cpp | 64 +++++++++- src/axom/quest/Discretize.hpp | 2 +- src/axom/quest/IntersectionShaper.hpp | 23 ++-- src/axom/quest/Shaper.cpp | 2 +- src/axom/quest/detail/Discretize_detail.hpp | 2 +- src/axom/quest/examples/CMakeLists.txt | 5 +- .../quest/examples/quest_shape_in_memory.cpp | 116 ++++++++++++++++-- 9 files changed, 258 insertions(+), 50 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index 6f0feae646..d3777ea2b9 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -45,7 +45,7 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, } Geometry::Geometry(const TransformableGeometryProperties &startProperties, - const axom::primal::Sphere& sphere, + const Sphere3D& sphere, axom::IndexType levelOfRefinement, std::shared_ptr operator_) : m_startProperties(startProperties) @@ -53,8 +53,28 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, , m_path() , m_meshGroup(nullptr) , m_topology() + , m_levelOfRefinement(levelOfRefinement) , m_sphere(sphere) + , m_operator(std::move(operator_)) +{ +} + +Geometry::Geometry(const TransformableGeometryProperties &startProperties, + const axom::Array& discreteFunction, + const Point3D& vorBase, + const Vector3D& vorDirection, + axom::IndexType levelOfRefinement, + std::shared_ptr operator_) + : m_startProperties(startProperties) + , m_format("vor3D") + , m_path() + , m_meshGroup(nullptr) + , m_topology() , m_levelOfRefinement(levelOfRefinement) + , m_sphere() + , m_discreteFunction(discreteFunction) + , m_vorBase(vorBase) + , m_vorDirection(vorDirection) , m_operator(std::move(operator_)) { } diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 4b6d28fae7..da84fefe3e 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -56,8 +56,12 @@ inline bool operator!=(const TransformableGeometryProperties &lhs, class Geometry { public: + using Point3D = axom::primal::Point; + using Vector3D = axom::primal::Vector; + using Sphere3D = axom::primal::Sphere; + /** - * Create a new Geometry object based on a file representation. + * Create a Geometry object based on a file representation. * * \param startProperties the transformable properties before any * operators are applied @@ -71,7 +75,7 @@ class Geometry std::shared_ptr operator_); /** - * Create a new Geometry object based on a blueprint tetrahedral mesh. + * Create a Geometry object based on a blueprint tetrahedral mesh. * * \param startProperties the transformable properties before any * operators are applied @@ -88,7 +92,7 @@ class Geometry std::shared_ptr operator_); /** - * Create a new sphere Geometry object. + * Create a sphere Geometry object. * * \param startProperties the transformable properties before any * operators are applied @@ -104,6 +108,33 @@ class Geometry axom::IndexType levelOfRefinement, std::shared_ptr operator_); + /** + * Create a volume-of-revolution (VOR) Geometry object. + * + * \param startProperties the transformable properties before any + * operators are applied + * \param discreteFunction Discrete function describing the surface + * of revolution. + * \param vorBase Coordinates of the base of the VOR. + * \param vorDirection VOR axis, in the direction of increasing z. + * \param levelOfRefinement Number of refinement levels to use for + * discretizing the sphere. + * \param operator_ a possibly null operator to apply to the geometry. + * + * The \c discreteFunction should be an Nx2 array, interpreted as + * (z,r) pairs, where z is the axial distance and r is the radius. + * The \c vorBase coordinates corresponds to z=0. + * \c vorAxis should point in the direction of increasing z. + * + * \internal TODO: Is this the simplex requirement overly restrictive? + */ + Geometry(const TransformableGeometryProperties &startProperties, + const axom::Array& discreteFunction, + const Point3D& vorBase, + const Vector3D& vorDirection, + axom::IndexType levelOfRefinement, + std::shared_ptr operator_); + /** * Get the format in which the geometry is specified. * @@ -111,9 +142,8 @@ class Geometry * - "c2c" = C2C file * - "proe" = ProE file * - "memory-blueprint" = Blueprint tetrahedral mesh in memory - * - "memory-xy" = discretized 2D, non-negative function as an - * array of points in memory * - "sphere3D" = 3D sphere, as \c primal::Sphere + * - "vor3D" = 3D volume of revolution. * - "cone3D" = 3D cone, as \c primal::Cone * "cylinder3D" = 3D cylinder, as \c primal::Cylinder * @@ -202,6 +232,14 @@ class Geometry return m_sphere; } + /** + @brief Get the discrete function. + */ + axom::ArrayView getDiscreteFunction() const + { + return m_discreteFunction.view(); + } + private: TransformableGeometryProperties m_startProperties; @@ -217,24 +255,20 @@ class Geometry //!@brief Topology of the blueprint simplex mesh, if it's in memory. std::string m_topology; - //!@brief The analytical sphere, if used. - axom::primal::Sphere m_sphere; - -#if 0 - //!@brief The analytical cylinder, if used. - axom::primal::Sphere m_cylinder; - - //!@brief The analytical cone, if used. - axom::primal::Sphere m_cone; - - //!@brief The discrete 2D curve, if used. - axom::Array m_discreteFunction; -#endif - //!@brief Level of refinement for discretizing analytical shapes // and surfaces of revolutions. axom::IndexType m_levelOfRefinement = 0; + //!@brief The analytical sphere, if used. + Sphere3D m_sphere; + + /*! + @brief The discrete 2D function, as an Nx2 array, if used. + */ + axom::Array m_discreteFunction; + Point3D m_vorBase; + Vector3D m_vorDirection; + std::shared_ptr m_operator; }; diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index c22a447f47..30112f5b52 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -103,12 +103,13 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() { if (m_meshRep) { return m_meshRep; } + using TetMesh = axom::mint::UnstructuredMesh; + const axom::klee::Geometry& geometry = m_shape.getGeometry(); const auto& geometryFormat = geometry.getFormat(); if (geometryFormat == "memory-blueprint") { - // TODO: For readability, move most of this into a function. // Put the in-memory geometry in m_meshRep. axom::sidre::Group* inputGroup = geometry.getBlueprintMesh(); axom::sidre::Group* rootGroup = inputGroup->getDataStore()->getRoot(); @@ -171,11 +172,10 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() } }); - // TODO: Set this up with sidre::Group to have data on device. - axom::mint::UnstructuredMesh* tetMesh = nullptr; + TetMesh* tetMesh = nullptr; if (m_sidreGroup != nullptr) { - tetMesh = new axom::mint::UnstructuredMesh( + tetMesh = new TetMesh( 3, axom::mint::CellType::TET, m_sidreGroup, @@ -184,7 +184,7 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() } else { - tetMesh = new axom::mint::UnstructuredMesh( + tetMesh = new TetMesh( 3, axom::mint::CellType::TET, nodeCount, @@ -196,6 +196,60 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() applyTransforms(); } + if (geometryFormat == "vor3D") + { + // Construct the tet m_meshRep from the volume-of-revolution. + auto& vorGeom = m_shape.getGeometry(); + const auto& discreteFcn = vorGeom.getDiscreteFunction(); + + // Generate the Octahedra + axom::Array octs; + int octCount = 0; + axom::ArrayView polyline((Point2D*)discreteFcn.data(), + discreteFcn.shape()[0]); + const bool good = axom::quest::discretize( + polyline, + int(polyline.size()), + m_shape.getGeometry().getLevelOfRefinement(), + octs, + octCount); + SLIC_ASSERT(good); + + // Dump discretized octs as a tet mesh + axom::mint::Mesh* mesh; + axom::quest::mesh_from_discretized_polyline( + octs.view(), + octCount, + polyline.size() - 1, + mesh); + + if (m_sidreGroup) + { + auto* tetMesh = static_cast(mesh); + // Don't directly use tetMesh, because we want the data in Sidre. + axom::mint::UnstructuredMesh* + siMesh = new axom::mint::UnstructuredMesh( + tetMesh->getDimension(), + tetMesh->getCellType(), + m_sidreGroup, + tetMesh->getTopologyName(), + tetMesh->getCoordsetName(), + tetMesh->getNumberOfNodes(), + tetMesh->getNumberOfCells()); + siMesh->appendNodes( tetMesh->getCoordinateArray(0) + , tetMesh->getCoordinateArray(1) + , tetMesh->getCoordinateArray(2) + , tetMesh->getNumberOfNodes() ); + siMesh->appendCells( tetMesh->getCellNodesArray() + , tetMesh->getNumberOfCells() ); + m_meshRep.reset(siMesh); + } else { + m_meshRep.reset(mesh); + } + + // Transform the coordinates of the linearized mesh. + applyTransforms(); + } if (m_meshRep) { diff --git a/src/axom/quest/Discretize.hpp b/src/axom/quest/Discretize.hpp index ddf71aea7e..b95bcca47e 100644 --- a/src/axom/quest/Discretize.hpp +++ b/src/axom/quest/Discretize.hpp @@ -69,7 +69,7 @@ bool discretize(const SphereType& s, * This routine initializes an Array, \a out. */ template -bool discretize(const axom::Array& polyline, +bool discretize(const axom::ArrayView& polyline, int len, int levels, axom::Array& out, diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 41e8759e66..fcca22e760 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -317,6 +317,7 @@ class IntersectionShaper : public Shaper using Point3D = primal::Point; using TetrahedronType = primal::Tetrahedron; using SegmentMesh = mint::UnstructuredMesh; + using TetMesh = mint::UnstructuredMesh; using RuntimePolicy = axom::runtime_policy::Policy; @@ -610,21 +611,15 @@ class IntersectionShaper : public Shaper std::string shapeFormat = shape.getGeometry().getFormat(); + // C2C mesh is not discretized into tets, but all others are. if(shapeFormat == "c2c") { prepareC2CCells(); } - - else if(shapeFormat == "proe") - { - prepareTetCells(); - } - - else if(shapeFormat == "memory-blueprint" || shapeFormat == "sphere3D") + else if(surfaceMeshIsTet()) { prepareTetCells(); } - else { SLIC_ERROR( @@ -1492,8 +1487,8 @@ class IntersectionShaper : public Shaper AXOM_ANNOTATE_SCOPE("runShapeQuery"); const std::string shapeFormat = shape.getGeometry().getFormat(); - // Testing separate workflow for Pro/E - if(shapeFormat == "proe" || shapeFormat == "memory-blueprint" || shapeFormat == "sphere3D") + // C2C mesh is not discretized into tets, but all others are. + if(surfaceMeshIsTet()) { switch(m_execPolicy) { @@ -2029,6 +2024,14 @@ class IntersectionShaper : public Shaper return volFrac; } + bool surfaceMeshIsTet() const { + bool isTet = m_surfaceMesh != nullptr && + m_surfaceMesh->getDimension() == 3 && + !m_surfaceMesh->hasMixedCellTypes() && + m_surfaceMesh->getCellType() == mint::TET; + return isTet; + } + private: RuntimePolicy m_execPolicy {RuntimePolicy::seq}; int m_level {DEFAULT_CIRCLE_REFINEMENT_LEVEL}; diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index faf22577e4..3f13172dc6 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -93,7 +93,7 @@ bool Shaper::isValidFormat(const std::string& format) const { return (format == "stl" || format == "proe" || format == "c2c" || format == "memory-blueprint" || format == "sphere3D" || - format == "none"); + format == "vor3D" || format == "none"); } void Shaper::loadShape(const klee::Shape& shape) diff --git a/src/axom/quest/detail/Discretize_detail.hpp b/src/axom/quest/detail/Discretize_detail.hpp index fa1fdc7d97..e24521abf6 100644 --- a/src/axom/quest/detail/Discretize_detail.hpp +++ b/src/axom/quest/detail/Discretize_detail.hpp @@ -264,7 +264,7 @@ namespace quest * This routine initializes an Array pointed to by \a out. */ template -bool discretize(const axom::Array &polyline, +bool discretize(const axom::ArrayView &polyline, int pointcount, int levels, axom::Array &out, diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 9cb5b2e05d..1b63233bc8 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -252,15 +252,16 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) # 3D shaping tests. No 2D shaping in this feature, at least for now. set(_nranks 1) - set(_testshapes "tet" "sphere") + set(_testshapes "tetmesh" "sphere" "cyl" "cone" "vor") foreach(_testshape ${_testshapes}) set(_testname "quest_shape_in_memory_${_testshape}") axom_add_test( NAME ${_testname} COMMAND quest_shape_in_memory_ex + --testShape ${_testshape} --method intersection --refinements 2 --scale 0.75 0.75 0.75 - inline_mesh --min -1 -1 -1 --max 5 5 5 --res 20 20 20 -d 3 + inline_mesh --min -2 -2 -2 --max 4 4 4 --res 20 20 20 -d 3 NUM_MPI_TASKS ${_nranks}) endforeach() endif() diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 55035bb2a4..0d0f33fadc 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -86,9 +86,9 @@ struct Input int boxDim {-1}; // The shape to run. - std::string shape {"tet"}; + std::string testShape {"tetmesh"}; // The shapes this example is set up to run. - const std::set availableShapes {"tet", "sphere"}; + const std::set availableShapes {"tetmesh", "sphere", "cyl", "cone", "vor"}; ShapingMethod shapingMethod {ShapingMethod::Sampling}; RuntimePolicy policy {RuntimePolicy::seq}; @@ -198,7 +198,7 @@ struct Input void parse(int argc, char** argv, axom::CLI::App& app) { - app.add_option("-o,--output-file", outputFile) + app.add_option("-o,--outputFile", outputFile) ->description("Path to output file(s)"); app.add_flag("-v,--verbose,!--no-verbose", m_verboseOutput) @@ -229,7 +229,7 @@ struct Input ->transform( axom::CLI::CheckedTransformer(methodMap, axom::CLI::ignore_case)); - app.add_option("-s,--shape", shape) + app.add_option("-s,--testShape", testShape) ->description("The shape to run") ->check(axom::CLI::IsMember(availableShapes)); @@ -578,11 +578,94 @@ axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) } axom::klee::Geometry tetMeshGeometry( prop, tetMesh.getSidreGroup(), topo, {scaleOp} ); - axom::klee::Shape tetShape( "tet", "AL", {}, {}, tetMeshGeometry ); + axom::klee::Shape tetShape( "tetmesh", "TETMESH", {}, {}, tetMeshGeometry ); return tetShape; } +axom::klee::Geometry createGeometry_Vor( + axom::primal::Point& vorBase, + axom::primal::Vector& vorDirection, + axom::Array& discreteFunction ) +{ + axom::klee::TransformableGeometryProperties prop{ + axom::klee::Dimensions::Three, + axom::klee::LengthUnit::unspecified}; + + SLIC_ASSERT( params.scaleFactors.empty() || params.scaleFactors.size() == 3 ); + std::shared_ptr scaleOp; + if (!params.scaleFactors.empty()) + { + scaleOp = + std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + prop); + } + + const axom::IndexType levelOfRefinement = params.refinementLevel; + axom::klee::Geometry vorGeometry(prop, discreteFunction, + vorBase, vorDirection, + levelOfRefinement, scaleOp); + return vorGeometry; +} + +axom::klee::Shape createShape_Vor() +{ + axom::primal::Point vorBase{0.0, 0.0, 0.0}; + axom::primal::Vector vorDirection{1.0, 0.0, 0.0}; + axom::Array discreteFunction({3, 2}, axom::ArrayStrideOrder::ROW); + discreteFunction[0][0] = 0.0; + discreteFunction[0][1] = 1.0; + discreteFunction[1][0] = 0.5; + discreteFunction[1][1] = 0.8; + discreteFunction[2][0] = 1.0; + discreteFunction[2][1] = 1.0; + + axom::klee::Geometry vorGeometry = createGeometry_Vor( + vorBase, vorDirection, discreteFunction); + + axom::klee::Shape vorShape( "vor", "VOR", {}, {}, vorGeometry ); + + return vorShape; +} + +axom::klee::Shape createShape_Cylinder() +{ + axom::primal::Point vorBase{0.0, 0.0, 0.0}; + axom::primal::Vector vorDirection{1.0, 0.0, 0.0}; + axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); + discreteFunction[0][0] = 0.0; + discreteFunction[0][1] = 1.0; + discreteFunction[1][0] = 1.0; + discreteFunction[1][1] = 1.0; + + axom::klee::Geometry vorGeometry = createGeometry_Vor( + vorBase, vorDirection, discreteFunction); + + axom::klee::Shape vorShape( "cyl", "CYL", {}, {}, vorGeometry ); + + return vorShape; +} + +axom::klee::Shape createShape_Cone() +{ + axom::primal::Point vorBase{0.0, 0.0, 0.0}; + axom::primal::Vector vorDirection{1.0, 0.0, 0.0}; + axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); + discreteFunction[0][0] = 0.0; + discreteFunction[0][1] = 0.5; + discreteFunction[1][0] = 1.0; + discreteFunction[1][1] = 1.1; + + axom::klee::Geometry vorGeometry = createGeometry_Vor( + vorBase, vorDirection, discreteFunction); + + axom::klee::Shape vorShape( "cone", "CONE", {}, {}, vorGeometry ); + + return vorShape; +} + //!@brief Create a ShapeSet with a single shape. axom::klee::ShapeSet createShapeSet(const axom::klee::Shape& shape) { @@ -797,14 +880,26 @@ int main(int argc, char** argv) shapeSet = create2DShapeSet(ds); break; case 3: - if (params.shape == "tet") + if (params.testShape == "tetmesh") { shapeSet = createShapeSet( createShape_TetMesh(ds) ); } - else if (params.shape == "sphere") + else if (params.testShape == "sphere") { shapeSet = createShapeSet( createShape_Sphere() ); } + else if (params.testShape == "cyl") + { + shapeSet = createShapeSet( createShape_Cylinder() ); + } + else if (params.testShape == "cone") + { + shapeSet = createShapeSet( createShape_Cone() ); + } + else if (params.testShape == "vor") + { + shapeSet = createShapeSet( createShape_Vor() ); + } break; } @@ -1127,7 +1222,7 @@ int main(int argc, char** argv) shapeMesh->getNumberOfCells()))); if (!params.outputFile.empty()) { - std::string shapeFileName = params.outputFile + "." + shape.getName(); + std::string shapeFileName = params.outputFile + ".shape"; conduit::Node tmpNode, info; shapeMesh->getSidreGroup()->createNativeLayout(tmpNode); conduit::relay::io::blueprint::save_mesh(tmpNode, shapeFileName, "hdf5"); @@ -1168,8 +1263,9 @@ int main(int argc, char** argv) #ifdef MFEM_USE_MPI if (!params.outputFile.empty()) { - shaper->getDC()->Save(params.outputFile, sidre::Group::getDefaultIOProtocol()); - SLIC_INFO(axom::fmt::format("{:=^80}", "Wrote output mesh " + params.outputFile)); + std::string fileName = params.outputFile + ".volfracs"; + shaper->getDC()->Save(fileName, sidre::Group::getDefaultIOProtocol()); + SLIC_INFO(axom::fmt::format("{:=^80}", "Wrote output mesh " + fileName)); } #endif From cf5838826453b91cb22253e1d4b9c86521802822 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 9 Aug 2024 07:56:55 -0700 Subject: [PATCH 10/43] Small changes to comments. --- src/axom/klee/Geometry.hpp | 15 +++++++++------ src/axom/quest/DiscreteShape.hpp | 2 +- src/axom/quest/examples/quest_shape_in_memory.cpp | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index da84fefe3e..be87e1c198 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -148,6 +148,9 @@ class Geometry * "cylinder3D" = 3D cylinder, as \c primal::Cylinder * * \return the format of the shape + * + * TODO: Depending on the specified geometry, some members are not + * used. It may clarify make each supported geometry a subclass. */ const std::string &getFormat() const { return m_format; } @@ -212,8 +215,8 @@ class Geometry TransformableGeometryProperties getEndProperties() const; /** - @brief Return the number of generations for discretization of - analytical curves. + @brief Return the number of levels of refinement for discretization + of analytical curves. This number is unused for geometries that are specified in discrete form. @@ -233,7 +236,7 @@ class Geometry } /** - @brief Get the discrete function. + @brief Get the discrete function used in volumes of revolution. */ axom::ArrayView getDiscreteFunction() const { @@ -262,11 +265,11 @@ class Geometry //!@brief The analytical sphere, if used. Sphere3D m_sphere; - /*! - @brief The discrete 2D function, as an Nx2 array, if used. - */ + //! @brief The discrete 2D function, as an Nx2 array, if used. axom::Array m_discreteFunction; + //!@brief The base of the VOR axis, corresponding to z=0. Point3D m_vorBase; + //!@brief VOR axis in the direction of increasing z. Vector3D m_vorDirection; std::shared_ptr m_operator; diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index 3c4386668e..4a85537326 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -141,7 +141,7 @@ class DiscreteShape std::string m_prefixPath; //@{ - //!@name Various parameters for discretization. + //!@name Various parameters for discretization of analytical shapes. RefinementType m_refinementType; double m_percentError {MINIMUM_PERCENT_ERROR}; int m_samplesPerKnotSpan {DEFAULT_SAMPLES_PER_KNOT_SPAN}; diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 0d0f33fadc..527367774d 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -57,7 +57,7 @@ using VolFracSampling = quest::shaping::VolFracSampling; //------------------------------------------------------------------------------ -/// Struct to help choose if our shaping method: sampling or intersection for now +/// Struct to help choose our shaping method: sampling or intersection for now enum class ShapingMethod : int { Sampling, From e3e89749f853a14dc8d083d74baa7d399af62111 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 9 Aug 2024 18:29:22 -0700 Subject: [PATCH 11/43] Implement arbitrary locations for VOR (not always on x-axis). --- src/axom/klee/Geometry.hpp | 10 +++ src/axom/quest/DiscreteShape.cpp | 63 +++++++++++++++- src/axom/quest/DiscreteShape.hpp | 4 ++ src/axom/quest/examples/CMakeLists.txt | 2 +- .../quest/examples/quest_shape_in_memory.cpp | 71 +++++++++++-------- 5 files changed, 119 insertions(+), 31 deletions(-) diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index be87e1c198..06712966cb 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -174,6 +174,16 @@ class Geometry */ const std::string& getBlueprintTopology() const; + /** + @brief Return the VOR axis direction. + */ + const Vector3D getVorDirection() const { return m_vorDirection; } + + /** + @brief Return the 3D coordinates of the VOR base. + */ + const Point3D getVorBaseCoords() const { return m_vorBase; } + /*! @brief Predicate that returns true when the shape has an associated geometry A false means that this is set up to determine volume fractions without diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 30112f5b52..5e765109bf 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -215,6 +215,23 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() octCount); SLIC_ASSERT(good); + // Rotate to the VOR axis direction and translate to the base location. + numerics::Matrix rotate = vorAxisRotMatrix(vorGeom.getVorDirection()); + const auto& translate = vorGeom.getVorBaseCoords(); + auto octsView = octs.view(); + axom::for_all( + octCount, + AXOM_LAMBDA(axom::IndexType iOct) { + auto& oct = octsView[iOct]; + for ( int iVert = 0; iVert < OctType::NUM_VERTS; ++iVert ) + { + auto& newCoords = oct[iVert]; + auto oldCoords = newCoords; + numerics::matrix_vector_multiply(rotate, oldCoords.data(), newCoords.data()); + newCoords.array() += translate.array(); + } + }); + // Dump discretized octs as a tet mesh axom::mint::Mesh* mesh; axom::quest::mesh_from_discretized_polyline( @@ -225,8 +242,8 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() if (m_sidreGroup) { + // If using sidre, copy the tetMesh into sidre. auto* tetMesh = static_cast(mesh); - // Don't directly use tetMesh, because we want the data in Sidre. axom::mint::UnstructuredMesh* siMesh = new axom::mint::UnstructuredMesh( tetMesh->getDimension(), @@ -243,6 +260,8 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() siMesh->appendCells( tetMesh->getCellNodesArray() , tetMesh->getNumberOfCells() ); m_meshRep.reset(siMesh); + delete mesh; + mesh = nullptr; } else { m_meshRep.reset(mesh); } @@ -423,6 +442,48 @@ numerics::Matrix DiscreteShape::getTransforms() const return transformation; } +// Return a 3x3 matrix that rotate coordinates from the x-axis to the given direction. +numerics::Matrix DiscreteShape::vorAxisRotMatrix(const Vector3D& dir) +{ + // Note that the rotation matrix is not unique. + static const Vector3D x {1.0, 0.0, 0.0}; + Vector3D a = dir.unitVector(); + Vector3D u; // Rotation vector, the cross product of x and a. + numerics::cross_product( x.data(), a.data(), u.data() ); + double sinT = u.norm(); + double cosT = numerics::dot_product( x.data(), a.data(), 3 ); + double ccosT = 1 - cosT; + + // Degenerate case with angle near 0 or pi. + if (utilities::isNearlyEqual(sinT, 0.0)) + { + if (cosT > 0) + { + return numerics::Matrix::identity(3); + } + else + { + // Give u a tiny component in any non-x direction + // so we can rotate around it. + u[1] = 1e-8; + } + } + + u = u.unitVector(); + numerics::Matrix rot(3, 3, 0.0); + rot(0, 0) = u[0]*u[0] * ccosT + cosT; + rot(0, 1) = u[0]*u[1] * ccosT - u[2]*sinT; + rot(0, 2) = u[0]*u[2] * ccosT + u[1]*sinT; + rot(1, 0) = u[1]*u[0] * ccosT + u[2]*sinT; + rot(1, 1) = u[1]*u[1] * ccosT + cosT; + rot(1, 2) = u[1]*u[2] * ccosT - u[0]*sinT; + rot(2, 0) = u[0]*u[2] * ccosT - u[1]*sinT; + rot(2, 1) = u[1]*u[2] * ccosT + u[0]*sinT; + rot(2, 2) = u[2]*u[2] * ccosT + cosT; + + return rot; +} + void DiscreteShape::setSamplesPerKnotSpan(int nSamples) { using axom::utilities::clampLower; diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index 4a85537326..683ba7a6f4 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -34,6 +34,7 @@ class DiscreteShape using RefinementType = enum { RefinementUniformSegments, RefinementDynamic }; using Point3D = axom::primal::Point; + using Vector3D = axom::primal::Vector; using TetType = axom::primal::Tetrahedron; using OctType = axom::primal::Octahedron; @@ -164,6 +165,9 @@ class DiscreteShape */ void setParentGroup(axom::sidre::Group* parentGroup); + //!@brief Return a 3x3 matrix that rotate coordinates from the x-axis to the given direction. + numerics::Matrix vorAxisRotMatrix(const Vector3D& dir); + void clearInternalData(); }; diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 1b63233bc8..00d0cf82ea 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -261,7 +261,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI --testShape ${_testshape} --method intersection --refinements 2 --scale 0.75 0.75 0.75 - inline_mesh --min -2 -2 -2 --max 4 4 4 --res 20 20 20 -d 3 + inline_mesh --min -3 -3 -3 --max 3 3 3 --res 20 20 20 -d 3 NUM_MPI_TASKS ${_nranks}) endforeach() endif() diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 527367774d..67d79c90a5 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -73,8 +73,11 @@ struct Input std::string meshFile; std::string outputFile; - std::vector sphereCenter {1.0, 1.0, 1.0}; - double sphereRadius {1.0}; + std::vector center {0.0, 0.0, 0.0}; + double radius {1.0}; + double radius2 {0.3}; + double length {2.0}; + std::vector direction {0.0, 0.0, 1.0}; // Shape transformation parameters std::vector scaleFactors; @@ -242,14 +245,26 @@ struct Input ->check(axom::utilities::ValidCaliperMode); #endif - app.add_option("--sphereCenter", sphereCenter) - ->description("Center of sphere (x,y[,z])") + app.add_option("--center", center) + ->description("Center of sphere or base of cone/cyl/VOR (x,y[,z]) shape") ->expected(2, 3); - app.add_option("--sphereRadius", sphereRadius) - ->description("Radius of sphere") + app.add_option("--radius", radius) + ->description("Radius of sphere or cylinder shape") + ->check(axom::CLI::PositiveNumber); + + app.add_option("--length", length) + ->description("Length of cone/cyl/VOR shape") + ->check(axom::CLI::PositiveNumber); + + app.add_option("--dir", direction) + ->description("Direction of axis of cone/cyl/VOR (x,y[,z]) shape") ->expected(2, 3); + app.add_option("--radius2", radius2) + ->description("Second radius of cone shape") + ->check(axom::CLI::PositiveNumber); + app.add_option("--scale", scaleFactors) ->description("Scale factor to apply to shape (x,y[,z])") ->expected(2, 3) @@ -513,7 +528,7 @@ axom::klee::ShapeSet create2DShapeSet(sidre::DataStore& ds) axom::klee::Shape createShape_Sphere() { - axom::primal::Sphere sphere{params.sphereCenter.data(), params.sphereRadius}; + axom::primal::Sphere sphere{params.center.data(), params.radius}; axom::klee::TransformableGeometryProperties prop{ axom::klee::Dimensions::Three, @@ -612,14 +627,14 @@ axom::klee::Geometry createGeometry_Vor( axom::klee::Shape createShape_Vor() { - axom::primal::Point vorBase{0.0, 0.0, 0.0}; - axom::primal::Vector vorDirection{1.0, 0.0, 0.0}; + Point3D vorBase {params.center.data()}; + axom::primal::Vector vorDirection{params.direction.data()}; axom::Array discreteFunction({3, 2}, axom::ArrayStrideOrder::ROW); discreteFunction[0][0] = 0.0; discreteFunction[0][1] = 1.0; - discreteFunction[1][0] = 0.5; + discreteFunction[1][0] = 0.5*params.length; discreteFunction[1][1] = 0.8; - discreteFunction[2][0] = 1.0; + discreteFunction[2][0] = params.length; discreteFunction[2][1] = 1.0; axom::klee::Geometry vorGeometry = createGeometry_Vor( @@ -632,13 +647,15 @@ axom::klee::Shape createShape_Vor() axom::klee::Shape createShape_Cylinder() { - axom::primal::Point vorBase{0.0, 0.0, 0.0}; - axom::primal::Vector vorDirection{1.0, 0.0, 0.0}; + Point3D vorBase {params.center.data()}; + axom::primal::Vector vorDirection{params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); + double radius = params.radius; + double height = params.length; discreteFunction[0][0] = 0.0; - discreteFunction[0][1] = 1.0; - discreteFunction[1][0] = 1.0; - discreteFunction[1][1] = 1.0; + discreteFunction[0][1] = radius; + discreteFunction[1][0] = height; + discreteFunction[1][1] = radius; axom::klee::Geometry vorGeometry = createGeometry_Vor( vorBase, vorDirection, discreteFunction); @@ -650,13 +667,16 @@ axom::klee::Shape createShape_Cylinder() axom::klee::Shape createShape_Cone() { - axom::primal::Point vorBase{0.0, 0.0, 0.0}; - axom::primal::Vector vorDirection{1.0, 0.0, 0.0}; + Point3D vorBase {params.center.data()}; + axom::primal::Vector vorDirection{params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); + double baseRadius = params.radius; + double topRadius = params.radius2; + double height = params.length; discreteFunction[0][0] = 0.0; - discreteFunction[0][1] = 0.5; - discreteFunction[1][0] = 1.0; - discreteFunction[1][1] = 1.1; + discreteFunction[0][1] = baseRadius; + discreteFunction[1][0] = height; + discreteFunction[1][1] = topRadius; axom::klee::Geometry vorGeometry = createGeometry_Vor( vorBase, vorDirection, discreteFunction); @@ -922,7 +942,7 @@ int main(int argc, char** argv) if (!params.outputFile.empty()) { - std::string shapeFileName = params.outputFile + "." + shape.getName(); + std::string shapeFileName = params.outputFile + ".shape"; conduit::Node tmpNode, info; dMesh->getSidreGroup()->createNativeLayout(tmpNode); conduit::relay::io::blueprint::save_mesh(tmpNode, shapeFileName, "hdf5"); @@ -1220,13 +1240,6 @@ int main(int argc, char** argv) axom::fmt::format("Shape '{}' discrete geometry has {} cells", shape.getName(), shapeMesh->getNumberOfCells()))); - if (!params.outputFile.empty()) - { - std::string shapeFileName = params.outputFile + ".shape"; - conduit::Node tmpNode, info; - shapeMesh->getSidreGroup()->createNativeLayout(tmpNode); - conduit::relay::io::blueprint::save_mesh(tmpNode, shapeFileName, "hdf5"); - } const std::string& materialName = shape.getMaterial(); double shapeVol = sumMaterialVolumes( &shapingDC, materialName ); From 381a75af991d6dcef2894c04d68cfb378d32660c Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sun, 11 Aug 2024 14:06:44 -0700 Subject: [PATCH 12/43] Add hexahedron shaping (in-memory only for now, not through klee). --- src/axom/klee/Geometry.cpp | 58 +-- src/axom/klee/Geometry.hpp | 49 ++- src/axom/klee/GeometryOperatorsIO.hpp | 2 +- src/axom/primal/geometry/Hexahedron.hpp | 2 +- src/axom/quest/DiscreteShape.cpp | 186 +++++---- src/axom/quest/DiscreteShape.hpp | 13 +- src/axom/quest/IntersectionShaper.hpp | 6 +- src/axom/quest/SamplingShaper.hpp | 8 +- src/axom/quest/Shaper.cpp | 4 +- src/axom/quest/examples/CMakeLists.txt | 2 +- .../quest/examples/quest_shape_in_memory.cpp | 357 +++++++++++------- 11 files changed, 426 insertions(+), 261 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index d3777ea2b9..609280f7ab 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -13,13 +13,13 @@ namespace axom { namespace klee { -bool operator==(const TransformableGeometryProperties &lhs, - const TransformableGeometryProperties &rhs) +bool operator==(const TransformableGeometryProperties& lhs, + const TransformableGeometryProperties& rhs) { return lhs.dimensions == rhs.dimensions && lhs.units == rhs.units; } -Geometry::Geometry(const TransformableGeometryProperties &startProperties, +Geometry::Geometry(const TransformableGeometryProperties& startProperties, std::string format, std::string path, std::shared_ptr operator_) @@ -30,7 +30,7 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, , m_operator(std::move(operator_)) { } -Geometry::Geometry(const TransformableGeometryProperties &startProperties, +Geometry::Geometry(const TransformableGeometryProperties& startProperties, axom::sidre::Group* meshGroup, const std::string& topology, std::shared_ptr operator_) @@ -41,10 +41,22 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, , m_topology(topology) , m_levelOfRefinement(0) , m_operator(std::move(operator_)) -{ -} +{ } + +Geometry::Geometry(const TransformableGeometryProperties& startProperties, + const axom::primal::Hexahedron& hex, + std::shared_ptr operator_) + : m_startProperties(startProperties) + , m_format("hex3D") + , m_path() + , m_meshGroup(nullptr) + , m_topology() + , m_hex(hex) + , m_levelOfRefinement(0) + , m_operator(std::move(operator_)) +{ } -Geometry::Geometry(const TransformableGeometryProperties &startProperties, +Geometry::Geometry(const TransformableGeometryProperties& startProperties, const Sphere3D& sphere, axom::IndexType levelOfRefinement, std::shared_ptr operator_) @@ -56,10 +68,9 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, , m_levelOfRefinement(levelOfRefinement) , m_sphere(sphere) , m_operator(std::move(operator_)) -{ -} +{ } -Geometry::Geometry(const TransformableGeometryProperties &startProperties, +Geometry::Geometry(const TransformableGeometryProperties& startProperties, const axom::Array& discreteFunction, const Point3D& vorBase, const Vector3D& vorDirection, @@ -76,14 +87,13 @@ Geometry::Geometry(const TransformableGeometryProperties &startProperties, , m_vorBase(vorBase) , m_vorDirection(vorDirection) , m_operator(std::move(operator_)) -{ -} +{ } bool Geometry::hasGeometry() const { - bool isInMemory = m_format == "memory-blueprint" || m_format == "sphere3D" - || m_format == "cone3D" || m_format == "cylinder3D"; - if (isInMemory) + bool isInMemory = m_format == "memory-blueprint" || m_format == "sphere3D" || + m_format == "cone3D" || m_format == "cylinder3D"; + if(isInMemory) { return true; } @@ -101,17 +111,23 @@ TransformableGeometryProperties Geometry::getEndProperties() const axom::sidre::Group* Geometry::getBlueprintMesh() const { - SLIC_ASSERT_MSG(m_meshGroup, - axom::fmt::format("The Geometry format '{}' is not specified " - "as a blueprint mesh and/or has not been converted into one.", m_format)); + SLIC_ASSERT_MSG( + m_meshGroup, + axom::fmt::format( + "The Geometry format '{}' is not specified " + "as a blueprint mesh and/or has not been converted into one.", + m_format)); return m_meshGroup; } const std::string& Geometry::getBlueprintTopology() const { - SLIC_ASSERT_MSG(m_meshGroup, - axom::fmt::format("The Geometry format '{}' is not specified " - "as a blueprint mesh and/or has not been converted into one.", m_format)); + SLIC_ASSERT_MSG( + m_meshGroup, + axom::fmt::format( + "The Geometry format '{}' is not specified " + "as a blueprint mesh and/or has not been converted into one.", + m_format)); return m_topology; } diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 06712966cb..be21ab1f78 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -59,6 +59,7 @@ class Geometry using Point3D = axom::primal::Point; using Vector3D = axom::primal::Vector; using Sphere3D = axom::primal::Sphere; + using Hex3D = axom::primal::Hexahedron; /** * Create a Geometry object based on a file representation. @@ -88,7 +89,19 @@ class Geometry */ Geometry(const TransformableGeometryProperties &startProperties, axom::sidre::Group *meshGroup, - const std::string& topology, + const std::string &topology, + std::shared_ptr operator_); + + /** + * Create a hexahedron Geometry object. + * + * \param startProperties the transformable properties before any + * operators are applied + * \param hex Hexahedron + * \param operator_ a possibly null operator to apply to the geometry. + */ + Geometry(const TransformableGeometryProperties &startProperties, + const axom::primal::Hexahedron &hex, std::shared_ptr operator_); /** @@ -104,7 +117,7 @@ class Geometry * \internal TODO: Is this the simplex requirement overly restrictive? */ Geometry(const TransformableGeometryProperties &startProperties, - const axom::primal::Sphere& sphere, + const axom::primal::Sphere &sphere, axom::IndexType levelOfRefinement, std::shared_ptr operator_); @@ -129,9 +142,9 @@ class Geometry * \internal TODO: Is this the simplex requirement overly restrictive? */ Geometry(const TransformableGeometryProperties &startProperties, - const axom::Array& discreteFunction, - const Point3D& vorBase, - const Vector3D& vorDirection, + const axom::Array &discreteFunction, + const Point3D &vorBase, + const Vector3D &vorDirection, axom::IndexType levelOfRefinement, std::shared_ptr operator_); @@ -146,6 +159,7 @@ class Geometry * - "vor3D" = 3D volume of revolution. * - "cone3D" = 3D cone, as \c primal::Cone * "cylinder3D" = 3D cylinder, as \c primal::Cylinder + * - "hex3D" = 3D hexahedron (8 points) * * \return the format of the shape * @@ -166,13 +180,13 @@ class Geometry * @brief Return the blueprint mesh, for formats that are specified * by a blueprint mesh or have been converted to a blueprint mesh. */ - axom::sidre::Group* getBlueprintMesh() const; + axom::sidre::Group *getBlueprintMesh() const; /** * @brief Return the blueprint mesh topology, for formats that are specified * by a blueprint mesh or have been converted to a blueprint mesh. */ - const std::string& getBlueprintTopology() const; + const std::string &getBlueprintTopology() const; /** @brief Return the VOR axis direction. @@ -231,19 +245,19 @@ class Geometry This number is unused for geometries that are specified in discrete form. */ - axom::IndexType getLevelOfRefinement() const - { - return m_levelOfRefinement; - } + axom::IndexType getLevelOfRefinement() const { return m_levelOfRefinement; } + + /** + @brief Return the hex geometry, when the Geometry + represents a hexahedron. + */ + const axom::primal::Hexahedron &getHex() const { return m_hex; } /** @brief Return the sphere geometry, when the Geometry represents an alalytical sphere. */ - const axom::primal::Sphere& getSphere() const - { - return m_sphere; - } + const axom::primal::Sphere &getSphere() const { return m_sphere; } /** @brief Get the discrete function used in volumes of revolution. @@ -263,11 +277,14 @@ class Geometry std::string m_path; //!@brief Geometry blueprint simplex mesh, when/if it's in memory. - axom::sidre::Group* m_meshGroup; + axom::sidre::Group *m_meshGroup; //!@brief Topology of the blueprint simplex mesh, if it's in memory. std::string m_topology; + //!@brief The hexahedron, if used. + Hex3D m_hex; + //!@brief Level of refinement for discretizing analytical shapes // and surfaces of revolutions. axom::IndexType m_levelOfRefinement = 0; diff --git a/src/axom/klee/GeometryOperatorsIO.hpp b/src/axom/klee/GeometryOperatorsIO.hpp index c9af1b43ee..38eaaf7e43 100644 --- a/src/axom/klee/GeometryOperatorsIO.hpp +++ b/src/axom/klee/GeometryOperatorsIO.hpp @@ -91,7 +91,7 @@ class GeometryOperatorData private: Path m_path; std::vector m_singleOperatorData; -}; // GeometryOperatorData +}; // GeometryOperatorData /** * Data for a named operator. diff --git a/src/axom/primal/geometry/Hexahedron.hpp b/src/axom/primal/geometry/Hexahedron.hpp index c16a55886b..424a3f4c36 100644 --- a/src/axom/primal/geometry/Hexahedron.hpp +++ b/src/axom/primal/geometry/Hexahedron.hpp @@ -248,7 +248,7 @@ class Hexahedron * \note Assumes tets is pre-allocated */ AXOM_HOST_DEVICE - void triangulate(axom::StackArray& tets) + void triangulate(axom::StackArray& tets) const { // Hex center (hc) PointType hc = vertexMean(); diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 5e765109bf..d9c237050b 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -79,7 +79,7 @@ constexpr double DiscreteShape::MINIMUM_PERCENT_ERROR; constexpr double DiscreteShape::MAXIMUM_PERCENT_ERROR; constexpr double DiscreteShape::DEFAULT_VERTEX_WELD_THRESHOLD; -DiscreteShape::DiscreteShape(const axom::klee::Shape &shape, +DiscreteShape::DiscreteShape(const axom::klee::Shape& shape, const std::string& prefixPath, axom::sidre::Group* parentGroup) : m_shape(shape) @@ -92,7 +92,7 @@ DiscreteShape::DiscreteShape(const axom::klee::Shape &shape, void DiscreteShape::clearInternalData() { m_meshRep.reset(); - if (m_sidreGroup) + if(m_sidreGroup) { m_sidreGroup->getParent()->destroyGroupAndData(m_sidreGroup->getIndex()); m_sidreGroup = nullptr; @@ -101,33 +101,91 @@ void DiscreteShape::clearInternalData() std::shared_ptr DiscreteShape::createMeshRepresentation() { - if (m_meshRep) { return m_meshRep; } + if(m_meshRep) + { + return m_meshRep; + } - using TetMesh = axom::mint::UnstructuredMesh; + using TetMesh = + axom::mint::UnstructuredMesh; const axom::klee::Geometry& geometry = m_shape.getGeometry(); const auto& geometryFormat = geometry.getFormat(); - if (geometryFormat == "memory-blueprint") + if(geometryFormat == "memory-blueprint") { // Put the in-memory geometry in m_meshRep. axom::sidre::Group* inputGroup = geometry.getBlueprintMesh(); axom::sidre::Group* rootGroup = inputGroup->getDataStore()->getRoot(); std::string modName = inputGroup->getName() + "_modified"; - while (rootGroup->hasGroup(modName)) { modName = modName + "-"; } + while(rootGroup->hasGroup(modName)) + { + modName = modName + "-"; + } axom::sidre::Group* modGroup = rootGroup->createGroup(modName); int allocID = inputGroup->getDefaultAllocatorID(); modGroup->deepCopyGroup(inputGroup, allocID); - m_meshRep.reset(axom::mint::getMesh(modGroup->getGroup(inputGroup->getName()), - m_shape.getGeometry().getBlueprintTopology())); + m_meshRep.reset( + axom::mint::getMesh(modGroup->getGroup(inputGroup->getName()), + m_shape.getGeometry().getBlueprintTopology())); // Transform the coordinates of the linearized mesh. applyTransforms(); } - else if ( geometryFormat == "sphere3D" ) + else if(geometryFormat == "hex3D") + { + const auto& hex = geometry.getHex(); + axom::StackArray tets; + hex.triangulate(tets); + + const axom::IndexType tetCount = HexType::NUM_TRIANGULATE; + const axom::IndexType nodeCount = HexType::NUM_TRIANGULATE * 4; + axom::Array nodeCoords(nodeCount, 3); + auto nodeCoordsView = nodeCoords.view(); + axom::Array connectivity(tetCount, 4); + auto connectivityView = connectivity.view(); + // NOTE: This is not much computation, so just run on host. + axom::for_all( + tetCount, + AXOM_LAMBDA(axom::IndexType iTet) { + const auto& tet = tets[iTet]; + for(int i = 0; i < 4; ++i) + { + axom::IndexType iNode = iTet * 4 + i; + const auto& coords = tet[i]; + nodeCoordsView[iNode][0] = coords[0]; + nodeCoordsView[iNode][1] = coords[1]; + nodeCoordsView[iNode][2] = coords[2]; + connectivityView[iTet][i] = iNode; + } + }); + + TetMesh* tetMesh = nullptr; + if(m_sidreGroup != nullptr) + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + m_sidreGroup, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + else + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + tetMesh->appendNodes((double*)nodeCoords.data(), nodeCoords.shape()[0]); + tetMesh->appendCells(connectivity.data(), connectivity.shape()[0]); + m_meshRep.reset(tetMesh); + + applyTransforms(); + } + else if(geometryFormat == "sphere3D") { /* Discretize the sphere into m_meshRep and apply transforms: @@ -139,7 +197,6 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() is a problem. */ const auto& sphere = geometry.getSphere(); - using TetType = axom::primal::Tetrahedron; axom::Array octs; int octCount = 0; axom::quest::discretize(sphere, geometry.getLevelOfRefinement(), octs, octCount); @@ -151,7 +208,7 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() axom::Array nodeCoords(nodeCount, nodeCount); axom::Array connectivity( - axom::StackArray{tetCount, NODES_PER_TET}); + axom::StackArray {tetCount, NODES_PER_TET}); auto nodeCoordsView = nodeCoords.view(); auto connectivityView = connectivity.view(); @@ -160,12 +217,12 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() AXOM_LAMBDA(axom::IndexType octIdx) { TetType tetsInOct[TETS_PER_OCT]; axom::primal::split(octs[octIdx], tetsInOct); - for ( int iTet = 0; iTet < TETS_PER_OCT; ++iTet ) + for(int iTet = 0; iTet < TETS_PER_OCT; ++iTet) { - axom::IndexType tetIdx = octIdx*TETS_PER_OCT + iTet; - for ( int iNode = 0; iNode < NODES_PER_TET; ++iNode ) + axom::IndexType tetIdx = octIdx * TETS_PER_OCT + iTet; + for(int iNode = 0; iNode < NODES_PER_TET; ++iNode) { - axom::IndexType nodeIdx = tetIdx*NODES_PER_TET + iNode; + axom::IndexType nodeIdx = tetIdx * NODES_PER_TET + iNode; nodeCoordsView[nodeIdx] = tetsInOct[iTet][iNode]; connectivityView[tetIdx][iNode] = nodeIdx; } @@ -173,22 +230,14 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() }); TetMesh* tetMesh = nullptr; - if (m_sidreGroup != nullptr) + if(m_sidreGroup != nullptr) { - tetMesh = new TetMesh( - 3, - axom::mint::CellType::TET, - m_sidreGroup, - nodeCount, - tetCount); + tetMesh = + new TetMesh(3, axom::mint::CellType::TET, m_sidreGroup, nodeCount, tetCount); } else { - tetMesh = new TetMesh( - 3, - axom::mint::CellType::TET, - nodeCount, - tetCount); + tetMesh = new TetMesh(3, axom::mint::CellType::TET, nodeCount, tetCount); } tetMesh->appendNodes((double*)nodeCoords.data(), nodeCount); tetMesh->appendCells(connectivity.data(), tetCount); @@ -196,7 +245,7 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() applyTransforms(); } - if (geometryFormat == "vor3D") + if(geometryFormat == "vor3D") { // Construct the tet m_meshRep from the volume-of-revolution. auto& vorGeom = m_shape.getGeometry(); @@ -223,29 +272,30 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() octCount, AXOM_LAMBDA(axom::IndexType iOct) { auto& oct = octsView[iOct]; - for ( int iVert = 0; iVert < OctType::NUM_VERTS; ++iVert ) + for(int iVert = 0; iVert < OctType::NUM_VERTS; ++iVert) { auto& newCoords = oct[iVert]; auto oldCoords = newCoords; - numerics::matrix_vector_multiply(rotate, oldCoords.data(), newCoords.data()); + numerics::matrix_vector_multiply(rotate, + oldCoords.data(), + newCoords.data()); newCoords.array() += translate.array(); } }); // Dump discretized octs as a tet mesh axom::mint::Mesh* mesh; - axom::quest::mesh_from_discretized_polyline( - octs.view(), - octCount, - polyline.size() - 1, - mesh); + axom::quest::mesh_from_discretized_polyline(octs.view(), + octCount, + polyline.size() - 1, + mesh); - if (m_sidreGroup) + if(m_sidreGroup) { // If using sidre, copy the tetMesh into sidre. auto* tetMesh = static_cast(mesh); - axom::mint::UnstructuredMesh* - siMesh = new axom::mint::UnstructuredMesh( + axom::mint::UnstructuredMesh* siMesh = + new axom::mint::UnstructuredMesh( tetMesh->getDimension(), tetMesh->getCellType(), m_sidreGroup, @@ -253,16 +303,18 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() tetMesh->getCoordsetName(), tetMesh->getNumberOfNodes(), tetMesh->getNumberOfCells()); - siMesh->appendNodes( tetMesh->getCoordinateArray(0) - , tetMesh->getCoordinateArray(1) - , tetMesh->getCoordinateArray(2) - , tetMesh->getNumberOfNodes() ); - siMesh->appendCells( tetMesh->getCellNodesArray() - , tetMesh->getNumberOfCells() ); + siMesh->appendNodes(tetMesh->getCoordinateArray(0), + tetMesh->getCoordinateArray(1), + tetMesh->getCoordinateArray(2), + tetMesh->getNumberOfNodes()); + siMesh->appendCells(tetMesh->getCellNodesArray(), + tetMesh->getNumberOfCells()); m_meshRep.reset(siMesh); delete mesh; mesh = nullptr; - } else { + } + else + { m_meshRep.reset(mesh); } @@ -270,7 +322,7 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() applyTransforms(); } - if (m_meshRep) + if(m_meshRep) { return m_meshRep; } @@ -380,9 +432,8 @@ void DiscreteShape::applyTransforms() const int numSurfaceVertices = m_meshRep->getNumberOfNodes(); double* x = m_meshRep->getCoordinateArray(mint::X_COORDINATE); double* y = m_meshRep->getCoordinateArray(mint::Y_COORDINATE); - double* z = spaceDim > 2 - ? m_meshRep->getCoordinateArray(mint::Z_COORDINATE) - : nullptr; + double* z = spaceDim > 2 ? m_meshRep->getCoordinateArray(mint::Z_COORDINATE) + : nullptr; double xformed[4]; for(int i = 0; i < numSurfaceVertices; ++i) @@ -404,7 +455,8 @@ numerics::Matrix DiscreteShape::getTransforms() const const auto identity4x4 = numerics::Matrix::identity(4); numerics::Matrix transformation(identity4x4); auto& geometryOperator = m_shape.getGeometry().getGeometryOperator(); - if (geometryOperator) { + if(geometryOperator) + { auto composite = std::dynamic_pointer_cast(geometryOperator); if(composite) @@ -433,7 +485,7 @@ numerics::Matrix DiscreteShape::getTransforms() const { internal::AffineMatrixVisitor visitor; geometryOperator->accept(visitor); - if (visitor.isValid()) + if(visitor.isValid()) { transformation = visitor.getMatrix(); } @@ -448,16 +500,16 @@ numerics::Matrix DiscreteShape::vorAxisRotMatrix(const Vector3D& dir) // Note that the rotation matrix is not unique. static const Vector3D x {1.0, 0.0, 0.0}; Vector3D a = dir.unitVector(); - Vector3D u; // Rotation vector, the cross product of x and a. - numerics::cross_product( x.data(), a.data(), u.data() ); + Vector3D u; // Rotation vector, the cross product of x and a. + numerics::cross_product(x.data(), a.data(), u.data()); double sinT = u.norm(); - double cosT = numerics::dot_product( x.data(), a.data(), 3 ); + double cosT = numerics::dot_product(x.data(), a.data(), 3); double ccosT = 1 - cosT; // Degenerate case with angle near 0 or pi. - if (utilities::isNearlyEqual(sinT, 0.0)) + if(utilities::isNearlyEqual(sinT, 0.0)) { - if (cosT > 0) + if(cosT > 0) { return numerics::Matrix::identity(3); } @@ -471,15 +523,15 @@ numerics::Matrix DiscreteShape::vorAxisRotMatrix(const Vector3D& dir) u = u.unitVector(); numerics::Matrix rot(3, 3, 0.0); - rot(0, 0) = u[0]*u[0] * ccosT + cosT; - rot(0, 1) = u[0]*u[1] * ccosT - u[2]*sinT; - rot(0, 2) = u[0]*u[2] * ccosT + u[1]*sinT; - rot(1, 0) = u[1]*u[0] * ccosT + u[2]*sinT; - rot(1, 1) = u[1]*u[1] * ccosT + cosT; - rot(1, 2) = u[1]*u[2] * ccosT - u[0]*sinT; - rot(2, 0) = u[0]*u[2] * ccosT - u[1]*sinT; - rot(2, 1) = u[1]*u[2] * ccosT + u[0]*sinT; - rot(2, 2) = u[2]*u[2] * ccosT + cosT; + rot(0, 0) = u[0] * u[0] * ccosT + cosT; + rot(0, 1) = u[0] * u[1] * ccosT - u[2] * sinT; + rot(0, 2) = u[0] * u[2] * ccosT + u[1] * sinT; + rot(1, 0) = u[1] * u[0] * ccosT + u[2] * sinT; + rot(1, 1) = u[1] * u[1] * ccosT + cosT; + rot(1, 2) = u[1] * u[2] * ccosT - u[0] * sinT; + rot(2, 0) = u[2] * u[0] * ccosT - u[1] * sinT; + rot(2, 1) = u[2] * u[1] * ccosT + u[0] * sinT; + rot(2, 2) = u[2] * u[2] * ccosT + cosT; return rot; } @@ -547,7 +599,7 @@ std::string DiscreteShape::resolvePath() const void DiscreteShape::setParentGroup(axom::sidre::Group* parentGroup) { - if (parentGroup) + if(parentGroup) { std::ostringstream os; os << this; @@ -556,5 +608,5 @@ void DiscreteShape::setParentGroup(axom::sidre::Group* parentGroup) } } -} // namespace klee +} // namespace quest } // namespace axom diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index 683ba7a6f4..5493bf9b5f 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -13,7 +13,7 @@ #include "axom/mint/mesh/Mesh.hpp" #if defined(AXOM_USE_MPI) -#include "mpi.h" + #include "mpi.h" #endif namespace axom @@ -29,7 +29,6 @@ namespace quest class DiscreteShape { public: - /// Refinement type. using RefinementType = enum { RefinementUniformSegments, RefinementDynamic }; @@ -37,6 +36,7 @@ class DiscreteShape using Vector3D = axom::primal::Vector; using TetType = axom::primal::Tetrahedron; using OctType = axom::primal::Octahedron; + using HexType = axom::primal::Hexahedron; static constexpr int DEFAULT_SAMPLES_PER_KNOT_SPAN {25}; static constexpr double MINIMUM_PERCENT_ERROR {0.}; @@ -91,17 +91,14 @@ class DiscreteShape /** @brief Set the MPI communicator used when reading C2C files. */ - void setMPICommunicator(MPI_Comm comm) - { - m_comm = comm; - } + void setMPICommunicator(MPI_Comm comm) { m_comm = comm; } #endif /*! Get the name of this shape. \return the shape's name */ - const axom::klee::Shape &getShape() const { return m_shape; } + const axom::klee::Shape& getShape() const { return m_shape; } /*! \brief Get the discrete mesh representation. @@ -171,7 +168,7 @@ class DiscreteShape void clearInternalData(); }; -} // namespace klee +} // namespace quest } // namespace axom #endif diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index fcca22e760..58d5633e83 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -2024,10 +2024,10 @@ class IntersectionShaper : public Shaper return volFrac; } - bool surfaceMeshIsTet() const { + bool surfaceMeshIsTet() const + { bool isTet = m_surfaceMesh != nullptr && - m_surfaceMesh->getDimension() == 3 && - !m_surfaceMesh->hasMixedCellTypes() && + m_surfaceMesh->getDimension() == 3 && !m_surfaceMesh->hasMixedCellTypes() && m_surfaceMesh->getCellType() == mint::TET; return isTet; } diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index fb66382fba..72769a59b7 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -305,7 +305,7 @@ class InOutSampler GeometricBoundingBox m_bbox; mint::Mesh* m_surfaceMesh {nullptr}; InOutOctreeType* m_octree {nullptr}; -}; // class InOutSampler +}; // class InOutSampler } // end namespace shaping @@ -412,13 +412,15 @@ class SamplingShaper : public Shaper switch(shapeDimension) { case klee::Dimensions::Two: - m_inoutSampler2D = new shaping::InOutSampler<2>(shapeName, m_surfaceMesh.get()); + m_inoutSampler2D = + new shaping::InOutSampler<2>(shapeName, m_surfaceMesh.get()); m_inoutSampler2D->computeBounds(); m_inoutSampler2D->initSpatialIndex(this->m_vertexWeldThreshold); break; case klee::Dimensions::Three: - m_inoutSampler3D = new shaping::InOutSampler<3>(shapeName, m_surfaceMesh.get()); + m_inoutSampler3D = + new shaping::InOutSampler<3>(shapeName, m_surfaceMesh.get()); m_inoutSampler3D->computeBounds(); m_inoutSampler3D->initSpatialIndex(this->m_vertexWeldThreshold); break; diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 3f13172dc6..c2effa71da 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -92,8 +92,8 @@ void Shaper::setRefinementType(Shaper::RefinementType t) bool Shaper::isValidFormat(const std::string& format) const { return (format == "stl" || format == "proe" || format == "c2c" || - format == "memory-blueprint" || format == "sphere3D" || - format == "vor3D" || format == "none"); + format == "memory-blueprint" || format == "hex3D" || + format == "sphere3D" || format == "vor3D" || format == "none"); } void Shaper::loadShape(const klee::Shape& shape) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 00d0cf82ea..74fcf5ae2e 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -252,7 +252,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) # 3D shaping tests. No 2D shaping in this feature, at least for now. set(_nranks 1) - set(_testshapes "tetmesh" "sphere" "cyl" "cone" "vor") + set(_testshapes "tetmesh" "hex" "sphere" "cyl" "cone" "vor") foreach(_testshape ${_testshapes}) set(_testname "quest_shape_in_memory_${_testshape}") axom_add_test( diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 67d79c90a5..0507f38996 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -91,7 +91,12 @@ struct Input // The shape to run. std::string testShape {"tetmesh"}; // The shapes this example is set up to run. - const std::set availableShapes {"tetmesh", "sphere", "cyl", "cone", "vor"}; + const std::set availableShapes {"tetmesh", + "sphere", + "cyl", + "cone", + "vor", + "hex"}; ShapingMethod shapingMethod {ShapingMethod::Sampling}; RuntimePolicy policy {RuntimePolicy::seq}; @@ -382,7 +387,7 @@ struct Input slic::setLoggingMsgLevel(m_verboseOutput ? slic::message::Debug : slic::message::Info); } -}; // struct Input +}; // struct Input Input params; /** @@ -491,33 +496,50 @@ void finalizeLogger() // Single triangle ShapeSet. axom::klee::ShapeSet create2DShapeSet(sidre::DataStore& ds) { - sidre::Group *meshGroup = ds.getRoot()->createGroup("triangleMesh"); + sidre::Group* meshGroup = ds.getRoot()->createGroup("triangleMesh"); AXOM_UNUSED_VAR(meshGroup); // variable is only referenced in debug configs const std::string topo = "mesh"; const std::string coordset = "coords"; axom::mint::UnstructuredMesh triangleMesh( - 2, axom::mint::CellType::TRIANGLE, meshGroup, topo, coordset ); + 2, + axom::mint::CellType::TRIANGLE, + meshGroup, + topo, + coordset); double lll = 2.0; // Insert tet at origin. - triangleMesh.appendNode( 0.0, 0.0 ); - triangleMesh.appendNode( lll, 0.0 ); - triangleMesh.appendNode( 0.0, lll ); - axom::IndexType conn[3] = { 0, 1, 2 }; - triangleMesh.appendCell( conn ); + triangleMesh.appendNode(0.0, 0.0); + triangleMesh.appendNode(lll, 0.0); + triangleMesh.appendNode(0.0, lll); + axom::IndexType conn[3] = {0, 1, 2}; + triangleMesh.appendCell(conn); meshGroup->print(); SLIC_ASSERT(axom::mint::blueprint::isValidRootGroup(meshGroup)); - axom::klee::TransformableGeometryProperties prop{ + axom::klee::TransformableGeometryProperties prop { axom::klee::Dimensions::Two, axom::klee::LengthUnit::unspecified}; - axom::klee::Geometry triangleGeom(prop, triangleMesh.getSidreGroup(), topo, nullptr); + axom::klee::Geometry triangleGeom(prop, + triangleMesh.getSidreGroup(), + topo, + nullptr); std::vector shapes; - axom::klee::Shape triangleShape( "triangle", "AL", {}, {}, axom::klee::Geometry{prop, triangleMesh.getSidreGroup(), topo, nullptr} ); - shapes.push_back(axom::klee::Shape{"triangle", "AL", {}, {}, axom::klee::Geometry{prop, triangleMesh.getSidreGroup(), topo, nullptr}}); + axom::klee::Shape triangleShape( + "triangle", + "AL", + {}, + {}, + axom::klee::Geometry {prop, triangleMesh.getSidreGroup(), topo, nullptr}); + shapes.push_back(axom::klee::Shape { + "triangle", + "AL", + {}, + {}, + axom::klee::Geometry {prop, triangleMesh.getSidreGroup(), topo, nullptr}}); axom::klee::ShapeSet shapeSet; shapeSet.setShapes(shapes); @@ -528,26 +550,25 @@ axom::klee::ShapeSet create2DShapeSet(sidre::DataStore& ds) axom::klee::Shape createShape_Sphere() { - axom::primal::Sphere sphere{params.center.data(), params.radius}; + axom::primal::Sphere sphere {params.center.data(), params.radius}; - axom::klee::TransformableGeometryProperties prop{ + axom::klee::TransformableGeometryProperties prop { axom::klee::Dimensions::Three, axom::klee::LengthUnit::unspecified}; - SLIC_ASSERT( params.scaleFactors.empty() || params.scaleFactors.size() == 3 ); + SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); std::shared_ptr scaleOp; - if (!params.scaleFactors.empty()) + if(!params.scaleFactors.empty()) { - scaleOp = - std::make_shared(params.scaleFactors[0], - params.scaleFactors[1], - params.scaleFactors[2], - prop); + scaleOp = std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + prop); } const axom::IndexType levelOfRefinement = params.refinementLevel; axom::klee::Geometry sphereGeometry(prop, sphere, levelOfRefinement, scaleOp); - axom::klee::Shape sphereShape( "sphere", "AU", {}, {}, sphereGeometry ); + axom::klee::Shape sphereShape("sphere", "AU", {}, {}, sphereGeometry); return sphereShape; } @@ -555,92 +576,99 @@ axom::klee::Shape createShape_Sphere() axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) { // Shape a single tetrahedron. - sidre::Group *meshGroup = ds.getRoot()->createGroup("tetMesh"); + sidre::Group* meshGroup = ds.getRoot()->createGroup("tetMesh"); AXOM_UNUSED_VAR(meshGroup); // variable is only referenced in debug configs const std::string topo = "mesh"; const std::string coordset = "coords"; axom::mint::UnstructuredMesh tetMesh( - 3, axom::mint::CellType::TET, meshGroup, topo, coordset ); + 3, + axom::mint::CellType::TET, + meshGroup, + topo, + coordset); double lll = 4.0; // Insert tet at origin. - tetMesh.appendNode( 0.0, 0.0, 0.0 ); - tetMesh.appendNode( lll, 0.0, 0.0 ); - tetMesh.appendNode( 0.0, lll, 0.0 ); - tetMesh.appendNode( 0.0, 0.0, lll ); - tetMesh.appendNode( lll, lll, 0.0 ); - axom::IndexType conn0[4] = { 0, 1, 2, 3 }; - tetMesh.appendCell( conn0 ); - axom::IndexType conn1[4] = { 4, 1, 2, 3 }; - tetMesh.appendCell( conn1 ); + tetMesh.appendNode(0.0, 0.0, 0.0); + tetMesh.appendNode(lll, 0.0, 0.0); + tetMesh.appendNode(0.0, lll, 0.0); + tetMesh.appendNode(0.0, 0.0, lll); + tetMesh.appendNode(lll, lll, 0.0); + axom::IndexType conn0[4] = {0, 1, 2, 3}; + tetMesh.appendCell(conn0); + axom::IndexType conn1[4] = {4, 1, 2, 3}; + tetMesh.appendCell(conn1); SLIC_ASSERT(axom::mint::blueprint::isValidRootGroup(meshGroup)); - axom::klee::TransformableGeometryProperties prop{ + axom::klee::TransformableGeometryProperties prop { axom::klee::Dimensions::Three, axom::klee::LengthUnit::unspecified}; - SLIC_ASSERT( params.scaleFactors.empty() || params.scaleFactors.size() == 3 ); + SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); std::shared_ptr scaleOp; - if (!params.scaleFactors.empty()) + if(!params.scaleFactors.empty()) { - scaleOp = - std::make_shared(params.scaleFactors[0], - params.scaleFactors[1], - params.scaleFactors[2], - prop); + scaleOp = std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + prop); } - axom::klee::Geometry tetMeshGeometry( prop, tetMesh.getSidreGroup(), topo, {scaleOp} ); - axom::klee::Shape tetShape( "tetmesh", "TETMESH", {}, {}, tetMeshGeometry ); + axom::klee::Geometry tetMeshGeometry(prop, + tetMesh.getSidreGroup(), + topo, + {scaleOp}); + axom::klee::Shape tetShape("tetmesh", "TETMESH", {}, {}, tetMeshGeometry); return tetShape; } -axom::klee::Geometry createGeometry_Vor( - axom::primal::Point& vorBase, - axom::primal::Vector& vorDirection, - axom::Array& discreteFunction ) +axom::klee::Geometry createGeometry_Vor(axom::primal::Point& vorBase, + axom::primal::Vector& vorDirection, + axom::Array& discreteFunction) { - axom::klee::TransformableGeometryProperties prop{ + axom::klee::TransformableGeometryProperties prop { axom::klee::Dimensions::Three, axom::klee::LengthUnit::unspecified}; - SLIC_ASSERT( params.scaleFactors.empty() || params.scaleFactors.size() == 3 ); + SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); std::shared_ptr scaleOp; - if (!params.scaleFactors.empty()) + if(!params.scaleFactors.empty()) { - scaleOp = - std::make_shared(params.scaleFactors[0], - params.scaleFactors[1], - params.scaleFactors[2], - prop); + scaleOp = std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + prop); } const axom::IndexType levelOfRefinement = params.refinementLevel; - axom::klee::Geometry vorGeometry(prop, discreteFunction, - vorBase, vorDirection, - levelOfRefinement, scaleOp); + axom::klee::Geometry vorGeometry(prop, + discreteFunction, + vorBase, + vorDirection, + levelOfRefinement, + scaleOp); return vorGeometry; } axom::klee::Shape createShape_Vor() { Point3D vorBase {params.center.data()}; - axom::primal::Vector vorDirection{params.direction.data()}; + axom::primal::Vector vorDirection {params.direction.data()}; axom::Array discreteFunction({3, 2}, axom::ArrayStrideOrder::ROW); discreteFunction[0][0] = 0.0; discreteFunction[0][1] = 1.0; - discreteFunction[1][0] = 0.5*params.length; + discreteFunction[1][0] = 0.5 * params.length; discreteFunction[1][1] = 0.8; discreteFunction[2][0] = params.length; discreteFunction[2][1] = 1.0; - axom::klee::Geometry vorGeometry = createGeometry_Vor( - vorBase, vorDirection, discreteFunction); + axom::klee::Geometry vorGeometry = + createGeometry_Vor(vorBase, vorDirection, discreteFunction); - axom::klee::Shape vorShape( "vor", "VOR", {}, {}, vorGeometry ); + axom::klee::Shape vorShape("vor", "VOR", {}, {}, vorGeometry); return vorShape; } @@ -648,7 +676,7 @@ axom::klee::Shape createShape_Vor() axom::klee::Shape createShape_Cylinder() { Point3D vorBase {params.center.data()}; - axom::primal::Vector vorDirection{params.direction.data()}; + axom::primal::Vector vorDirection {params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); double radius = params.radius; double height = params.length; @@ -657,10 +685,10 @@ axom::klee::Shape createShape_Cylinder() discreteFunction[1][0] = height; discreteFunction[1][1] = radius; - axom::klee::Geometry vorGeometry = createGeometry_Vor( - vorBase, vorDirection, discreteFunction); + axom::klee::Geometry vorGeometry = + createGeometry_Vor(vorBase, vorDirection, discreteFunction); - axom::klee::Shape vorShape( "cyl", "CYL", {}, {}, vorGeometry ); + axom::klee::Shape vorShape("cyl", "CYL", {}, {}, vorGeometry); return vorShape; } @@ -668,7 +696,7 @@ axom::klee::Shape createShape_Cylinder() axom::klee::Shape createShape_Cone() { Point3D vorBase {params.center.data()}; - axom::primal::Vector vorDirection{params.direction.data()}; + axom::primal::Vector vorDirection {params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); double baseRadius = params.radius; double topRadius = params.radius2; @@ -678,40 +706,77 @@ axom::klee::Shape createShape_Cone() discreteFunction[1][0] = height; discreteFunction[1][1] = topRadius; - axom::klee::Geometry vorGeometry = createGeometry_Vor( - vorBase, vorDirection, discreteFunction); + axom::klee::Geometry vorGeometry = + createGeometry_Vor(vorBase, vorDirection, discreteFunction); - axom::klee::Shape vorShape( "cone", "CONE", {}, {}, vorGeometry ); + axom::klee::Shape vorShape("cone", "CONE", {}, {}, vorGeometry); return vorShape; } +axom::klee::Shape createShape_Hex() +{ + axom::klee::TransformableGeometryProperties prop { + axom::klee::Dimensions::Three, + axom::klee::LengthUnit::unspecified}; + + SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); + std::shared_ptr scaleOp; + if(!params.scaleFactors.empty()) + { + scaleOp = std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + prop); + } + + const double len = params.length; + const Point3D p {0.0, 0.0, 0.0}; + const Point3D q {len, 0.0, 0.0}; + const Point3D r {len, 1.0, 0.0}; + const Point3D s {0.0, 1.0, 0.0}; + const Point3D t {0.0, 0.0, 1.0}; + const Point3D u {len, 0.0, 1.0}; + const Point3D v {len, 1.0, 1.0}; + const Point3D w {0.0, 1.0, 1.0}; + const primal::Hexahedron hex {p, q, r, s, t, u, v, w}; + + axom::klee::Geometry hexGeometry(prop, hex, scaleOp); + axom::klee::Shape hexShape("hex", "HEX", {}, {}, hexGeometry); + + return hexShape; +} + //!@brief Create a ShapeSet with a single shape. axom::klee::ShapeSet createShapeSet(const axom::klee::Shape& shape) { axom::klee::ShapeSet shapeSet; - shapeSet.setShapes(std::vector{shape}); + shapeSet.setShapes(std::vector {shape}); shapeSet.setDimensions(axom::klee::Dimensions::Three); return shapeSet; } -double volumeOfTetMesh(const axom::mint::UnstructuredMesh& tetMesh) +double volumeOfTetMesh( + const axom::mint::UnstructuredMesh& tetMesh) { using TetType = axom::primal::Tetrahedron; -{std::ofstream os("tets.js"); tetMesh.getSidreGroup()->print(os);} - axom::StackArray nodesShape{tetMesh.getNumberOfNodes()}; + { + std::ofstream os("tets.js"); + tetMesh.getSidreGroup()->print(os); + } + axom::StackArray nodesShape {tetMesh.getNumberOfNodes()}; axom::ArrayView x(tetMesh.getCoordinateArray(0), nodesShape); axom::ArrayView y(tetMesh.getCoordinateArray(1), nodesShape); axom::ArrayView z(tetMesh.getCoordinateArray(2), nodesShape); const axom::IndexType cellCount = tetMesh.getNumberOfCells(); axom::Array tetVolumes(cellCount, cellCount); double meshVolume = 0.0; - for ( axom::IndexType ic = 0; ic < cellCount; ++ic ) + for(axom::IndexType ic = 0; ic < cellCount; ++ic) { const axom::IndexType* nodeIds = tetMesh.getCellNodeIDs(ic); TetType tet; - for ( int j = 0; j < 4; ++j ) + for(int j = 0; j < 4; ++j) { auto cornerNodeId = nodeIds[j]; tet[j][0] = x[cornerNodeId]; @@ -732,14 +797,15 @@ double volumeOfTetMesh(const axom::mint::UnstructuredMesh -axom::sidre::View* getElementVolumes( sidre::MFEMSidreDataCollection* dc, - const std::string& volFieldName = std::string("elementVolumes") ) +axom::sidre::View* getElementVolumes( + sidre::MFEMSidreDataCollection* dc, + const std::string& volFieldName = std::string("elementVolumes")) { using HexahedronType = axom::primal::Hexahedron; axom::setDefaultAllocator(::getUmpireDeviceId()); axom::sidre::View* volSidreView = dc->GetNamedBuffer(volFieldName); - if (volSidreView == nullptr) + if(volSidreView == nullptr) { mfem::Mesh* mesh = dc->GetMesh(); const axom::IndexType cellCount = mesh->GetNE(); @@ -774,13 +840,14 @@ axom::sidre::View* getElementVolumes( sidre::MFEMSidreDataCollection* dc, // Set vertex coords to zero if within threshold. // (I don't know why we do this. I'm following examples.) - axom::ArrayView flatCoordsView((double*)vertCoords.data(), - vertCoords.size()*Point3D::dimension()); + axom::ArrayView flatCoordsView( + (double*)vertCoords.data(), + vertCoords.size() * Point3D::dimension()); assert(flatCoordsView.size() == cellCount * NUM_VERTS_PER_HEX * 3); axom::for_all( - cellCount*3, + cellCount * 3, AXOM_LAMBDA(axom::IndexType i) { - if ( axom::utilities::isNearlyEqual(flatCoordsView[i], 0.0, ZERO_THRESHOLD) ) + if(axom::utilities::isNearlyEqual(flatCoordsView[i], 0.0, ZERO_THRESHOLD)) { flatCoordsView[i] = 0.0; } @@ -804,7 +871,8 @@ axom::sidre::View* getElementVolumes( sidre::MFEMSidreDataCollection* dc, // Allocate and populate cell volumes. volSidreView = dc->AllocNamedBuffer(volFieldName, cellCount); - axom::ArrayView volView( volSidreView->getData(), volSidreView->getNumElements() ); + axom::ArrayView volView(volSidreView->getData(), + volSidreView->getNumElements()); axom::for_all( cellCount, AXOM_LAMBDA(axom::IndexType cellIdx) { @@ -818,19 +886,21 @@ axom::sidre::View* getElementVolumes( sidre::MFEMSidreDataCollection* dc, /*! @brief Return global sum of volume of the given material. */ -template -double sumMaterialVolumes( sidre::MFEMSidreDataCollection* dc, - const std::string& material ) +template +double sumMaterialVolumes(sidre::MFEMSidreDataCollection* dc, + const std::string& material) { mfem::Mesh* mesh = dc->GetMesh(); int const cellCount = mesh->GetNE(); // Get cell volumes from dc. axom::sidre::View* elementVols = getElementVolumes(dc); - axom::ArrayView elementVolsView(elementVols->getData(), elementVols->getNumElements()); + axom::ArrayView elementVolsView(elementVols->getData(), + elementVols->getNumElements()); // Get material volume fractions - const std::string materialFieldName = axom::fmt::format("vol_frac_{}", material); + const std::string materialFieldName = + axom::fmt::format("vol_frac_{}", material); mfem::GridFunction* volFracGf = dc->GetField(materialFieldName); axom::quest::GridFunctionView volFracView(volFracGf); @@ -895,30 +965,35 @@ int main(int argc, char** argv) // Create simple ShapeSet for the example. //--------------------------------------------------------------------------- axom::klee::ShapeSet shapeSet; - switch (params.boxDim) { + switch(params.boxDim) + { case 2: shapeSet = create2DShapeSet(ds); break; case 3: - if (params.testShape == "tetmesh") + if(params.testShape == "tetmesh") { - shapeSet = createShapeSet( createShape_TetMesh(ds) ); + shapeSet = createShapeSet(createShape_TetMesh(ds)); } - else if (params.testShape == "sphere") + else if(params.testShape == "hex") { - shapeSet = createShapeSet( createShape_Sphere() ); + shapeSet = createShapeSet(createShape_Hex()); } - else if (params.testShape == "cyl") + else if(params.testShape == "sphere") { - shapeSet = createShapeSet( createShape_Cylinder() ); + shapeSet = createShapeSet(createShape_Sphere()); } - else if (params.testShape == "cone") + else if(params.testShape == "cyl") { - shapeSet = createShapeSet( createShape_Cone() ); + shapeSet = createShapeSet(createShape_Cylinder()); } - else if (params.testShape == "vor") + else if(params.testShape == "cone") { - shapeSet = createShapeSet( createShape_Vor() ); + shapeSet = createShapeSet(createShape_Cone()); + } + else if(params.testShape == "vor") + { + shapeSet = createShapeSet(createShape_Vor()); } break; } @@ -933,14 +1008,14 @@ int main(int argc, char** argv) std::dynamic_pointer_cast>( dShape.createMeshRepresentation()); SLIC_INFO(axom::fmt::format( - "{:-^80}", - axom::fmt::format("Shape '{}' discrete geometry has {} cells", - shape.getName(), - dMesh->getNumberOfCells()))); + "{:-^80}", + axom::fmt::format("Shape '{}' discrete geometry has {} cells", + shape.getName(), + dMesh->getNumberOfCells()))); discreteShapeMeshes.push_back(dMesh); - if (!params.outputFile.empty()) + if(!params.outputFile.empty()) { std::string shapeFileName = params.outputFile + ".shape"; conduit::Node tmpNode, info; @@ -979,7 +1054,8 @@ int main(int argc, char** argv) shapingDC.SetMeshNodesName("positions"); // With MPI, loadComputationalMesh returns a parallel mesh. - mfem::ParMesh* parallelMesh = dynamic_cast(originalMeshDC->GetMesh()); + mfem::ParMesh* parallelMesh = + dynamic_cast(originalMeshDC->GetMesh()); shapingMesh = (parallelMesh != nullptr) ? new mfem::ParMesh(*parallelMesh) : new mfem::Mesh(*originalMeshDC->GetMesh()); @@ -1168,29 +1244,32 @@ int main(int argc, char** argv) int failCounts = 0; - auto* volFracGroups = shapingDC.GetBPGroup()->getGroup("matsets/material/volume_fractions"); + auto* volFracGroups = + shapingDC.GetBPGroup()->getGroup("matsets/material/volume_fractions"); //--------------------------------------------------------------------------- // Correctness test: volume fractions should be in [0,1]. //--------------------------------------------------------------------------- RAJA::ReduceSum rangeViolationCount(0); - for (axom::sidre::Group& materialGroup : volFracGroups->groups()) + for(axom::sidre::Group& materialGroup : volFracGroups->groups()) { axom::sidre::View* values = materialGroup.getView("value"); double* volFracData = values->getArray(); axom::ArrayView volFracDataView(volFracData, cellCount); - axom::for_all(cellCount, - AXOM_LAMBDA(axom::IndexType i) { - bool bad = volFracDataView[i] < 0.0 || volFracDataView[i] > 1.0; - rangeViolationCount += bad; }); + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType i) { + bool bad = volFracDataView[i] < 0.0 || volFracDataView[i] > 1.0; + rangeViolationCount += bad; + }); } failCounts += (rangeViolationCount.get() != 0); SLIC_INFO(axom::fmt::format( - "{:-^80}", - axom::fmt::format("Count of volume fractions outside of [0,1]: {}.", - rangeViolationCount.get()))); + "{:-^80}", + axom::fmt::format("Count of volume fractions outside of [0,1]: {}.", + rangeViolationCount.get()))); slic::flushStreams(); //--------------------------------------------------------------------------- @@ -1199,14 +1278,14 @@ int main(int argc, char** argv) axom::Array volSums(cellCount); volSums.fill(0.0); axom::ArrayView volSumsView = volSums.view(); - for (axom::sidre::Group& materialGroup : volFracGroups->groups()) + for(axom::sidre::Group& materialGroup : volFracGroups->groups()) { axom::sidre::View* values = materialGroup.getView("value"); double* volFracData = values->getArray(); axom::ArrayView volFracDataView(volFracData, cellCount); - axom::for_all(cellCount, - AXOM_LAMBDA(axom::IndexType i) { - volSumsView[i] += volFracDataView[i]; }); + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType i) { volSumsView[i] += volFracDataView[i]; }); } RAJA::ReduceSum nonUnitSums(0); axom::for_all( @@ -1219,9 +1298,9 @@ int main(int argc, char** argv) failCounts += (nonUnitSums.get() != 0); SLIC_INFO(axom::fmt::format( - "{:-^80}", - axom::fmt::format("Count non-unit volume fraction sums: {}.", - nonUnitSums.get()))); + "{:-^80}", + axom::fmt::format("Count non-unit volume fraction sums: {}.", + nonUnitSums.get()))); slic::flushStreams(); //--------------------------------------------------------------------------- @@ -1236,27 +1315,29 @@ int main(int argc, char** argv) dShape.createMeshRepresentation()); double shapeMeshVol = volumeOfTetMesh(*shapeMesh); SLIC_INFO(axom::fmt::format( - "{:-^80}", - axom::fmt::format("Shape '{}' discrete geometry has {} cells", - shape.getName(), - shapeMesh->getNumberOfCells()))); + "{:-^80}", + axom::fmt::format("Shape '{}' discrete geometry has {} cells", + shape.getName(), + shapeMesh->getNumberOfCells()))); const std::string& materialName = shape.getMaterial(); - double shapeVol = sumMaterialVolumes( &shapingDC, materialName ); + double shapeVol = + sumMaterialVolumes(&shapingDC, materialName); double diff = shapeVol - shapeMeshVol; bool err = !axom::utilities::isNearlyEqual(shapeVol, shapeMeshVol); failCounts += err; SLIC_INFO(axom::fmt::format( - "{:-^80}", - axom::fmt::format("Material '{}' in shape '{}' has volume {} vs {}, diff of {}, {}.", - materialName, - shape.getName(), - shapeVol, - shapeMeshVol, - diff, - (err ? "ERROR" : "OK") ))); + "{:-^80}", + axom::fmt::format( + "Material '{}' in shape '{}' has volume {} vs {}, diff of {}, {}.", + materialName, + shape.getName(), + shapeVol, + shapeMeshVol, + diff, + (err ? "ERROR" : "OK")))); } slic::flushStreams(); ds.getRoot()->destroyGroupAndData("meshVerification"); @@ -1274,7 +1355,7 @@ int main(int argc, char** argv) } #ifdef MFEM_USE_MPI - if (!params.outputFile.empty()) + if(!params.outputFile.empty()) { std::string fileName = params.outputFile + ".volfracs"; shaper->getDC()->Save(fileName, sidre::Group::getDefaultIOProtocol()); From 931d1baadebae6606fc7225f54c0b4d2aacfaad7 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 13 Aug 2024 10:29:33 -0700 Subject: [PATCH 13/43] Set DiscreteShape default behavior for adaptive refinement of SOR. --- src/axom/quest/DiscreteShape.cpp | 5 ++++- src/axom/quest/DiscreteShape.hpp | 4 ++++ src/axom/quest/Shaper.cpp | 5 ++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index d9c237050b..42f123a703 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -84,6 +84,9 @@ DiscreteShape::DiscreteShape(const axom::klee::Shape& shape, axom::sidre::Group* parentGroup) : m_shape(shape) , m_sidreGroup(nullptr) + , m_refinementType(DiscreteShape::RefinementUniformSegments) + , m_percentError( + utilities::clampVal(0.0, MINIMUM_PERCENT_ERROR, MAXIMUM_PERCENT_ERROR)) { setPrefixPath(prefixPath); setParentGroup(parentGroup); @@ -330,7 +333,7 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() if(!m_shape.getGeometry().hasGeometry()) { // If shape has no geometry, there's nothing to discretize. - SLIC_WARNING( + SLIC_DEBUG( axom::fmt::format("Current shape '{}' of material '{}' has no geometry", m_shape.getName(), m_shape.getMaterial())); diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index 5493bf9b5f..d1c6339c87 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -51,6 +51,10 @@ class DiscreteShape with a relative path. @param parentGroup Group under which to put the discrete mesh. If null, don't use sidre. + + Refinement type is set to DiscreteShape::RefinementUniformSegments + and percent erro is set to 0. See setPercentError() and + setRefinementType(). */ DiscreteShape(const axom::klee::Shape& shape, const std::string& prefixPath = {}, diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index c2effa71da..1604024566 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -126,7 +126,10 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, DiscreteShape discreteShape(shape, m_shapeSet.getPath()); discreteShape.setVertexWeldThreshold(m_vertexWeldThreshold); discreteShape.setRefinementType(m_refinementType); - discreteShape.setPercentError(percentError); + if(percentError > 0) + { + discreteShape.setPercentError(percentError); + } m_surfaceMesh = discreteShape.createMeshRepresentation(); revolvedVolume = discreteShape.getRevolvedVolume(); } From 4bee1707478a1f020da492623283c4bc416ac443 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 13 Aug 2024 14:43:04 -0700 Subject: [PATCH 14/43] Fix mesh pointers that get passed into classes then deleted. Fix by changing the pointer references to shared_ptr references. This bug was recently created when factoring out the generation of the mesh so it can be saved outside the scope it was generated in. --- src/axom/quest/InOutOctree.hpp | 3 +- src/axom/quest/SamplingShaper.hpp | 13 +++--- src/axom/quest/detail/inout/MeshWrapper.hpp | 32 ++++++-------- .../quest/examples/containment_driver.cpp | 14 +++--- src/axom/quest/interface/inout.cpp | 16 ++++--- src/axom/quest/interface/inout.hpp | 3 +- src/axom/quest/tests/quest_initialize.cpp | 5 +-- .../quest/tests/quest_inout_interface.cpp | 11 ++--- src/axom/quest/tests/quest_inout_octree.cpp | 43 +++++++++---------- src/axom/quest/tests/quest_inout_quadtree.cpp | 33 +++++++------- 10 files changed, 83 insertions(+), 90 deletions(-) diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index 93a8862de9..58e861f90f 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -175,7 +175,8 @@ class InOutOctree : public spin::SpatialOctree * \note The InOutOctree modifies its mesh in an effort to repair common * problems. Please make sure to discard all old copies of the meshPtr. */ - InOutOctree(const GeometricBoundingBox& bb, SurfaceMesh*& meshPtr) + InOutOctree(const GeometricBoundingBox& bb, + std::shared_ptr& meshPtr) : SpatialOctreeType( GeometricBoundingBox(bb).scale(DEFAULT_BOUNDING_BOX_SCALE_FACTOR)) , m_meshWrapper(meshPtr) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 72769a59b7..360f1f0d79 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -80,14 +80,15 @@ class InOutSampler * * \note Does not take ownership of the surface mesh */ - InOutSampler(const std::string& shapeName, mint::Mesh* surfaceMesh) + InOutSampler(const std::string& shapeName, + std::shared_ptr surfaceMesh) : m_shapeName(shapeName) , m_surfaceMesh(surfaceMesh) { } ~InOutSampler() { delete m_octree; } - mint::Mesh* getSurfaceMesh() const { return m_surfaceMesh; } + std::shared_ptr getSurfaceMesh() const { return m_surfaceMesh; } /// Computes the bounding box of the surface mesh void computeBounds() @@ -303,7 +304,7 @@ class InOutSampler std::string m_shapeName; GeometricBoundingBox m_bbox; - mint::Mesh* m_surfaceMesh {nullptr}; + std::shared_ptr m_surfaceMesh {nullptr}; InOutOctreeType* m_octree {nullptr}; }; // class InOutSampler @@ -412,15 +413,13 @@ class SamplingShaper : public Shaper switch(shapeDimension) { case klee::Dimensions::Two: - m_inoutSampler2D = - new shaping::InOutSampler<2>(shapeName, m_surfaceMesh.get()); + m_inoutSampler2D = new shaping::InOutSampler<2>(shapeName, m_surfaceMesh); m_inoutSampler2D->computeBounds(); m_inoutSampler2D->initSpatialIndex(this->m_vertexWeldThreshold); break; case klee::Dimensions::Three: - m_inoutSampler3D = - new shaping::InOutSampler<3>(shapeName, m_surfaceMesh.get()); + m_inoutSampler3D = new shaping::InOutSampler<3>(shapeName, m_surfaceMesh); m_inoutSampler3D->computeBounds(); m_inoutSampler3D->initSpatialIndex(this->m_vertexWeldThreshold); break; diff --git a/src/axom/quest/detail/inout/MeshWrapper.hpp b/src/axom/quest/detail/inout/MeshWrapper.hpp index 1f72ab339d..72cf8244ed 100644 --- a/src/axom/quest/detail/inout/MeshWrapper.hpp +++ b/src/axom/quest/detail/inout/MeshWrapper.hpp @@ -81,7 +81,7 @@ class SimplexMeshWrapper static constexpr CellIndex NO_CELL = -1; protected: - SimplexMeshWrapper(SurfaceMesh*& meshPtr) + SimplexMeshWrapper(std::shared_ptr& meshPtr) : m_surfaceMesh(meshPtr) , m_vertexPositions(&m_vertexSet) , m_cellToVertexRelation() @@ -278,7 +278,7 @@ class SimplexMeshWrapper } protected: - SurfaceMesh*& m_surfaceMesh; // ref to pointer to allow changing the mesh + std::shared_ptr& m_surfaceMesh; // ref to pointer to allow changing the mesh MeshVertexSet m_vertexSet {0}; MeshElementSet m_elementSet {0}; @@ -312,7 +312,7 @@ class MeshWrapper<2> : public SimplexMeshWrapper<2, MeshWrapper<2>> public: /// \brief Constructor for a mesh wrapper */ - MeshWrapper(SurfaceMesh*& meshPtr) : Base(meshPtr) { } + MeshWrapper(std::shared_ptr& meshPtr) : Base(meshPtr) { } /** * \brief Helper function to compute the bounding box of an edge @@ -419,7 +419,7 @@ class MeshWrapper<2> : public SimplexMeshWrapper<2, MeshWrapper<2>> // Grab relation from mesh using UMesh = mint::UnstructuredMesh; axom::IndexType* vertIds = - static_cast(m_surfaceMesh)->getCellNodeIDs(i); + std::static_pointer_cast(m_surfaceMesh)->getCellNodeIDs(i); // Remap the vertex IDs for(int j = 0; j < NUM_EDGE_VERTS; ++j) @@ -442,9 +442,8 @@ class MeshWrapper<2> : public SimplexMeshWrapper<2, MeshWrapper<2>> m_cellToVertexRelation.bindIndices(static_cast(m_cv_data.size()), &m_cv_data); - // Delete old mesh, and NULL its pointer - delete m_surfaceMesh; - m_surfaceMesh = nullptr; + // Delete old mesh + m_surfaceMesh.reset(); m_meshWasReindexed = true; } @@ -454,8 +453,7 @@ class MeshWrapper<2> : public SimplexMeshWrapper<2, MeshWrapper<2>> { if(m_surfaceMesh != nullptr) { - delete m_surfaceMesh; - m_surfaceMesh = nullptr; + m_surfaceMesh.reset(); } using UMesh = mint::UnstructuredMesh; @@ -476,7 +474,7 @@ class MeshWrapper<2> : public SimplexMeshWrapper<2, MeshWrapper<2>> edgeMesh->appendCell(tv); } - m_surfaceMesh = edgeMesh; + m_surfaceMesh.reset(edgeMesh); } }; @@ -500,7 +498,7 @@ class MeshWrapper<3> : public SimplexMeshWrapper<3, MeshWrapper<3>> public: /// \brief Constructor for a mesh wrapper */ - MeshWrapper(SurfaceMesh*& meshPtr) : Base(meshPtr) { } + MeshWrapper(std::shared_ptr& meshPtr) : Base(meshPtr) { } /** * \brief Helper function to compute the bounding box of a triangle @@ -570,7 +568,7 @@ class MeshWrapper<3> : public SimplexMeshWrapper<3, MeshWrapper<3>> // Grab relation from mesh using UMesh = mint::UnstructuredMesh; axom::IndexType* vertIds = - static_cast(m_surfaceMesh)->getCellNodeIDs(i); + std::static_pointer_cast(m_surfaceMesh)->getCellNodeIDs(i); // Remap the vertex IDs for(int j = 0; j < NUM_TRI_VERTS; ++j) @@ -595,9 +593,8 @@ class MeshWrapper<3> : public SimplexMeshWrapper<3, MeshWrapper<3>> m_cellToVertexRelation.bindIndices(static_cast(m_cv_data.size()), &m_cv_data); - // Delete old mesh, and NULL its pointer - delete m_surfaceMesh; - m_surfaceMesh = nullptr; + // Delete old mesh + m_surfaceMesh.reset(); m_meshWasReindexed = true; } @@ -607,8 +604,7 @@ class MeshWrapper<3> : public SimplexMeshWrapper<3, MeshWrapper<3>> { if(m_surfaceMesh != nullptr) { - delete m_surfaceMesh; - m_surfaceMesh = nullptr; + m_surfaceMesh.reset(); } using UMesh = mint::UnstructuredMesh; @@ -629,7 +625,7 @@ class MeshWrapper<3> : public SimplexMeshWrapper<3, MeshWrapper<3>> triMesh->appendCell(tv); } - m_surfaceMesh = triMesh; + m_surfaceMesh.reset(triMesh); } }; diff --git a/src/axom/quest/examples/containment_driver.cpp b/src/axom/quest/examples/containment_driver.cpp index 4606b33ab7..7ef006f68d 100644 --- a/src/axom/quest/examples/containment_driver.cpp +++ b/src/axom/quest/examples/containment_driver.cpp @@ -64,7 +64,7 @@ class ContainmentDriver ~ContainmentDriver() { - delete m_surfaceMesh; + m_surfaceMesh.reset(); delete m_octree; } @@ -77,8 +77,8 @@ class ContainmentDriver reader.read(); // Create surface mesh - m_surfaceMesh = new UMesh(2, mint::SEGMENT); - reader.getLinearMeshUniform(static_cast(m_surfaceMesh), + m_surfaceMesh.reset(new UMesh(2, mint::SEGMENT)); + reader.getLinearMeshUniform(static_cast(m_surfaceMesh.get()), segmentsPerKnotSpan); } #else @@ -100,11 +100,11 @@ class ContainmentDriver reader.read(); // Create surface mesh - m_surfaceMesh = new UMesh(3, mint::TRIANGLE); - reader.getMesh(static_cast(m_surfaceMesh)); + m_surfaceMesh.reset(new UMesh(3, mint::TRIANGLE)); + reader.getMesh(static_cast(m_surfaceMesh.get())); } - mint::Mesh* getSurfaceMesh() const { return m_surfaceMesh; } + mint::Mesh* getSurfaceMesh() const { return m_surfaceMesh.get(); } int dimension() const { return DIM; } @@ -482,7 +482,7 @@ class ContainmentDriver } private: - mint::Mesh* m_surfaceMesh {nullptr}; + std::shared_ptr m_surfaceMesh {nullptr}; InOutOctreeType* m_octree {nullptr}; GeometricBoundingBox m_meshBB; GeometricBoundingBox m_queryBB; diff --git a/src/axom/quest/interface/inout.cpp b/src/axom/quest/interface/inout.cpp index af2521b840..05316e103b 100644 --- a/src/axom/quest/interface/inout.cpp +++ b/src/axom/quest/interface/inout.cpp @@ -92,7 +92,7 @@ struct InOutHelper */ int initialize(const std::string& file, MPI_Comm comm) { - mint::Mesh* mesh = nullptr; + mint::Mesh* tmpMeshPtr = nullptr; m_params.m_dimension = getDimension(); // load the mesh @@ -106,7 +106,7 @@ struct InOutHelper numerics::Matrix::identity(4), m_params.m_segmentsPerKnotSpan, m_params.m_vertexWeldThreshold, - mesh, + tmpMeshPtr, revolvedVolume, comm); #else @@ -116,12 +116,14 @@ struct InOutHelper #endif break; case 3: - rc = internal::read_stl_mesh(file, mesh, comm); + rc = internal::read_stl_mesh(file, tmpMeshPtr, comm); break; default: // no-op break; } + std::shared_ptr mesh(tmpMeshPtr); + if(rc != QUEST_INOUT_SUCCESS) { SLIC_WARNING("reading mesh from [" << file << "] failed!"); @@ -138,7 +140,7 @@ struct InOutHelper * * \sa inout_init */ - int initialize(mint::Mesh*& mesh, MPI_Comm comm) + int initialize(std::shared_ptr& mesh, MPI_Comm comm) { // initialize logger, if necessary internal::logger_init(m_state.m_logger_is_initialized, @@ -221,7 +223,7 @@ struct InOutHelper // deal with mesh if(m_state.m_should_delete_mesh) { - delete m_surfaceMesh; + m_surfaceMesh.reset(); } m_surfaceMesh = nullptr; @@ -294,7 +296,7 @@ struct InOutHelper } private: - mint::Mesh* m_surfaceMesh {nullptr}; + std::shared_ptr m_surfaceMesh {nullptr}; InOutOctree* m_inoutTree {nullptr}; GeometricBoundingBox m_meshBoundingBox; SpacePt m_meshCenterOfMass; @@ -360,7 +362,7 @@ int inout_init(const std::string& file, MPI_Comm comm) return rc; } -int inout_init(mint::Mesh*& mesh, MPI_Comm comm) +int inout_init(std::shared_ptr& mesh, MPI_Comm comm) { const int dim = inout_get_dimension(); int rc = QUEST_INOUT_FAILED; diff --git a/src/axom/quest/interface/inout.hpp b/src/axom/quest/interface/inout.hpp index e2d8284a3b..20ecbace65 100644 --- a/src/axom/quest/interface/inout.hpp +++ b/src/axom/quest/interface/inout.hpp @@ -14,6 +14,7 @@ // C/C++ includes #include +#include /*! * \file inout.hpp @@ -78,7 +79,7 @@ int inout_init(const std::string& file, MPI_Comm comm = MPI_COMM_SELF); * by welding vertices) and updates the \a mesh pointer. It is the user's * responsibility to update any other pointers to this same mesh. */ -int inout_init(mint::Mesh*& mesh, MPI_Comm comm = MPI_COMM_SELF); +int inout_init(std::shared_ptr& mesh, MPI_Comm comm = MPI_COMM_SELF); /*! * \brief Finalizes the inout query diff --git a/src/axom/quest/tests/quest_initialize.cpp b/src/axom/quest/tests/quest_initialize.cpp index f8f2f6c67f..cac810edb1 100644 --- a/src/axom/quest/tests/quest_initialize.cpp +++ b/src/axom/quest/tests/quest_initialize.cpp @@ -25,7 +25,8 @@ TEST(quest_initialize, inout_pointer_initialize) { int rc = quest::QUEST_INOUT_SUCCESS; - mint::Mesh* input_mesh = axom::quest::utilities::make_tetrahedron_mesh(); + std::shared_ptr input_mesh { + axom::quest::utilities::make_tetrahedron_mesh()}; // Note: the following call updates the input_mesh pointer #ifdef AXOM_USE_MPI @@ -43,8 +44,6 @@ TEST(quest_initialize, inout_pointer_initialize) rc = quest::inout_finalize(); EXPECT_EQ(quest::QUEST_INOUT_SUCCESS, rc); - - delete input_mesh; } // Test initializing quest signed_distance from a preloaded mesh diff --git a/src/axom/quest/tests/quest_inout_interface.cpp b/src/axom/quest/tests/quest_inout_interface.cpp index 25d3e337d8..3643a2b7ed 100644 --- a/src/axom/quest/tests/quest_inout_interface.cpp +++ b/src/axom/quest/tests/quest_inout_interface.cpp @@ -137,7 +137,7 @@ TYPED_TEST(InOutInterfaceTest, initialize_from_mesh) EXPECT_TRUE(axom::utilities::filesystem::pathExists(this->meshfile)); - axom::mint::Mesh* mesh {nullptr}; + axom::mint::Mesh* tmpMeshPtr {nullptr}; int rc = failCode; @@ -152,15 +152,18 @@ TYPED_TEST(InOutInterfaceTest, initialize_from_mesh) identity, segmentsPerKnotSpan, weldThreshold, - mesh, + tmpMeshPtr, revolvedVolume); #endif // AXOM_USE_C2C } else // DIM == 3 { - rc = axom::quest::internal::read_stl_mesh(this->meshfile, mesh); + rc = axom::quest::internal::read_stl_mesh(this->meshfile, tmpMeshPtr); } + std::shared_ptr mesh {tmpMeshPtr}; + tmpMeshPtr = nullptr; + EXPECT_EQ(0, rc); ASSERT_NE(nullptr, mesh); @@ -179,8 +182,6 @@ TYPED_TEST(InOutInterfaceTest, initialize_from_mesh) // InOut should no longer be initialized EXPECT_FALSE(axom::quest::inout_initialized()); - - delete mesh; } TYPED_TEST(InOutInterfaceTest, query_properties) diff --git a/src/axom/quest/tests/quest_inout_octree.cpp b/src/axom/quest/tests/quest_inout_octree.cpp index 61a9f45a53..4a028360d7 100644 --- a/src/axom/quest/tests/quest_inout_octree.cpp +++ b/src/axom/quest/tests/quest_inout_octree.cpp @@ -46,18 +46,18 @@ using BlockIndex = Octree3D::BlockIndex; #endif /// Returns a SpacePt corresponding to the given vertex id \a vIdx in \a mesh -SpacePt getVertex(axom::mint::Mesh*& mesh, int vIdx) +SpacePt getVertex(axom::mint::Mesh& mesh, int vIdx) { SpacePt pt; - mesh->getNode(vIdx, pt.data()); + mesh.getNode(vIdx, pt.data()); return pt; } -GeometricBoundingBox computeBoundingBox(axom::mint::Mesh*& mesh) +GeometricBoundingBox computeBoundingBox(axom::mint::Mesh& mesh) { GeometricBoundingBox bbox; - for(int i = 0; i < mesh->getNumberOfNodes(); ++i) + for(int i = 0; i < mesh.getNumberOfNodes(); ++i) { bbox.addPoint(getVertex(mesh, i)); } @@ -66,7 +66,8 @@ GeometricBoundingBox computeBoundingBox(axom::mint::Mesh*& mesh) } /// Runs randomized inout queries on an octahedron mesh -void queryOctahedronMesh(axom::mint::Mesh*& mesh, const GeometricBoundingBox& bbox) +void queryOctahedronMesh(std::shared_ptr& mesh, + const GeometricBoundingBox& bbox) { const double bbMin = bbox.getMin()[0]; const double bbMax = bbox.getMax()[0]; @@ -96,7 +97,7 @@ void queryOctahedronMesh(axom::mint::Mesh*& mesh, const GeometricBoundingBox& bb case 3: case 4: case 5: - pt = getVertex(mesh, i); + pt = getVertex(*mesh, i); break; case 6: case 7: @@ -111,9 +112,9 @@ void queryOctahedronMesh(axom::mint::Mesh*& mesh, const GeometricBoundingBox& bb GridPt vertInds; mesh->getCellNodeIDs(tIdx, vertInds.data()); - pt = axom::quest::utilities::getCentroid(getVertex(mesh, vertInds[0]), - getVertex(mesh, vertInds[1]), - getVertex(mesh, vertInds[2])); + pt = axom::quest::utilities::getCentroid(getVertex(*mesh, vertInds[0]), + getVertex(*mesh, vertInds[1]), + getVertex(*mesh, vertInds[2])); } break; @@ -135,8 +136,8 @@ void queryOctahedronMesh(axom::mint::Mesh*& mesh, const GeometricBoundingBox& bb const int v2[] = {1, 2, 3, 4, 2, 3, 4, 1, 1, 2, 3, 4}; int eIdx = (i - 14); - pt = axom::quest::utilities::getCentroid(getVertex(mesh, v1[eIdx]), - getVertex(mesh, v2[eIdx])); + pt = axom::quest::utilities::getCentroid(getVertex(*mesh, v1[eIdx]), + getVertex(*mesh, v2[eIdx])); } break; @@ -182,7 +183,8 @@ TEST(quest_inout_octree, octahedron_mesh) << " and tests point containment.\n"); // Generate the InOutOctree - axom::mint::Mesh* mesh = axom::quest::utilities::make_octahedron_mesh(); + std::shared_ptr mesh( + axom::quest::utilities::make_octahedron_mesh()); // axom::mint::write_vtk(mesh, "octahedron.vtk"); /// @@ -203,9 +205,6 @@ TEST(quest_inout_octree, octahedron_mesh) SLIC_INFO("Testing InOutOctree on octahedron mesh with shifted bounding box " << bbox2); queryOctahedronMesh(mesh, bbox2); - - delete mesh; - mesh = nullptr; } TEST(quest_inout_octree, tetrahedron_mesh) @@ -219,24 +218,22 @@ TEST(quest_inout_octree, tetrahedron_mesh) for(auto thresh : thresholds) { - mint::Mesh* mesh = quest::utilities::make_tetrahedron_mesh(); - GeometricBoundingBox bbox = computeBoundingBox(mesh); + std::shared_ptr mesh {quest::utilities::make_tetrahedron_mesh()}; + GeometricBoundingBox bbox = computeBoundingBox(*mesh); Octree3D octree(bbox, mesh); octree.setVertexWeldThreshold(thresh); octree.generateIndex(); - SpacePt queryInside = quest::utilities::getCentroid(getVertex(mesh, 0), - getVertex(mesh, 1), - getVertex(mesh, 2), - getVertex(mesh, 3)); + SpacePt queryInside = quest::utilities::getCentroid(getVertex(*mesh, 0), + getVertex(*mesh, 1), + getVertex(*mesh, 2), + getVertex(*mesh, 3)); SpacePt queryOutside = SpacePt(2. * bbox.getMax().array()); EXPECT_TRUE(octree.within(queryInside)); EXPECT_FALSE(octree.within(queryOutside)); - - delete mesh; } } diff --git a/src/axom/quest/tests/quest_inout_quadtree.cpp b/src/axom/quest/tests/quest_inout_quadtree.cpp index d25b0338cc..bbf08c3ed9 100644 --- a/src/axom/quest/tests/quest_inout_quadtree.cpp +++ b/src/axom/quest/tests/quest_inout_quadtree.cpp @@ -40,18 +40,18 @@ using BlockIndex = Octree2D::BlockIndex; } // namespace /// Returns a SpacePt corresponding to the given vertex id \a vIdx in \a mesh -SpacePt getVertex(axom::mint::Mesh*& mesh, int vIdx) +SpacePt getVertex(axom::mint::Mesh& mesh, int vIdx) { SpacePt pt; - mesh->getNode(vIdx, pt.data()); + mesh.getNode(vIdx, pt.data()); return pt; } -GeometricBoundingBox computeBoundingBox(axom::mint::Mesh*& mesh) +GeometricBoundingBox computeBoundingBox(axom::mint::Mesh& mesh) { GeometricBoundingBox bbox; - for(int i = 0; i < mesh->getNumberOfNodes(); ++i) + for(int i = 0; i < mesh.getNumberOfNodes(); ++i) { bbox.addPoint(getVertex(mesh, i)); } @@ -71,9 +71,10 @@ TEST(quest_inout_quadtree, triangle_boundary_mesh) for(auto thresh : thresholds) { // create a simple mesh on the boundary of an equilateral triangle - mint::Mesh* mesh = [=]() { - auto* mesh = - new mint::UnstructuredMesh(DIM, mint::SEGMENT); + std::shared_ptr mesh = [=]() { + auto mesh = std::make_shared>( + DIM, + mint::SEGMENT); mesh->appendNode(1, 1); mesh->appendNode(4, 1); mesh->appendNode(2.5, 3 * sqrt(3) / 2.); @@ -86,22 +87,20 @@ TEST(quest_inout_quadtree, triangle_boundary_mesh) return mesh; }(); - GeometricBoundingBox bbox = computeBoundingBox(mesh); + GeometricBoundingBox bbox = computeBoundingBox(*mesh); Octree2D octree(bbox, mesh); octree.setVertexWeldThreshold(thresh); octree.generateIndex(); - SpacePt queryInside = quest::utilities::getCentroid(getVertex(mesh, 0), - getVertex(mesh, 1), - getVertex(mesh, 2)); + SpacePt queryInside = quest::utilities::getCentroid(getVertex(*mesh, 0), + getVertex(*mesh, 1), + getVertex(*mesh, 2)); SpacePt queryOutside = SpacePt(2. * bbox.getMax().array()); EXPECT_TRUE(octree.within(queryInside)); EXPECT_FALSE(octree.within(queryOutside)); - - delete mesh; } } @@ -117,11 +116,11 @@ TEST(quest_inout_quadtree, circle_mesh) for(double radius : {1. / 3., 1., sqrt(2.), 1234.5678}) { ASSERT_TRUE(num_segments >= 3); - mint::Mesh* mesh = - quest::utilities::make_circle_mesh_2d(radius, num_segments); + std::shared_ptr mesh { + quest::utilities::make_circle_mesh_2d(radius, num_segments)}; //mint::write_vtk(mesh,axom::fmt::format("circle_mesh_r{:.3f}_s{:06}.vtk", radius, num_segments)); - GeometricBoundingBox bbox = computeBoundingBox(mesh).scale(1.2); + GeometricBoundingBox bbox = computeBoundingBox(*mesh).scale(1.2); Octree2D octree(bbox, mesh); octree.generateIndex(); @@ -191,8 +190,6 @@ TEST(quest_inout_quadtree, circle_mesh) 100. * (static_cast(insideCount) / NUM_PT_TESTS), 100. * (static_cast(outsideCount) / NUM_PT_TESTS), 100. * (static_cast(uncertainCount) / NUM_PT_TESTS))); - - delete mesh; } } } From b409d476e5443716cf45b50d98bcd97ffcecffd5 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 13 Aug 2024 14:58:40 -0700 Subject: [PATCH 15/43] Get in-memory shaping working on rzansel host execution. --- src/axom/primal/geometry/NumericArray.hpp | 52 +++++++++++-------- .../quest/examples/quest_shape_in_memory.cpp | 31 ++++++----- 2 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/axom/primal/geometry/NumericArray.hpp b/src/axom/primal/geometry/NumericArray.hpp index 3e40fdb0e4..0775826276 100644 --- a/src/axom/primal/geometry/NumericArray.hpp +++ b/src/axom/primal/geometry/NumericArray.hpp @@ -44,8 +44,8 @@ AXOM_HOST_DEVICE bool operator==(const NumericArray& lhs, * \return status true if lhs!=rhs, otherwise, false. */ template -bool operator!=(const NumericArray& lhs, - const NumericArray& rhs); +AXOM_HOST_DEVICE bool operator!=(const NumericArray& lhs, + const NumericArray& rhs); /*! * \brief Performs component-wise addition of two numeric arrays. @@ -73,7 +73,7 @@ AXOM_HOST_DEVICE NumericArray operator-(const NumericArray& lh * \result C resulting numeric array from unary negation. */ template -NumericArray operator-(const NumericArray& arr); +AXOM_HOST_DEVICE NumericArray operator-(const NumericArray& arr); /*! * \brief Scalar multiplication a numeric array; Scalar on rhs. @@ -82,7 +82,8 @@ NumericArray operator-(const NumericArray& arr); * \return C resutling numeric array, \f$ \ni: C_i = scalar*arr_i, \forall i\f$ */ template -NumericArray operator*(const NumericArray& arr, double scalar); +AXOM_HOST_DEVICE NumericArray operator*(const NumericArray& arr, + double scalar); /*! * \brief Scalar multiplication a numeric array; Scalar on lhs. @@ -91,7 +92,8 @@ NumericArray operator*(const NumericArray& arr, double scalar) * \return C resulting numeric array, \f$ \ni: C_i = scalar*arr_i, \forall i\f$ */ template -NumericArray operator*(double scalar, const NumericArray& arr); +AXOM_HOST_DEVICE NumericArray operator*(double scalar, + const NumericArray& arr); /*! * \brief Component-wise multiplication of NumericArrays @@ -111,8 +113,8 @@ AXOM_HOST_DEVICE NumericArray operator*(const NumericArray& lh * \pre \f$ rhs_i != 0.0, \forall i \f$ */ template -NumericArray operator/(const NumericArray& lhs, - const NumericArray& rhs); +AXOM_HOST_DEVICE NumericArray operator/(const NumericArray& lhs, + const NumericArray& rhs); /*! * \brief Scalar division of NumericArray; Scalar on rhs @@ -122,7 +124,8 @@ NumericArray operator/(const NumericArray& lhs, * \pre scalar != 0.0 */ template -NumericArray operator/(const NumericArray& arr, double scalar); +AXOM_HOST_DEVICE NumericArray operator/(const NumericArray& arr, + double scalar); /*! * \brief Coordinate-wise absolute value on the NumericArray @@ -131,7 +134,7 @@ NumericArray operator/(const NumericArray& arr, double scalar) * \return A NumericArray whose coordinates are the absolute value of arr */ template -NumericArray abs(const NumericArray& arr); +AXOM_HOST_DEVICE NumericArray abs(const NumericArray& arr); /*! * \brief Overloaded output operator for numeric arrays @@ -310,6 +313,7 @@ class NumericArray // NOLINT * \pre forall i, arr[i] != 0 * \return A reference to the NumericArray instance after cwise division. */ + AXOM_HOST_DEVICE NumericArray& operator/=(const NumericArray& arr); /*! @@ -535,7 +539,7 @@ AXOM_HOST_DEVICE inline NumericArray& NumericArray::operator*= //------------------------------------------------------------------------------ template -inline NumericArray& NumericArray::operator/=( +AXOM_HOST_DEVICE inline NumericArray& NumericArray::operator/=( const NumericArray& v) { for(int i = 0; i < SIZE; ++i) @@ -715,7 +719,8 @@ AXOM_HOST_DEVICE bool operator==(const NumericArray& lhs, //------------------------------------------------------------------------------ template -bool operator!=(const NumericArray& lhs, const NumericArray& rhs) +AXOM_HOST_DEVICE bool operator!=(const NumericArray& lhs, + const NumericArray& rhs) { return !(lhs == rhs); } @@ -730,8 +735,9 @@ std::ostream& operator<<(std::ostream& os, const NumericArray& arr) //------------------------------------------------------------------------------ template -inline NumericArray operator*(const NumericArray& arr, - double scalar) +AXOM_HOST_DEVICE inline NumericArray operator*( + const NumericArray& arr, + double scalar) { NumericArray result(arr); result *= scalar; @@ -740,8 +746,9 @@ inline NumericArray operator*(const NumericArray& arr, //------------------------------------------------------------------------------ template -inline NumericArray operator*(double scalar, - const NumericArray& arr) +AXOM_HOST_DEVICE inline NumericArray operator*( + double scalar, + const NumericArray& arr) { NumericArray result(arr); result *= scalar; @@ -772,8 +779,9 @@ AXOM_HOST_DEVICE inline NumericArray operator*( //------------------------------------------------------------------------------ template -inline NumericArray operator/(const NumericArray& lhs, - const NumericArray& rhs) +AXOM_HOST_DEVICE inline NumericArray operator/( + const NumericArray& lhs, + const NumericArray& rhs) { NumericArray result(lhs); result /= rhs; @@ -782,8 +790,9 @@ inline NumericArray operator/(const NumericArray& lhs, //------------------------------------------------------------------------------ template -inline NumericArray operator/(const NumericArray& arr, - double scalar) +AXOM_HOST_DEVICE inline NumericArray operator/( + const NumericArray& arr, + double scalar) { NumericArray result(arr); result /= scalar; @@ -803,7 +812,8 @@ AXOM_HOST_DEVICE inline NumericArray operator-( //------------------------------------------------------------------------------ template -inline NumericArray operator-(const NumericArray& arr) +AXOM_HOST_DEVICE inline NumericArray operator-( + const NumericArray& arr) { NumericArray result; result -= arr; @@ -812,7 +822,7 @@ inline NumericArray operator-(const NumericArray& arr) //------------------------------------------------------------------------------ template -inline NumericArray abs(const NumericArray& arr) +AXOM_HOST_DEVICE inline NumericArray abs(const NumericArray& arr) { NumericArray result(arr); diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 0507f38996..ad59750880 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -819,24 +819,23 @@ axom::sidre::View* getElementVolumes( auto vertCoordsView = vertCoords.view(); // This runs only only on host, because the mfem::Mesh only uses host memory, I think. - axom::for_all( - cellCount, - AXOM_LAMBDA(axom::IndexType cellIdx) { - // Get the indices of this element's vertices - mfem::Array verts; - mesh->GetElementVertices(cellIdx, verts); - SLIC_ASSERT(verts.Size() == NUM_VERTS_PER_HEX); - - // Get the coordinates for the vertices - for(int j = 0; j < NUM_VERTS_PER_HEX; ++j) + for(axom::IndexType cellIdx = 0; cellIdx < cellCount; ++cellIdx) + { + // Get the indices of this element's vertices + mfem::Array verts; + mesh->GetElementVertices(cellIdx, verts); + SLIC_ASSERT(verts.Size() == NUM_VERTS_PER_HEX); + + // Get the coordinates for the vertices + for(int j = 0; j < NUM_VERTS_PER_HEX; ++j) + { + int vertIdx = cellIdx * NUM_VERTS_PER_HEX + j; + for(int k = 0; k < NUM_COMPS_PER_VERT; k++) { - int vertIdx = cellIdx * NUM_VERTS_PER_HEX + j; - for(int k = 0; k < NUM_COMPS_PER_VERT; k++) - { - vertCoordsView[vertIdx][k] = (mesh->GetVertex(verts[j]))[k]; - } + vertCoordsView[vertIdx][k] = (mesh->GetVertex(verts[j]))[k]; } - }); + } + } // Set vertex coords to zero if within threshold. // (I don't know why we do this. I'm following examples.) From e5ac6bd50f0753efc90a1516739ebc2ddcb935bf Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 14 Aug 2024 07:58:13 -0700 Subject: [PATCH 16/43] Enable multi-policy testing for in-memory shaping. --- src/axom/quest/examples/CMakeLists.txt | 33 +++++++++++++------ .../quest/examples/quest_shape_in_memory.cpp | 4 +++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 74fcf5ae2e..7689a25fbb 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -252,17 +252,30 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) # 3D shaping tests. No 2D shaping in this feature, at least for now. set(_nranks 1) + + # Run the in-memory shaping example on with each enabled policy + set(_policies "seq") + if(RAJA_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() + set(_testshapes "tetmesh" "hex" "sphere" "cyl" "cone" "vor") - foreach(_testshape ${_testshapes}) - set(_testname "quest_shape_in_memory_${_testshape}") - axom_add_test( - NAME ${_testname} - COMMAND quest_shape_in_memory_ex - --testShape ${_testshape} - --method intersection --refinements 2 - --scale 0.75 0.75 0.75 - inline_mesh --min -3 -3 -3 --max 3 3 3 --res 20 20 20 -d 3 - NUM_MPI_TASKS ${_nranks}) + + foreach(_policy ${_policies}) + foreach(_testshape ${_testshapes}) + set(_testname "quest_shape_in_memory_${_policy}_${_testshape}") + axom_add_test( + NAME ${_testname} + COMMAND quest_shape_in_memory_ex + --policy ${_policy} + --testShape ${_testshape} + --method intersection --refinements 2 + --scale 0.75 0.75 0.75 + inline_mesh --min -3 -3 -3 --max 3 3 3 --res 20 20 20 -d 3 + NUM_MPI_TASKS ${_nranks}) + endforeach() endforeach() endif() endif() diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index ad59750880..fa1639093e 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -1121,6 +1121,10 @@ int main(int argc, char** argv) if(auto* intersectionShaper = dynamic_cast(shaper)) { intersectionShaper->setLevel(params.refinementLevel); + SLIC_INFO(axom::fmt::format( + "{:-^80}", + axom::fmt::format("Setting IntersectionShaper policy to '{}'", + axom::runtime_policy::policyToName(params.policy)))); intersectionShaper->setExecPolicy(params.policy); if(!params.backgroundMaterial.empty()) From 6815047ef673f971ce79b0451aade6bd4ea939cb Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 30 Aug 2024 16:41:23 -0700 Subject: [PATCH 17/43] Support single tet shape in in-memory shaping. --- src/axom/klee/Geometry.cpp | 14 +++++++ src/axom/klee/Geometry.hpp | 23 +++++++++++ src/axom/quest/DiscreteShape.cpp | 40 +++++++++++++++++++ src/axom/quest/Shaper.cpp | 2 +- src/axom/quest/examples/CMakeLists.txt | 2 +- .../quest/examples/quest_shape_in_memory.cpp | 34 ++++++++++++++++ 6 files changed, 113 insertions(+), 2 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index 609280f7ab..6316c06508 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -43,6 +43,19 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, , m_operator(std::move(operator_)) { } +Geometry::Geometry(const TransformableGeometryProperties& startProperties, + const axom::primal::Tetrahedron& tet, + std::shared_ptr operator_) + : m_startProperties(startProperties) + , m_format("tet3D") + , m_path() + , m_meshGroup(nullptr) + , m_topology() + , m_tet(tet) + , m_levelOfRefinement(0) + , m_operator(std::move(operator_)) +{ } + Geometry::Geometry(const TransformableGeometryProperties& startProperties, const axom::primal::Hexahedron& hex, std::shared_ptr operator_) @@ -92,6 +105,7 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, bool Geometry::hasGeometry() const { bool isInMemory = m_format == "memory-blueprint" || m_format == "sphere3D" || + m_format == "tet3D" || m_format == "hex3D" || m_format == "cone3D" || m_format == "cylinder3D"; if(isInMemory) { diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index be21ab1f78..928f53ad41 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -59,6 +59,7 @@ class Geometry using Point3D = axom::primal::Point; using Vector3D = axom::primal::Vector; using Sphere3D = axom::primal::Sphere; + using Tet3D = axom::primal::Tetrahedron; using Hex3D = axom::primal::Hexahedron; /** @@ -92,6 +93,18 @@ class Geometry const std::string &topology, std::shared_ptr operator_); + /** + * Create a tetrahedron Geometry object. + * + * \param startProperties the transformable properties before any + * operators are applied + * \param tet Tetrahedron + * \param operator_ a possibly null operator to apply to the geometry. + */ + Geometry(const TransformableGeometryProperties &startProperties, + const axom::primal::Tetrahedron &tet, + std::shared_ptr operator_); + /** * Create a hexahedron Geometry object. * @@ -155,6 +168,7 @@ class Geometry * - "c2c" = C2C file * - "proe" = ProE file * - "memory-blueprint" = Blueprint tetrahedral mesh in memory + * - "tet3D" = 3D tetrahedron (4 points) * - "sphere3D" = 3D sphere, as \c primal::Sphere * - "vor3D" = 3D volume of revolution. * - "cone3D" = 3D cone, as \c primal::Cone @@ -247,6 +261,12 @@ class Geometry */ axom::IndexType getLevelOfRefinement() const { return m_levelOfRefinement; } + /** + @brief Return the tet geometry, when the Geometry + represents a tetrahedron. + */ + const axom::primal::Tetrahedron &getTet() const { return m_tet; } + /** @brief Return the hex geometry, when the Geometry represents a hexahedron. @@ -282,6 +302,9 @@ class Geometry //!@brief Topology of the blueprint simplex mesh, if it's in memory. std::string m_topology; + //!@brief The tetrahedron, if used. + Tet3D m_tet; + //!@brief The hexahedron, if used. Hex3D m_hex; diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 42f123a703..85699d989d 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -138,6 +138,46 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() // Transform the coordinates of the linearized mesh. applyTransforms(); } + else if(geometryFormat == "tet3D") + { + const auto& tet = geometry.getTet(); + + const axom::IndexType tetCount = 1; + const axom::IndexType nodeCount = 4; + axom::Array nodeCoords(nodeCount, 3); + axom::Array connectivity(tetCount, 4); + + for(int iNode = 0; iNode < 4; ++iNode) + { + const auto& coords = tet[iNode]; + nodeCoords[iNode][0] = coords[0]; + nodeCoords[iNode][1] = coords[1]; + nodeCoords[iNode][2] = coords[2]; + connectivity[0][iNode] = iNode; + } + + TetMesh* tetMesh = nullptr; + if(m_sidreGroup != nullptr) + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + m_sidreGroup, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + else + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + tetMesh->appendNodes((double*)nodeCoords.data(), nodeCoords.shape()[0]); + tetMesh->appendCells(connectivity.data(), connectivity.shape()[0]); + m_meshRep.reset(tetMesh); + + applyTransforms(); + } else if(geometryFormat == "hex3D") { const auto& hex = geometry.getHex(); diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 1604024566..2e73929a6b 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -92,7 +92,7 @@ void Shaper::setRefinementType(Shaper::RefinementType t) bool Shaper::isValidFormat(const std::string& format) const { return (format == "stl" || format == "proe" || format == "c2c" || - format == "memory-blueprint" || format == "hex3D" || + format == "memory-blueprint" || format == "tet3D" || format == "hex3D" || format == "sphere3D" || format == "vor3D" || format == "none"); } diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 5e2979c0c0..0f38dfa455 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -261,7 +261,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) endif() - set(_testshapes "tetmesh" "hex" "sphere" "cyl" "cone" "vor") + set(_testshapes "tetmesh" "tet" "hex" "sphere" "cyl" "cone" "vor") foreach(_policy ${_policies}) foreach(_testshape ${_testshapes}) diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index fa1639093e..2d5689ba48 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -96,6 +96,7 @@ struct Input "cyl", "cone", "vor", + "tet", "hex"}; ShapingMethod shapingMethod {ShapingMethod::Sampling}; @@ -714,6 +715,35 @@ axom::klee::Shape createShape_Cone() return vorShape; } +axom::klee::Shape createShape_Tet() +{ + axom::klee::TransformableGeometryProperties prop { + axom::klee::Dimensions::Three, + axom::klee::LengthUnit::unspecified}; + + SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); + std::shared_ptr scaleOp; + if(!params.scaleFactors.empty()) + { + scaleOp = std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + prop); + } + + const double len = params.length; + const Point3D a {0.0, 0.0, 0.0}; + const Point3D b {len, 0.0, 0.0}; + const Point3D c {len, 1.0, 0.0}; + const Point3D d {0.0, 1.0, 0.0}; + const primal::Tetrahedron tet {a, b, c, d}; + + axom::klee::Geometry tetGeometry(prop, tet, scaleOp); + axom::klee::Shape tetShape("tet", "TET", {}, {}, tetGeometry); + + return tetShape; +} + axom::klee::Shape createShape_Hex() { axom::klee::TransformableGeometryProperties prop { @@ -974,6 +1004,10 @@ int main(int argc, char** argv) { shapeSet = createShapeSet(createShape_TetMesh(ds)); } + else if(params.testShape == "tet") + { + shapeSet = createShapeSet(createShape_Tet()); + } else if(params.testShape == "hex") { shapeSet = createShapeSet(createShape_Hex()); From ae8bfb2418d5ddeffd7885f72b016bb6b99c742e Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sat, 31 Aug 2024 18:40:47 -0700 Subject: [PATCH 18/43] Support plane shape. --- src/axom/klee/Geometry.cpp | 15 +++- src/axom/klee/Geometry.hpp | 26 +++++++ src/axom/quest/DiscreteShape.cpp | 74 +++++++++++++++++++ src/axom/quest/Shaper.cpp | 3 +- src/axom/quest/examples/CMakeLists.txt | 2 +- .../quest/examples/quest_shape_in_memory.cpp | 58 +++++++++++++-- 6 files changed, 169 insertions(+), 9 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index 6316c06508..651a7ab956 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -102,10 +102,23 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, , m_operator(std::move(operator_)) { } +Geometry::Geometry(const TransformableGeometryProperties& startProperties, + const axom::primal::Plane& plane, + std::shared_ptr operator_) + : m_startProperties(startProperties) + , m_format("plane3D") + , m_path() + , m_meshGroup(nullptr) + , m_topology() + , m_plane(plane) + , m_levelOfRefinement(0) + , m_operator(std::move(operator_)) +{ } + bool Geometry::hasGeometry() const { bool isInMemory = m_format == "memory-blueprint" || m_format == "sphere3D" || - m_format == "tet3D" || m_format == "hex3D" || + m_format == "tet3D" || m_format == "hex3D" || m_format == "plane3D" || m_format == "cone3D" || m_format == "cylinder3D"; if(isInMemory) { diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 928f53ad41..07a417ee5d 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -61,6 +61,7 @@ class Geometry using Sphere3D = axom::primal::Sphere; using Tet3D = axom::primal::Tetrahedron; using Hex3D = axom::primal::Hexahedron; + using Plane3D = axom::primal::Plane; /** * Create a Geometry object based on a file representation. @@ -161,6 +162,21 @@ class Geometry axom::IndexType levelOfRefinement, std::shared_ptr operator_); + /** + * Create a planar Geometry object. + * + * \param startProperties the transformable properties before any + * operators are applied + * \param tet Tetrahedron + * \param operator_ a possibly null operator to apply to the geometry. + * + * The space on the positive normal side of the plane is considered + * "inside the shape". + */ + Geometry(const TransformableGeometryProperties &startProperties, + const axom::primal::Plane &plane, + std::shared_ptr operator_); + /** * Get the format in which the geometry is specified. * @@ -174,6 +190,7 @@ class Geometry * - "cone3D" = 3D cone, as \c primal::Cone * "cylinder3D" = 3D cylinder, as \c primal::Cylinder * - "hex3D" = 3D hexahedron (8 points) + * - "plane3D" = 3D plane * * \return the format of the shape * @@ -279,6 +296,12 @@ class Geometry */ const axom::primal::Sphere &getSphere() const { return m_sphere; } + /** + @brief Return the plane geometry, when the Geometry + represents a plane. + */ + const axom::primal::Plane &getPlane() const { return m_plane; } + /** @brief Get the discrete function used in volumes of revolution. */ @@ -308,6 +331,9 @@ class Geometry //!@brief The hexahedron, if used. Hex3D m_hex; + //!@brief The plane, if used. + Plane3D m_plane; + //!@brief Level of refinement for discretizing analytical shapes // and surfaces of revolutions. axom::IndexType m_levelOfRefinement = 0; diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 85699d989d..c2d901930a 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -228,6 +228,80 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() applyTransforms(); } + else if(geometryFormat == "plane3D") + { + const auto& plane = geometry.getPlane(); + // Generate a big bounding hex on the positive side of the plane. + axom::primal::Hexahedron boundingHex; + const double len = 1e6; // Big enough to contain anticipated mesh. + // We should compute based on the mesh. + boundingHex[0] = Point3D{0.0, -len, -len}; + boundingHex[1] = Point3D{len, -len, -len}; + boundingHex[2] = Point3D{len, len, -len}; + boundingHex[3] = Point3D{0.0, len, -len}; + boundingHex[4] = Point3D{0.0, -len, len}; + boundingHex[5] = Point3D{len, -len, len}; + boundingHex[6] = Point3D{len, len, len}; + boundingHex[7] = Point3D{0.0, len, len}; + numerics::Matrix rotate = vorAxisRotMatrix(plane.getNormal()); + const auto translate = plane.getNormal() * plane.getOffset(); + for (int i = 0; i < 8; ++ i ) + { + Point3D newCoords; + numerics::matrix_vector_multiply(rotate, + boundingHex[i].data(), + newCoords.data()); + newCoords.array() += translate.array(); + boundingHex[i].array() = newCoords.array(); + } + + axom::StackArray tets; + boundingHex.triangulate(tets); + + const axom::IndexType tetCount = HexType::NUM_TRIANGULATE; + const axom::IndexType nodeCount = HexType::NUM_TRIANGULATE * 4; + axom::Array nodeCoords(nodeCount, 3); + auto nodeCoordsView = nodeCoords.view(); + axom::Array connectivity(tetCount, 4); + auto connectivityView = connectivity.view(); + // NOTE: This is not much computation, so just run on host. + axom::for_all( + tetCount, + AXOM_LAMBDA(axom::IndexType iTet) { + const auto& tet = tets[iTet]; + for(int i = 0; i < 4; ++i) + { + axom::IndexType iNode = iTet * 4 + i; + const auto& coords = tet[i]; + nodeCoordsView[iNode][0] = coords[0]; + nodeCoordsView[iNode][1] = coords[1]; + nodeCoordsView[iNode][2] = coords[2]; + connectivityView[iTet][i] = iNode; + } + }); + + TetMesh* tetMesh = nullptr; + if(m_sidreGroup != nullptr) + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + m_sidreGroup, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + else + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + tetMesh->appendNodes((double*)nodeCoords.data(), nodeCoords.shape()[0]); + tetMesh->appendCells(connectivity.data(), connectivity.shape()[0]); + m_meshRep.reset(tetMesh); + + applyTransforms(); + } else if(geometryFormat == "sphere3D") { /* diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 2e73929a6b..48ada1b3b9 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -93,7 +93,8 @@ bool Shaper::isValidFormat(const std::string& format) const { return (format == "stl" || format == "proe" || format == "c2c" || format == "memory-blueprint" || format == "tet3D" || format == "hex3D" || - format == "sphere3D" || format == "vor3D" || format == "none"); + format == "plane3D" || format == "sphere3D" || format == "vor3D" || + format == "none"); } void Shaper::loadShape(const klee::Shape& shape) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 0f38dfa455..9fa3d2e798 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -261,7 +261,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) endif() - set(_testshapes "tetmesh" "tet" "hex" "sphere" "cyl" "cone" "vor") + set(_testshapes "tetmesh" "tet" "hex" "sphere" "cyl" "cone" "vor" "plane") foreach(_policy ${_policies}) foreach(_testshape ${_testshapes}) diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 2d5689ba48..2a16172856 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -97,7 +97,8 @@ struct Input "cone", "vor", "tet", - "hex"}; + "hex", + "plane"}; ShapingMethod shapingMethod {ShapingMethod::Sampling}; RuntimePolicy policy {RuntimePolicy::seq}; @@ -118,6 +119,16 @@ struct Input public: bool isVerbose() const { return m_verboseOutput; } + /// @brief Return volume of input box mesh + double boxMeshVolume() const + { + primal::Vector x{boxMaxs[0] - boxMins[0], 0, 0}; + primal::Vector y{0, boxMaxs[1] - boxMins[1], 0}; + primal::Vector z{0, 0, boxMaxs[2] - boxMins[2]}; + double volume = primal::Vector::scalar_triple_product(x, y, z); + return volume; + } + /// Generate an mfem Cartesian mesh, scaled to the bounding box range mfem::Mesh* createBoxMesh() { @@ -777,6 +788,35 @@ axom::klee::Shape createShape_Hex() return hexShape; } +axom::klee::Shape createShape_Plane() +{ + axom::klee::TransformableGeometryProperties prop { + axom::klee::Dimensions::Three, + axom::klee::LengthUnit::unspecified}; + + SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); + std::shared_ptr scaleOp; + if(!params.scaleFactors.empty()) + { + scaleOp = std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + prop); + } + + // Create a plane crossing center of mesh. No matter the normal, + // it cuts the mesh in half. + Point3D center{0.5*(primal::NumericArray(params.boxMins.data()) + + primal::NumericArray(params.boxMaxs.data()))}; + primal::Vector normal{1.0, 0.0, 0.0}; + const primal::Plane plane {normal, center, true}; + + axom::klee::Geometry planeGeometry(prop, plane, scaleOp); + axom::klee::Shape planeShape("plane", "PLANE", {}, {}, planeGeometry); + + return planeShape; +} + //!@brief Create a ShapeSet with a single shape. axom::klee::ShapeSet createShapeSet(const axom::klee::Shape& shape) { @@ -791,7 +831,7 @@ double volumeOfTetMesh( const axom::mint::UnstructuredMesh& tetMesh) { using TetType = axom::primal::Tetrahedron; - { + if(0) { std::ofstream os("tets.js"); tetMesh.getSidreGroup()->print(os); } @@ -1028,6 +1068,10 @@ int main(int argc, char** argv) { shapeSet = createShapeSet(createShape_Vor()); } + else if(params.testShape == "plane") + { + shapeSet = createShapeSet(createShape_Plane()); + } break; } @@ -1341,7 +1385,8 @@ int main(int argc, char** argv) slic::flushStreams(); //--------------------------------------------------------------------------- - // Correctness test: shape volume in shapingMesh should match volume of the shape mesh. + // Correctness test: shape volume in shapingMesh should match volume of the + // shape mesh for closes shape. //--------------------------------------------------------------------------- auto* meshVerificationGroup = ds.getRoot()->createGroup("meshVerification"); for(const auto& shape : shapeSet.getShapes()) @@ -1360,9 +1405,10 @@ int main(int argc, char** argv) const std::string& materialName = shape.getMaterial(); double shapeVol = sumMaterialVolumes(&shapingDC, materialName); - double diff = shapeVol - shapeMeshVol; + double correctShapeVol = params.testShape == "plane" ? params.boxMeshVolume()/2 : shapeMeshVol; + double diff = shapeVol - correctShapeVol; - bool err = !axom::utilities::isNearlyEqual(shapeVol, shapeMeshVol); + bool err = !axom::utilities::isNearlyEqual(shapeVol, correctShapeVol); failCounts += err; SLIC_INFO(axom::fmt::format( @@ -1372,7 +1418,7 @@ int main(int argc, char** argv) materialName, shape.getName(), shapeVol, - shapeMeshVol, + correctShapeVol, diff, (err ? "ERROR" : "OK")))); } From 382313ebe8f543772384cd383895efb4b360ff40 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 1 Oct 2024 17:26:50 -0700 Subject: [PATCH 19/43] Make some groups and views const where they are not modified. --- src/axom/klee/Geometry.cpp | 4 ++-- src/axom/klee/Geometry.hpp | 8 ++++---- src/axom/quest/DiscreteShape.cpp | 14 ++++---------- src/axom/quest/DiscreteShape.hpp | 3 +++ src/axom/quest/Shaper.cpp | 2 +- src/axom/quest/Shaper.hpp | 2 ++ src/axom/sidre/core/Group.cpp | 4 ++-- src/axom/sidre/core/Group.hpp | 8 ++++---- 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index 651a7ab956..ac61fcb089 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -31,7 +31,7 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, { } Geometry::Geometry(const TransformableGeometryProperties& startProperties, - axom::sidre::Group* meshGroup, + const axom::sidre::Group* meshGroup, const std::string& topology, std::shared_ptr operator_) : m_startProperties(startProperties) @@ -136,7 +136,7 @@ TransformableGeometryProperties Geometry::getEndProperties() const return m_startProperties; } -axom::sidre::Group* Geometry::getBlueprintMesh() const +const axom::sidre::Group* Geometry::getBlueprintMesh() const { SLIC_ASSERT_MSG( m_meshGroup, diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 07a417ee5d..7759407a0a 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -84,13 +84,13 @@ class Geometry * operators are applied * \param meshGroup a simplex geometry in blueprint format. * The elements should be segments, triangles or tetrahedra. - * \param topology The \c meshGroup topology to use. + * \param topology The blueprint topology to use. * \param operator_ a possibly null operator to apply to the geometry. * * \internal TODO: Is this the simplex requirement overly restrictive? */ Geometry(const TransformableGeometryProperties &startProperties, - axom::sidre::Group *meshGroup, + const axom::sidre::Group *meshGroup, const std::string &topology, std::shared_ptr operator_); @@ -211,7 +211,7 @@ class Geometry * @brief Return the blueprint mesh, for formats that are specified * by a blueprint mesh or have been converted to a blueprint mesh. */ - axom::sidre::Group *getBlueprintMesh() const; + const axom::sidre::Group *getBlueprintMesh() const; /** * @brief Return the blueprint mesh topology, for formats that are specified @@ -320,7 +320,7 @@ class Geometry std::string m_path; //!@brief Geometry blueprint simplex mesh, when/if it's in memory. - axom::sidre::Group *m_meshGroup; + const axom::sidre::Group *m_meshGroup; //!@brief Topology of the blueprint simplex mesh, if it's in memory. std::string m_topology; diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index c2d901930a..604d908429 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -95,11 +95,6 @@ DiscreteShape::DiscreteShape(const axom::klee::Shape& shape, void DiscreteShape::clearInternalData() { m_meshRep.reset(); - if(m_sidreGroup) - { - m_sidreGroup->getParent()->destroyGroupAndData(m_sidreGroup->getIndex()); - m_sidreGroup = nullptr; - } } std::shared_ptr DiscreteShape::createMeshRepresentation() @@ -118,17 +113,16 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() if(geometryFormat == "memory-blueprint") { // Put the in-memory geometry in m_meshRep. - axom::sidre::Group* inputGroup = geometry.getBlueprintMesh(); - axom::sidre::Group* rootGroup = inputGroup->getDataStore()->getRoot(); + const axom::sidre::Group* inputGroup = geometry.getBlueprintMesh(); + int allocID = inputGroup->getDefaultAllocatorID(); std::string modName = inputGroup->getName() + "_modified"; - while(rootGroup->hasGroup(modName)) + while(m_sidreGroup->hasGroup(modName)) { modName = modName + "-"; } - axom::sidre::Group* modGroup = rootGroup->createGroup(modName); - int allocID = inputGroup->getDefaultAllocatorID(); + axom::sidre::Group* modGroup = m_sidreGroup->createGroup(modName); modGroup->deepCopyGroup(inputGroup, allocID); m_meshRep.reset( diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index d1c6339c87..a61d7d9c08 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -136,6 +136,9 @@ class DiscreteShape */ std::shared_ptr m_meshRep; + //!@brief Internal DataStore for working space + axom::sidre::DataStore m_dataStore; + //!@brief Sidre store for m_meshRep. axom::sidre::Group* m_sidreGroup {nullptr}; diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 48ada1b3b9..bbcadd6e09 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -124,7 +124,7 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, axom::fmt::format("Shape has unsupported format: '{}", geometryFormat)); // Code for discretizing shapes has been factored into DiscreteShape class. - DiscreteShape discreteShape(shape, m_shapeSet.getPath()); + DiscreteShape discreteShape(shape, m_shapeSet.getPath(), m_dataStore.getRoot()); discreteShape.setVertexWeldThreshold(m_vertexWeldThreshold); discreteShape.setRefinementType(m_refinementType); if(percentError > 0) diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index a05802f6e4..da8eed8341 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -152,6 +152,8 @@ class Shaper int getRank() const; protected: + sidre::DataStore m_dataStore; + const klee::ShapeSet& m_shapeSet; sidre::MFEMSidreDataCollection* m_dc; diff --git a/src/axom/sidre/core/Group.cpp b/src/axom/sidre/core/Group.cpp index 8fbef37b13..d1ae0a1c74 100644 --- a/src/axom/sidre/core/Group.cpp +++ b/src/axom/sidre/core/Group.cpp @@ -963,7 +963,7 @@ View* Group::copyView(View* view) * ************************************************************************* */ -View* Group::deepCopyView(View* view, int allocID) +View* Group::deepCopyView(const View* view, int allocID) { allocID = getValidAllocatorID(allocID); @@ -1402,7 +1402,7 @@ Group* Group::copyGroup(Group* group) * ************************************************************************* */ -Group* Group::deepCopyGroup(Group* srcGroup, int allocID) +Group* Group::deepCopyGroup(const Group* srcGroup, int allocID) { allocID = getValidAllocatorID(allocID); diff --git a/src/axom/sidre/core/Group.hpp b/src/axom/sidre/core/Group.hpp index 3ee12f865f..02371765c7 100644 --- a/src/axom/sidre/core/Group.hpp +++ b/src/axom/sidre/core/Group.hpp @@ -849,13 +849,13 @@ class Group /*! * \brief Destroy View with given name or path owned by this Group, but leave - * its data intect. + * its data intact. */ void destroyView(const std::string& path); /*! * \brief Destroy View with given index owned by this Group, but leave - * its data intect. + * its data intact. */ void destroyView(IndexType idx); @@ -937,7 +937,7 @@ class Group * \return pointer to the new copied View object or nullptr if a View * is not copied into this Group. */ - View* deepCopyView(View* view, int allocID = INVALID_ALLOCATOR_ID); + View* deepCopyView(const View* view, int allocID = INVALID_ALLOCATOR_ID); //@} @@ -1306,7 +1306,7 @@ class Group * \return pointer to the new copied Group object or nullptr if a Group * is not copied into this Group. */ - Group* deepCopyGroup(Group* srcGroup, int allocID = INVALID_ALLOCATOR_ID); + Group* deepCopyGroup(const Group* srcGroup, int allocID = INVALID_ALLOCATOR_ID); //@} From d73df726a4c2a2f5ed8752bf543db0881ddedb75 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 1 Oct 2024 17:44:03 -0700 Subject: [PATCH 20/43] More robust way to get temporary unique Group name. --- src/axom/quest/DiscreteShape.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 604d908429..d9c2fafab8 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -712,9 +712,19 @@ void DiscreteShape::setParentGroup(axom::sidre::Group* parentGroup) { if(parentGroup) { - std::ostringstream os; - os << this; - std::string myGroupName = os.str(); + // Use object address to create a unique name for sidre group under parent. + std::string myGroupName; + int i = 0; + while (myGroupName.empty()) + { + std::ostringstream os; + os << "DiscreteShapeTemp-" << i; + if ( !parentGroup->hasGroup(os.str()) ) + { + myGroupName = os.str(); + } + ++i; + } m_sidreGroup = parentGroup->createGroup(myGroupName); } } From 933e4e3c2462c8878b20bd7bab75738af1f50850 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Tue, 1 Oct 2024 17:44:45 -0700 Subject: [PATCH 21/43] Change parameter ordering. --- src/axom/quest/DiscreteShape.cpp | 14 ++++++++++---- src/axom/quest/DiscreteShape.hpp | 11 +++++++---- src/axom/quest/Shaper.cpp | 2 +- src/axom/quest/examples/quest_shape_in_memory.cpp | 4 ++-- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index d9c2fafab8..2a5e573f7a 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -80,8 +80,8 @@ constexpr double DiscreteShape::MAXIMUM_PERCENT_ERROR; constexpr double DiscreteShape::DEFAULT_VERTEX_WELD_THRESHOLD; DiscreteShape::DiscreteShape(const axom::klee::Shape& shape, - const std::string& prefixPath, - axom::sidre::Group* parentGroup) + axom::sidre::Group* parentGroup, + const std::string& prefixPath) : m_shape(shape) , m_sidreGroup(nullptr) , m_refinementType(DiscreteShape::RefinementUniformSegments) @@ -344,11 +344,17 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() if(m_sidreGroup != nullptr) { tetMesh = - new TetMesh(3, axom::mint::CellType::TET, m_sidreGroup, nodeCount, tetCount); + new TetMesh(3, axom::mint::CellType::TET, + m_sidreGroup, + nodeCount, + tetCount); } else { - tetMesh = new TetMesh(3, axom::mint::CellType::TET, nodeCount, tetCount); + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + nodeCount, + tetCount); } tetMesh->appendNodes((double*)nodeCoords.data(), nodeCount); tetMesh->appendCells(connectivity.data(), tetCount); diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index a61d7d9c08..b217ce8841 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -47,18 +47,18 @@ class DiscreteShape @brief Constructor. @param shape The Klee specifications for the shape. - @param prefixPath Path prefix for shape files specified - with a relative path. @param parentGroup Group under which to put the discrete mesh. If null, don't use sidre. + @param prefixPath Path prefix for shape files specified + with a relative path. Refinement type is set to DiscreteShape::RefinementUniformSegments and percent erro is set to 0. See setPercentError() and setRefinementType(). */ DiscreteShape(const axom::klee::Shape& shape, - const std::string& prefixPath = {}, - axom::sidre::Group* parentGroup = nullptr); + axom::sidre::Group* parentGroup, + const std::string& prefixPath = {} ); virtual ~DiscreteShape() { clearInternalData(); } @@ -107,6 +107,9 @@ class DiscreteShape /*! \brief Get the discrete mesh representation. + If the sidre parent group was used in the constructor, the + mesh data is stored under that group. + If the discrete mesh isn't generated yet (for analytical shapes) generate it. */ diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index bbcadd6e09..b7837609b9 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -124,7 +124,7 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, axom::fmt::format("Shape has unsupported format: '{}", geometryFormat)); // Code for discretizing shapes has been factored into DiscreteShape class. - DiscreteShape discreteShape(shape, m_shapeSet.getPath(), m_dataStore.getRoot()); + DiscreteShape discreteShape(shape, m_dataStore.getRoot(), m_shapeSet.getPath()); discreteShape.setVertexWeldThreshold(m_vertexWeldThreshold); discreteShape.setRefinementType(m_refinementType); if(percentError > 0) diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index d9e282c2e7..29e6f35a85 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -1079,7 +1079,7 @@ int main(int argc, char** argv) std::vector> discreteShapeMeshes; for(const auto& shape : shapeSet.getShapes()) { - axom::quest::DiscreteShape dShape(shape, "", shapeMeshGroup); + axom::quest::DiscreteShape dShape(shape, shapeMeshGroup); auto dMesh = std::dynamic_pointer_cast>( dShape.createMeshRepresentation()); @@ -1390,7 +1390,7 @@ int main(int argc, char** argv) auto* meshVerificationGroup = ds.getRoot()->createGroup("meshVerification"); for(const auto& shape : shapeSet.getShapes()) { - axom::quest::DiscreteShape dShape(shape, "", meshVerificationGroup); + axom::quest::DiscreteShape dShape(shape, meshVerificationGroup); auto shapeMesh = std::dynamic_pointer_cast>( dShape.createMeshRepresentation()); From 42bf676962ebf58b1f2d5ce61e89333f702d00ef Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 2 Oct 2024 06:12:42 -0700 Subject: [PATCH 22/43] Rename memory-blueprint format to blueprint-tet and add checks to ensure the user provided a blueprint mesh. --- src/axom/klee/Geometry.cpp | 55 +++++++++++++++++++++++++++++--- src/axom/klee/Geometry.hpp | 21 +++++++----- src/axom/quest/DiscreteShape.cpp | 2 +- src/axom/quest/Shaper.cpp | 2 +- 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index ac61fcb089..a23fad0ab8 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -6,6 +6,7 @@ #include "axom/klee/Geometry.hpp" #include "axom/klee/GeometryOperators.hpp" +#include "conduit_blueprint_mesh.hpp" #include @@ -35,13 +36,18 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, const std::string& topology, std::shared_ptr operator_) : m_startProperties(startProperties) - , m_format("memory-blueprint") + , m_format("blueprint-tets") , m_path() , m_meshGroup(meshGroup) , m_topology(topology) , m_levelOfRefinement(0) , m_operator(std::move(operator_)) -{ } +{ +#ifdef AXOM_DEBUG + SLIC_ASSERT_MSG(isBlueprintTetMesh(m_meshGroup), + "Mesh provided to Geometry is not a valid blueprint unstructured tetrahedral mesh."); +#endif +} Geometry::Geometry(const TransformableGeometryProperties& startProperties, const axom::primal::Tetrahedron& tet, @@ -78,8 +84,8 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, , m_path() , m_meshGroup(nullptr) , m_topology() - , m_levelOfRefinement(levelOfRefinement) , m_sphere(sphere) + , m_levelOfRefinement(levelOfRefinement) , m_operator(std::move(operator_)) { } @@ -94,11 +100,11 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, , m_path() , m_meshGroup(nullptr) , m_topology() - , m_levelOfRefinement(levelOfRefinement) , m_sphere() , m_discreteFunction(discreteFunction) , m_vorBase(vorBase) , m_vorDirection(vorDirection) + , m_levelOfRefinement(levelOfRefinement) , m_operator(std::move(operator_)) { } @@ -117,7 +123,7 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, bool Geometry::hasGeometry() const { - bool isInMemory = m_format == "memory-blueprint" || m_format == "sphere3D" || + bool isInMemory = m_format == "blueprint-tets" || m_format == "sphere3D" || m_format == "tet3D" || m_format == "hex3D" || m_format == "plane3D" || m_format == "cone3D" || m_format == "cylinder3D"; if(isInMemory) @@ -158,5 +164,44 @@ const std::string& Geometry::getBlueprintTopology() const return m_topology; } + +bool Geometry::isBlueprintTetMesh(const axom::sidre::Group *meshGroup) const +{ + conduit::Node bpMesh; + meshGroup->createNativeLayout(bpMesh); + + conduit::Node info; + bool isValid = conduit::blueprint::mesh::verify(bpMesh, info); + if (!isValid) + { + return false; + } + + const auto& topology = bpMesh.fetch_existing(axom::fmt::format("topologies/{}", m_topology)); + + std::string coordsetName = topology.fetch_existing("coordset").as_string(); + const auto& coordSet = bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", coordsetName)); + + auto dim = conduit::blueprint::mesh::coordset::dims(coordSet); + if (dim != 3) + { + return false; + } + + auto topoType = topology.fetch_existing("type").as_string(); + if (topoType != "unstructured") + { + return false; + } + + auto shapeType = topology.fetch_existing("elements/shape").as_string(); + if (shapeType != "tet") + { + return false; + } + + return true; +} + } // namespace klee } // namespace axom diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 7759407a0a..82cd5eb506 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -178,12 +178,13 @@ class Geometry std::shared_ptr operator_); /** - * Get the format in which the geometry is specified. + * @brief Get the format in which the geometry was specified. * + * The format is determined by the constructor used. * Values are: * - "c2c" = C2C file * - "proe" = ProE file - * - "memory-blueprint" = Blueprint tetrahedral mesh in memory + * - "blueprint-tets" = Blueprint tetrahedral mesh in memory * - "tet3D" = 3D tetrahedron (4 points) * - "sphere3D" = 3D sphere, as \c primal::Sphere * - "vor3D" = 3D volume of revolution. @@ -313,7 +314,7 @@ class Geometry private: TransformableGeometryProperties m_startProperties; - //!@brief Geometry file format. + //!@brief Geometry format. std::string m_format; //!@brief Geometry file path, if it's file-based. @@ -334,21 +335,25 @@ class Geometry //!@brief The plane, if used. Plane3D m_plane; - //!@brief Level of refinement for discretizing analytical shapes - // and surfaces of revolutions. - axom::IndexType m_levelOfRefinement = 0; - //!@brief The analytical sphere, if used. Sphere3D m_sphere; //! @brief The discrete 2D function, as an Nx2 array, if used. axom::Array m_discreteFunction; - //!@brief The base of the VOR axis, corresponding to z=0. + + //!@brief The point corresponding to z=0 on the VOR axis. Point3D m_vorBase; + //!@brief VOR axis in the direction of increasing z. Vector3D m_vorDirection; + //!@brief Level of refinement for discretizing curved + // analytical shapes and surfaces of revolutions. + axom::IndexType m_levelOfRefinement = 0; + std::shared_ptr m_operator; + + bool isBlueprintTetMesh(const axom::sidre::Group *meshGroup) const; }; } // namespace klee diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 2a5e573f7a..9769fb852e 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -110,7 +110,7 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() const axom::klee::Geometry& geometry = m_shape.getGeometry(); const auto& geometryFormat = geometry.getFormat(); - if(geometryFormat == "memory-blueprint") + if(geometryFormat == "blueprint-tets") { // Put the in-memory geometry in m_meshRep. const axom::sidre::Group* inputGroup = geometry.getBlueprintMesh(); diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index b7837609b9..b153e498e8 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -92,7 +92,7 @@ void Shaper::setRefinementType(Shaper::RefinementType t) bool Shaper::isValidFormat(const std::string& format) const { return (format == "stl" || format == "proe" || format == "c2c" || - format == "memory-blueprint" || format == "tet3D" || format == "hex3D" || + format == "blueprint-tets" || format == "tet3D" || format == "hex3D" || format == "plane3D" || format == "sphere3D" || format == "vor3D" || format == "none"); } From 6585fa25303716427c4f9686c16970689c285d64 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 2 Oct 2024 07:04:35 -0700 Subject: [PATCH 23/43] Factor out code to shorten a method for readability. --- src/axom/quest/DiscreteShape.cpp | 662 ++++++++++++++++--------------- src/axom/quest/DiscreteShape.hpp | 15 +- 2 files changed, 363 insertions(+), 314 deletions(-) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 9769fb852e..8e2e242124 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -104,339 +104,32 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() return m_meshRep; } - using TetMesh = - axom::mint::UnstructuredMesh; - const axom::klee::Geometry& geometry = m_shape.getGeometry(); const auto& geometryFormat = geometry.getFormat(); if(geometryFormat == "blueprint-tets") { - // Put the in-memory geometry in m_meshRep. - const axom::sidre::Group* inputGroup = geometry.getBlueprintMesh(); - int allocID = inputGroup->getDefaultAllocatorID(); - - std::string modName = inputGroup->getName() + "_modified"; - while(m_sidreGroup->hasGroup(modName)) - { - modName = modName + "-"; - } - - axom::sidre::Group* modGroup = m_sidreGroup->createGroup(modName); - modGroup->deepCopyGroup(inputGroup, allocID); - - m_meshRep.reset( - axom::mint::getMesh(modGroup->getGroup(inputGroup->getName()), - m_shape.getGeometry().getBlueprintTopology())); - - // Transform the coordinates of the linearized mesh. - applyTransforms(); + createBlueprintTetsRepresentation(); } else if(geometryFormat == "tet3D") { - const auto& tet = geometry.getTet(); - - const axom::IndexType tetCount = 1; - const axom::IndexType nodeCount = 4; - axom::Array nodeCoords(nodeCount, 3); - axom::Array connectivity(tetCount, 4); - - for(int iNode = 0; iNode < 4; ++iNode) - { - const auto& coords = tet[iNode]; - nodeCoords[iNode][0] = coords[0]; - nodeCoords[iNode][1] = coords[1]; - nodeCoords[iNode][2] = coords[2]; - connectivity[0][iNode] = iNode; - } - - TetMesh* tetMesh = nullptr; - if(m_sidreGroup != nullptr) - { - tetMesh = new TetMesh(3, - axom::mint::CellType::TET, - m_sidreGroup, - nodeCoords.shape()[0], - connectivity.shape()[0]); - } - else - { - tetMesh = new TetMesh(3, - axom::mint::CellType::TET, - nodeCoords.shape()[0], - connectivity.shape()[0]); - } - tetMesh->appendNodes((double*)nodeCoords.data(), nodeCoords.shape()[0]); - tetMesh->appendCells(connectivity.data(), connectivity.shape()[0]); - m_meshRep.reset(tetMesh); - - applyTransforms(); + createTetRepresentation(); } else if(geometryFormat == "hex3D") { - const auto& hex = geometry.getHex(); - axom::StackArray tets; - hex.triangulate(tets); - - const axom::IndexType tetCount = HexType::NUM_TRIANGULATE; - const axom::IndexType nodeCount = HexType::NUM_TRIANGULATE * 4; - axom::Array nodeCoords(nodeCount, 3); - auto nodeCoordsView = nodeCoords.view(); - axom::Array connectivity(tetCount, 4); - auto connectivityView = connectivity.view(); - // NOTE: This is not much computation, so just run on host. - axom::for_all( - tetCount, - AXOM_LAMBDA(axom::IndexType iTet) { - const auto& tet = tets[iTet]; - for(int i = 0; i < 4; ++i) - { - axom::IndexType iNode = iTet * 4 + i; - const auto& coords = tet[i]; - nodeCoordsView[iNode][0] = coords[0]; - nodeCoordsView[iNode][1] = coords[1]; - nodeCoordsView[iNode][2] = coords[2]; - connectivityView[iTet][i] = iNode; - } - }); - - TetMesh* tetMesh = nullptr; - if(m_sidreGroup != nullptr) - { - tetMesh = new TetMesh(3, - axom::mint::CellType::TET, - m_sidreGroup, - nodeCoords.shape()[0], - connectivity.shape()[0]); - } - else - { - tetMesh = new TetMesh(3, - axom::mint::CellType::TET, - nodeCoords.shape()[0], - connectivity.shape()[0]); - } - tetMesh->appendNodes((double*)nodeCoords.data(), nodeCoords.shape()[0]); - tetMesh->appendCells(connectivity.data(), connectivity.shape()[0]); - m_meshRep.reset(tetMesh); - - applyTransforms(); + createHexRepresentation(); } else if(geometryFormat == "plane3D") { - const auto& plane = geometry.getPlane(); - // Generate a big bounding hex on the positive side of the plane. - axom::primal::Hexahedron boundingHex; - const double len = 1e6; // Big enough to contain anticipated mesh. - // We should compute based on the mesh. - boundingHex[0] = Point3D{0.0, -len, -len}; - boundingHex[1] = Point3D{len, -len, -len}; - boundingHex[2] = Point3D{len, len, -len}; - boundingHex[3] = Point3D{0.0, len, -len}; - boundingHex[4] = Point3D{0.0, -len, len}; - boundingHex[5] = Point3D{len, -len, len}; - boundingHex[6] = Point3D{len, len, len}; - boundingHex[7] = Point3D{0.0, len, len}; - numerics::Matrix rotate = vorAxisRotMatrix(plane.getNormal()); - const auto translate = plane.getNormal() * plane.getOffset(); - for (int i = 0; i < 8; ++ i ) - { - Point3D newCoords; - numerics::matrix_vector_multiply(rotate, - boundingHex[i].data(), - newCoords.data()); - newCoords.array() += translate.array(); - boundingHex[i].array() = newCoords.array(); - } - - axom::StackArray tets; - boundingHex.triangulate(tets); - - const axom::IndexType tetCount = HexType::NUM_TRIANGULATE; - const axom::IndexType nodeCount = HexType::NUM_TRIANGULATE * 4; - axom::Array nodeCoords(nodeCount, 3); - auto nodeCoordsView = nodeCoords.view(); - axom::Array connectivity(tetCount, 4); - auto connectivityView = connectivity.view(); - // NOTE: This is not much computation, so just run on host. - axom::for_all( - tetCount, - AXOM_LAMBDA(axom::IndexType iTet) { - const auto& tet = tets[iTet]; - for(int i = 0; i < 4; ++i) - { - axom::IndexType iNode = iTet * 4 + i; - const auto& coords = tet[i]; - nodeCoordsView[iNode][0] = coords[0]; - nodeCoordsView[iNode][1] = coords[1]; - nodeCoordsView[iNode][2] = coords[2]; - connectivityView[iTet][i] = iNode; - } - }); - - TetMesh* tetMesh = nullptr; - if(m_sidreGroup != nullptr) - { - tetMesh = new TetMesh(3, - axom::mint::CellType::TET, - m_sidreGroup, - nodeCoords.shape()[0], - connectivity.shape()[0]); - } - else - { - tetMesh = new TetMesh(3, - axom::mint::CellType::TET, - nodeCoords.shape()[0], - connectivity.shape()[0]); - } - tetMesh->appendNodes((double*)nodeCoords.data(), nodeCoords.shape()[0]); - tetMesh->appendCells(connectivity.data(), connectivity.shape()[0]); - m_meshRep.reset(tetMesh); - - applyTransforms(); + createPlaneRepresentation(); } else if(geometryFormat == "sphere3D") { - /* - Discretize the sphere into m_meshRep and apply transforms: - 1. Discretize the sphere into a set of Octahedra. - 2. Split each Octahedron into 8 tets. - 3. Insert tets into tet mesh. - 4. Transform the tet mesh. - We can reduce memory use by eliminating repeated points if it - is a problem. - */ - const auto& sphere = geometry.getSphere(); - axom::Array octs; - int octCount = 0; - axom::quest::discretize(sphere, geometry.getLevelOfRefinement(), octs, octCount); - - constexpr int TETS_PER_OCT = 8; - constexpr int NODES_PER_TET = 4; - const axom::IndexType tetCount = octCount * TETS_PER_OCT; - const axom::IndexType nodeCount = tetCount * NODES_PER_TET; - - axom::Array nodeCoords(nodeCount, nodeCount); - axom::Array connectivity( - axom::StackArray {tetCount, NODES_PER_TET}); - - auto nodeCoordsView = nodeCoords.view(); - auto connectivityView = connectivity.view(); - axom::for_all( - octCount, - AXOM_LAMBDA(axom::IndexType octIdx) { - TetType tetsInOct[TETS_PER_OCT]; - axom::primal::split(octs[octIdx], tetsInOct); - for(int iTet = 0; iTet < TETS_PER_OCT; ++iTet) - { - axom::IndexType tetIdx = octIdx * TETS_PER_OCT + iTet; - for(int iNode = 0; iNode < NODES_PER_TET; ++iNode) - { - axom::IndexType nodeIdx = tetIdx * NODES_PER_TET + iNode; - nodeCoordsView[nodeIdx] = tetsInOct[iTet][iNode]; - connectivityView[tetIdx][iNode] = nodeIdx; - } - } - }); - - TetMesh* tetMesh = nullptr; - if(m_sidreGroup != nullptr) - { - tetMesh = - new TetMesh(3, axom::mint::CellType::TET, - m_sidreGroup, - nodeCount, - tetCount); - } - else - { - tetMesh = new TetMesh(3, - axom::mint::CellType::TET, - nodeCount, - tetCount); - } - tetMesh->appendNodes((double*)nodeCoords.data(), nodeCount); - tetMesh->appendCells(connectivity.data(), tetCount); - m_meshRep.reset(tetMesh); - - applyTransforms(); + createSphereRepresentation(); } if(geometryFormat == "vor3D") { - // Construct the tet m_meshRep from the volume-of-revolution. - auto& vorGeom = m_shape.getGeometry(); - const auto& discreteFcn = vorGeom.getDiscreteFunction(); - - // Generate the Octahedra - axom::Array octs; - int octCount = 0; - axom::ArrayView polyline((Point2D*)discreteFcn.data(), - discreteFcn.shape()[0]); - const bool good = axom::quest::discretize( - polyline, - int(polyline.size()), - m_shape.getGeometry().getLevelOfRefinement(), - octs, - octCount); - SLIC_ASSERT(good); - - // Rotate to the VOR axis direction and translate to the base location. - numerics::Matrix rotate = vorAxisRotMatrix(vorGeom.getVorDirection()); - const auto& translate = vorGeom.getVorBaseCoords(); - auto octsView = octs.view(); - axom::for_all( - octCount, - AXOM_LAMBDA(axom::IndexType iOct) { - auto& oct = octsView[iOct]; - for(int iVert = 0; iVert < OctType::NUM_VERTS; ++iVert) - { - auto& newCoords = oct[iVert]; - auto oldCoords = newCoords; - numerics::matrix_vector_multiply(rotate, - oldCoords.data(), - newCoords.data()); - newCoords.array() += translate.array(); - } - }); - - // Dump discretized octs as a tet mesh - axom::mint::Mesh* mesh; - axom::quest::mesh_from_discretized_polyline(octs.view(), - octCount, - polyline.size() - 1, - mesh); - - if(m_sidreGroup) - { - // If using sidre, copy the tetMesh into sidre. - auto* tetMesh = static_cast(mesh); - axom::mint::UnstructuredMesh* siMesh = - new axom::mint::UnstructuredMesh( - tetMesh->getDimension(), - tetMesh->getCellType(), - m_sidreGroup, - tetMesh->getTopologyName(), - tetMesh->getCoordsetName(), - tetMesh->getNumberOfNodes(), - tetMesh->getNumberOfCells()); - siMesh->appendNodes(tetMesh->getCoordinateArray(0), - tetMesh->getCoordinateArray(1), - tetMesh->getCoordinateArray(2), - tetMesh->getNumberOfNodes()); - siMesh->appendCells(tetMesh->getCellNodesArray(), - tetMesh->getNumberOfCells()); - m_meshRep.reset(siMesh); - delete mesh; - mesh = nullptr; - } - else - { - m_meshRep.reset(mesh); - } - - // Transform the coordinates of the linearized mesh. - applyTransforms(); + createVORRepresentation(); } if(m_meshRep) @@ -538,6 +231,349 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() return m_meshRep; } +void DiscreteShape::createBlueprintTetsRepresentation() +{ + const axom::klee::Geometry& geometry = m_shape.getGeometry(); + + // Put the in-memory geometry in m_meshRep. + const axom::sidre::Group* inputGroup = geometry.getBlueprintMesh(); + int allocID = inputGroup->getDefaultAllocatorID(); + + std::string modName = inputGroup->getName() + "_modified"; + while(m_sidreGroup->hasGroup(modName)) + { + modName = modName + "-"; + } + + axom::sidre::Group* modGroup = m_sidreGroup->createGroup(modName); + modGroup->deepCopyGroup(inputGroup, allocID); + + m_meshRep.reset( + axom::mint::getMesh(modGroup->getGroup(inputGroup->getName()), + m_shape.getGeometry().getBlueprintTopology())); + + // Transform the coordinates of the linearized mesh. + applyTransforms(); +} + +void DiscreteShape::createTetRepresentation() +{ + const axom::klee::Geometry& geometry = m_shape.getGeometry(); + + const auto& tet = geometry.getTet(); + + const axom::IndexType tetCount = 1; + const axom::IndexType nodeCount = 4; + axom::Array nodeCoords(nodeCount, 3); + axom::Array connectivity(tetCount, 4); + + for(int iNode = 0; iNode < 4; ++iNode) + { + const auto& coords = tet[iNode]; + nodeCoords[iNode][0] = coords[0]; + nodeCoords[iNode][1] = coords[1]; + nodeCoords[iNode][2] = coords[2]; + connectivity[0][iNode] = iNode; + } + + TetMesh* tetMesh = nullptr; + if(m_sidreGroup != nullptr) + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + m_sidreGroup, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + else + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + tetMesh->appendNodes((double*)nodeCoords.data(), nodeCoords.shape()[0]); + tetMesh->appendCells(connectivity.data(), connectivity.shape()[0]); + m_meshRep.reset(tetMesh); + + applyTransforms(); +} + +void DiscreteShape::createHexRepresentation() +{ + const axom::klee::Geometry& geometry = m_shape.getGeometry(); + const auto& hex = geometry.getHex(); + axom::StackArray tets; + hex.triangulate(tets); + + const axom::IndexType tetCount = HexType::NUM_TRIANGULATE; + const axom::IndexType nodeCount = HexType::NUM_TRIANGULATE * 4; + axom::Array nodeCoords(nodeCount, 3); + auto nodeCoordsView = nodeCoords.view(); + axom::Array connectivity(tetCount, 4); + auto connectivityView = connectivity.view(); + // NOTE: This is not much computation, so just run on host. + axom::for_all( + tetCount, + AXOM_LAMBDA(axom::IndexType iTet) { + const auto& tet = tets[iTet]; + for(int i = 0; i < 4; ++i) + { + axom::IndexType iNode = iTet * 4 + i; + const auto& coords = tet[i]; + nodeCoordsView[iNode][0] = coords[0]; + nodeCoordsView[iNode][1] = coords[1]; + nodeCoordsView[iNode][2] = coords[2]; + connectivityView[iTet][i] = iNode; + } + }); + + TetMesh* tetMesh = nullptr; + if(m_sidreGroup != nullptr) + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + m_sidreGroup, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + else + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + tetMesh->appendNodes((double*)nodeCoords.data(), nodeCoords.shape()[0]); + tetMesh->appendCells(connectivity.data(), connectivity.shape()[0]); + m_meshRep.reset(tetMesh); + + applyTransforms(); +} + +void DiscreteShape::createPlaneRepresentation() +{ + const axom::klee::Geometry& geometry = m_shape.getGeometry(); + + const auto& plane = geometry.getPlane(); + // Generate a big bounding hex on the positive side of the plane. + axom::primal::Hexahedron boundingHex; + const double len = 1e6; // Big enough to contain anticipated mesh. + // We should compute based on the mesh. + boundingHex[0] = Point3D{0.0, -len, -len}; + boundingHex[1] = Point3D{len, -len, -len}; + boundingHex[2] = Point3D{len, len, -len}; + boundingHex[3] = Point3D{0.0, len, -len}; + boundingHex[4] = Point3D{0.0, -len, len}; + boundingHex[5] = Point3D{len, -len, len}; + boundingHex[6] = Point3D{len, len, len}; + boundingHex[7] = Point3D{0.0, len, len}; + numerics::Matrix rotate = vorAxisRotMatrix(plane.getNormal()); + const auto translate = plane.getNormal() * plane.getOffset(); + for (int i = 0; i < 8; ++ i ) + { + Point3D newCoords; + numerics::matrix_vector_multiply(rotate, + boundingHex[i].data(), + newCoords.data()); + newCoords.array() += translate.array(); + boundingHex[i].array() = newCoords.array(); + } + + axom::StackArray tets; + boundingHex.triangulate(tets); + + const axom::IndexType tetCount = HexType::NUM_TRIANGULATE; + const axom::IndexType nodeCount = HexType::NUM_TRIANGULATE * 4; + axom::Array nodeCoords(nodeCount, 3); + auto nodeCoordsView = nodeCoords.view(); + axom::Array connectivity(tetCount, 4); + auto connectivityView = connectivity.view(); + // NOTE: This is not much computation, so just run on host. + axom::for_all( + tetCount, + AXOM_LAMBDA(axom::IndexType iTet) { + const auto& tet = tets[iTet]; + for(int i = 0; i < 4; ++i) + { + axom::IndexType iNode = iTet * 4 + i; + const auto& coords = tet[i]; + nodeCoordsView[iNode][0] = coords[0]; + nodeCoordsView[iNode][1] = coords[1]; + nodeCoordsView[iNode][2] = coords[2]; + connectivityView[iTet][i] = iNode; + } + }); + + TetMesh* tetMesh = nullptr; + if(m_sidreGroup != nullptr) + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + m_sidreGroup, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + else + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + nodeCoords.shape()[0], + connectivity.shape()[0]); + } + tetMesh->appendNodes((double*)nodeCoords.data(), nodeCoords.shape()[0]); + tetMesh->appendCells(connectivity.data(), connectivity.shape()[0]); + m_meshRep.reset(tetMesh); + + applyTransforms(); +} + +void DiscreteShape::createSphereRepresentation() +{ + const axom::klee::Geometry& geometry = m_shape.getGeometry(); + + /* + Discretize the sphere into m_meshRep and apply transforms: + 1. Discretize the sphere into a set of Octahedra. + 2. Split each Octahedron into 8 tets. + 3. Insert tets into tet mesh. + 4. Transform the tet mesh. + We can reduce memory use by eliminating repeated points if it + is a problem. + */ + const auto& sphere = geometry.getSphere(); + axom::Array octs; + int octCount = 0; + axom::quest::discretize(sphere, geometry.getLevelOfRefinement(), octs, octCount); + + constexpr int TETS_PER_OCT = 8; + constexpr int NODES_PER_TET = 4; + const axom::IndexType tetCount = octCount * TETS_PER_OCT; + const axom::IndexType nodeCount = tetCount * NODES_PER_TET; + + axom::Array nodeCoords(nodeCount, nodeCount); + axom::Array connectivity( + axom::StackArray {tetCount, NODES_PER_TET}); + + auto nodeCoordsView = nodeCoords.view(); + auto connectivityView = connectivity.view(); + axom::for_all( + octCount, + AXOM_LAMBDA(axom::IndexType octIdx) { + TetType tetsInOct[TETS_PER_OCT]; + axom::primal::split(octs[octIdx], tetsInOct); + for(int iTet = 0; iTet < TETS_PER_OCT; ++iTet) + { + axom::IndexType tetIdx = octIdx * TETS_PER_OCT + iTet; + for(int iNode = 0; iNode < NODES_PER_TET; ++iNode) + { + axom::IndexType nodeIdx = tetIdx * NODES_PER_TET + iNode; + nodeCoordsView[nodeIdx] = tetsInOct[iTet][iNode]; + connectivityView[tetIdx][iNode] = nodeIdx; + } + } + }); + + TetMesh* tetMesh = nullptr; + if(m_sidreGroup != nullptr) + { + tetMesh = + new TetMesh(3, axom::mint::CellType::TET, + m_sidreGroup, + nodeCount, + tetCount); + } + else + { + tetMesh = new TetMesh(3, + axom::mint::CellType::TET, + nodeCount, + tetCount); + } + tetMesh->appendNodes((double*)nodeCoords.data(), nodeCount); + tetMesh->appendCells(connectivity.data(), tetCount); + m_meshRep.reset(tetMesh); + + applyTransforms(); +} + +void DiscreteShape::createVORRepresentation() +{ + // Construct the tet m_meshRep from the volume-of-revolution. + auto& vorGeom = m_shape.getGeometry(); + const auto& discreteFcn = vorGeom.getDiscreteFunction(); + + // Generate the Octahedra + axom::Array octs; + int octCount = 0; + axom::ArrayView polyline((Point2D*)discreteFcn.data(), + discreteFcn.shape()[0]); + const bool good = axom::quest::discretize( + polyline, + int(polyline.size()), + m_shape.getGeometry().getLevelOfRefinement(), + octs, + octCount); + SLIC_ASSERT(good); + + // Rotate to the VOR axis direction and translate to the base location. + numerics::Matrix rotate = vorAxisRotMatrix(vorGeom.getVorDirection()); + const auto& translate = vorGeom.getVorBaseCoords(); + auto octsView = octs.view(); + axom::for_all( + octCount, + AXOM_LAMBDA(axom::IndexType iOct) { + auto& oct = octsView[iOct]; + for(int iVert = 0; iVert < OctType::NUM_VERTS; ++iVert) + { + auto& newCoords = oct[iVert]; + auto oldCoords = newCoords; + numerics::matrix_vector_multiply(rotate, + oldCoords.data(), + newCoords.data()); + newCoords.array() += translate.array(); + } + }); + + // Dump discretized octs as a tet mesh + axom::mint::Mesh* mesh; + axom::quest::mesh_from_discretized_polyline(octs.view(), + octCount, + polyline.size() - 1, + mesh); + + if(m_sidreGroup) + { + // If using sidre, copy the tetMesh into sidre. + auto* tetMesh = static_cast(mesh); + axom::mint::UnstructuredMesh* siMesh = + new axom::mint::UnstructuredMesh( + tetMesh->getDimension(), + tetMesh->getCellType(), + m_sidreGroup, + tetMesh->getTopologyName(), + tetMesh->getCoordsetName(), + tetMesh->getNumberOfNodes(), + tetMesh->getNumberOfCells()); + siMesh->appendNodes(tetMesh->getCoordinateArray(0), + tetMesh->getCoordinateArray(1), + tetMesh->getCoordinateArray(2), + tetMesh->getNumberOfNodes()); + siMesh->appendCells(tetMesh->getCellNodesArray(), + tetMesh->getNumberOfCells()); + m_meshRep.reset(siMesh); + delete mesh; + mesh = nullptr; + } + else + { + m_meshRep.reset(mesh); + } + + // Transform the coordinates of the linearized mesh. + applyTransforms(); +} + void DiscreteShape::applyTransforms() { numerics::Matrix transformation = getTransforms(); diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index b217ce8841..8b77697b4f 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -10,7 +10,7 @@ #include #include "axom/klee/Shape.hpp" -#include "axom/mint/mesh/Mesh.hpp" +#include "axom/mint/mesh/UnstructuredMesh.hpp" #if defined(AXOM_USE_MPI) #include "mpi.h" @@ -38,6 +38,10 @@ class DiscreteShape using OctType = axom::primal::Octahedron; using HexType = axom::primal::Hexahedron; + // Common type for the mesh approximation of the shape. + using TetMesh = + axom::mint::UnstructuredMesh; + static constexpr int DEFAULT_SAMPLES_PER_KNOT_SPAN {25}; static constexpr double MINIMUM_PERCENT_ERROR {0.}; static constexpr double MAXIMUM_PERCENT_ERROR {100.}; @@ -176,6 +180,15 @@ class DiscreteShape numerics::Matrix vorAxisRotMatrix(const Vector3D& dir); void clearInternalData(); + +public: + // These are public only for the CUDA device compiler. + void createBlueprintTetsRepresentation(); + void createTetRepresentation(); + void createHexRepresentation(); + void createPlaneRepresentation(); + void createSphereRepresentation(); + void createVORRepresentation(); }; } // namespace quest From 408b4271f770edade0bb7f68454f89321b306398 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 2 Oct 2024 07:32:56 -0700 Subject: [PATCH 24/43] Reformat. --- src/axom/klee/Geometry.cpp | 20 ++++++++------- src/axom/klee/Geometry.hpp | 2 +- src/axom/quest/DiscreteShape.cpp | 25 +++++++------------ src/axom/quest/DiscreteShape.hpp | 2 +- src/axom/quest/Shaper.cpp | 6 ++--- .../quest/examples/quest_shape_in_memory.cpp | 19 ++++++++------ 6 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index a23fad0ab8..acb074c521 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -45,7 +45,8 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, { #ifdef AXOM_DEBUG SLIC_ASSERT_MSG(isBlueprintTetMesh(m_meshGroup), - "Mesh provided to Geometry is not a valid blueprint unstructured tetrahedral mesh."); + "Mesh provided to Geometry is not a valid blueprint " + "unstructured tetrahedral mesh."); #endif } @@ -164,38 +165,39 @@ const std::string& Geometry::getBlueprintTopology() const return m_topology; } - -bool Geometry::isBlueprintTetMesh(const axom::sidre::Group *meshGroup) const +bool Geometry::isBlueprintTetMesh(const axom::sidre::Group* meshGroup) const { conduit::Node bpMesh; meshGroup->createNativeLayout(bpMesh); conduit::Node info; bool isValid = conduit::blueprint::mesh::verify(bpMesh, info); - if (!isValid) + if(!isValid) { return false; } - const auto& topology = bpMesh.fetch_existing(axom::fmt::format("topologies/{}", m_topology)); + const auto& topology = + bpMesh.fetch_existing(axom::fmt::format("topologies/{}", m_topology)); std::string coordsetName = topology.fetch_existing("coordset").as_string(); - const auto& coordSet = bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", coordsetName)); + const auto& coordSet = + bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", coordsetName)); auto dim = conduit::blueprint::mesh::coordset::dims(coordSet); - if (dim != 3) + if(dim != 3) { return false; } auto topoType = topology.fetch_existing("type").as_string(); - if (topoType != "unstructured") + if(topoType != "unstructured") { return false; } auto shapeType = topology.fetch_existing("elements/shape").as_string(); - if (shapeType != "tet") + if(shapeType != "tet") { return false; } diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 82cd5eb506..4206dd0eb2 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -189,7 +189,7 @@ class Geometry * - "sphere3D" = 3D sphere, as \c primal::Sphere * - "vor3D" = 3D volume of revolution. * - "cone3D" = 3D cone, as \c primal::Cone - * "cylinder3D" = 3D cylinder, as \c primal::Cylinder + * - "cylinder3D" = 3D cylinder, as \c primal::Cylinder * - "hex3D" = 3D hexahedron (8 points) * - "plane3D" = 3D plane * diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 8e2e242124..dfb58fdfc9 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -92,10 +92,7 @@ DiscreteShape::DiscreteShape(const axom::klee::Shape& shape, setParentGroup(parentGroup); } -void DiscreteShape::clearInternalData() -{ - m_meshRep.reset(); -} +void DiscreteShape::clearInternalData() { m_meshRep.reset(); } std::shared_ptr DiscreteShape::createMeshRepresentation() { @@ -358,8 +355,9 @@ void DiscreteShape::createPlaneRepresentation() const auto& plane = geometry.getPlane(); // Generate a big bounding hex on the positive side of the plane. axom::primal::Hexahedron boundingHex; - const double len = 1e6; // Big enough to contain anticipated mesh. + const double len = 1e6; // Big enough to contain anticipated mesh. // We should compute based on the mesh. + // clang-format off boundingHex[0] = Point3D{0.0, -len, -len}; boundingHex[1] = Point3D{len, -len, -len}; boundingHex[2] = Point3D{len, len, -len}; @@ -368,9 +366,10 @@ void DiscreteShape::createPlaneRepresentation() boundingHex[5] = Point3D{len, -len, len}; boundingHex[6] = Point3D{len, len, len}; boundingHex[7] = Point3D{0.0, len, len}; + // clang-format on numerics::Matrix rotate = vorAxisRotMatrix(plane.getNormal()); const auto translate = plane.getNormal() * plane.getOffset(); - for (int i = 0; i < 8; ++ i ) + for(int i = 0; i < 8; ++i) { Point3D newCoords; numerics::matrix_vector_multiply(rotate, @@ -478,17 +477,11 @@ void DiscreteShape::createSphereRepresentation() if(m_sidreGroup != nullptr) { tetMesh = - new TetMesh(3, axom::mint::CellType::TET, - m_sidreGroup, - nodeCount, - tetCount); + new TetMesh(3, axom::mint::CellType::TET, m_sidreGroup, nodeCount, tetCount); } else { - tetMesh = new TetMesh(3, - axom::mint::CellType::TET, - nodeCount, - tetCount); + tetMesh = new TetMesh(3, axom::mint::CellType::TET, nodeCount, tetCount); } tetMesh->appendNodes((double*)nodeCoords.data(), nodeCount); tetMesh->appendCells(connectivity.data(), tetCount); @@ -757,11 +750,11 @@ void DiscreteShape::setParentGroup(axom::sidre::Group* parentGroup) // Use object address to create a unique name for sidre group under parent. std::string myGroupName; int i = 0; - while (myGroupName.empty()) + while(myGroupName.empty()) { std::ostringstream os; os << "DiscreteShapeTemp-" << i; - if ( !parentGroup->hasGroup(os.str()) ) + if(!parentGroup->hasGroup(os.str())) { myGroupName = os.str(); } diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index 8b77697b4f..18d2332bb6 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -62,7 +62,7 @@ class DiscreteShape */ DiscreteShape(const axom::klee::Shape& shape, axom::sidre::Group* parentGroup, - const std::string& prefixPath = {} ); + const std::string& prefixPath = {}); virtual ~DiscreteShape() { clearInternalData(); } diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index b153e498e8..c3f0a9ca3e 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -92,9 +92,9 @@ void Shaper::setRefinementType(Shaper::RefinementType t) bool Shaper::isValidFormat(const std::string& format) const { return (format == "stl" || format == "proe" || format == "c2c" || - format == "blueprint-tets" || format == "tet3D" || format == "hex3D" || - format == "plane3D" || format == "sphere3D" || format == "vor3D" || - format == "none"); + format == "blueprint-tets" || format == "tet3D" || + format == "hex3D" || format == "plane3D" || format == "sphere3D" || + format == "vor3D" || format == "none"); } void Shaper::loadShape(const klee::Shape& shape) diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 29e6f35a85..ddfc5f04e4 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -122,9 +122,9 @@ struct Input /// @brief Return volume of input box mesh double boxMeshVolume() const { - primal::Vector x{boxMaxs[0] - boxMins[0], 0, 0}; - primal::Vector y{0, boxMaxs[1] - boxMins[1], 0}; - primal::Vector z{0, 0, boxMaxs[2] - boxMins[2]}; + primal::Vector x {boxMaxs[0] - boxMins[0], 0, 0}; + primal::Vector y {0, boxMaxs[1] - boxMins[1], 0}; + primal::Vector z {0, 0, boxMaxs[2] - boxMins[2]}; double volume = primal::Vector::scalar_triple_product(x, y, z); return volume; } @@ -806,9 +806,10 @@ axom::klee::Shape createShape_Plane() // Create a plane crossing center of mesh. No matter the normal, // it cuts the mesh in half. - Point3D center{0.5*(primal::NumericArray(params.boxMins.data()) + - primal::NumericArray(params.boxMaxs.data()))}; - primal::Vector normal{1.0, 0.0, 0.0}; + Point3D center {0.5 * + (primal::NumericArray(params.boxMins.data()) + + primal::NumericArray(params.boxMaxs.data()))}; + primal::Vector normal {1.0, 0.0, 0.0}; const primal::Plane plane {normal, center, true}; axom::klee::Geometry planeGeometry(prop, plane, scaleOp); @@ -831,7 +832,8 @@ double volumeOfTetMesh( const axom::mint::UnstructuredMesh& tetMesh) { using TetType = axom::primal::Tetrahedron; - if(0) { + if(0) + { std::ofstream os("tets.js"); tetMesh.getSidreGroup()->print(os); } @@ -1404,7 +1406,8 @@ int main(int argc, char** argv) const std::string& materialName = shape.getMaterial(); double shapeVol = sumMaterialVolumes(&shapingDC, materialName); - double correctShapeVol = params.testShape == "plane" ? params.boxMeshVolume()/2 : shapeMeshVol; + double correctShapeVol = + params.testShape == "plane" ? params.boxMeshVolume() / 2 : shapeMeshVol; double diff = shapeVol - correctShapeVol; bool err = !axom::utilities::isNearlyEqual(shapeVol, correctShapeVol); From 39232fd0d34b0c27c3bbe3337eed5dbc549be74d Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 2 Oct 2024 12:05:58 -0700 Subject: [PATCH 25/43] Check for valid sidre Group in cases where it's required. --- src/axom/quest/DiscreteShape.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index dfb58fdfc9..4ceec6a3ed 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -90,6 +90,11 @@ DiscreteShape::DiscreteShape(const axom::klee::Shape& shape, { setPrefixPath(prefixPath); setParentGroup(parentGroup); + + if (parentGroup == nullptr && m_shape.getGeometry().getFormat() == "blueprint-tets") + { + SLIC_ERROR("DiscreteShape: Support for Blueprint-mesh shape format currently requires a non-null parentGroup in the constructor. This restriction can be lifted with additional coding, so please file an issue with the Axom team if you need this feature."); + } } void DiscreteShape::clearInternalData() { m_meshRep.reset(); } @@ -230,6 +235,8 @@ std::shared_ptr DiscreteShape::createMeshRepresentation() void DiscreteShape::createBlueprintTetsRepresentation() { + SLIC_ASSERT(m_sidreGroup != nullptr); + const axom::klee::Geometry& geometry = m_shape.getGeometry(); // Put the in-memory geometry in m_meshRep. @@ -242,6 +249,13 @@ void DiscreteShape::createBlueprintTetsRepresentation() modName = modName + "-"; } + /* + We use a sidre::Group::deepCopyGroup as a convenient way to copy + the mesh. This requires a non-null m_sidreGroup. This + restriction can be lifted if we implement code to copy the + inputGroup directly into an mint::UnstructuredMesh without putting + it into another sidre::Group. We currently don't have that. + */ axom::sidre::Group* modGroup = m_sidreGroup->createGroup(modName); modGroup->deepCopyGroup(inputGroup, allocID); From 7507658caf18fd8be0ea58dcc47942a9ee87de00 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sun, 6 Oct 2024 06:02:47 -0700 Subject: [PATCH 26/43] Temporary work-around for data re-allocation crash in docker tests. See issue 1271. --- src/axom/core/memory_management.hpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/axom/core/memory_management.hpp b/src/axom/core/memory_management.hpp index 7edbd51304..1feb63bee6 100644 --- a/src/axom/core/memory_management.hpp +++ b/src/axom/core/memory_management.hpp @@ -235,7 +235,28 @@ inline T* reallocate(T* pointer, std::size_t n, int allocID) noexcept } else { - pointer = static_cast(rm.reallocate(pointer, numbytes)); + auto oldPointer = pointer; + auto foundAllocator = rm.getAllocator(pointer); + auto size = foundAllocator.getSize(pointer); + constexpr bool workAround = true; + if(workAround) + { + /* + This empty-buffer work-around addresses issue #1287 and PR + #1271. The reproducer is the immediate_ug_reserve test in + file axom/src/axom/quest/test/quest_initialize.cpp. This + work-around doesn't address the actual cause of the problem, + something we should try to identify and fix. + */ + pointer = static_cast(foundAllocator.allocate(numbytes)); + auto copysize = std::min(size, numbytes); + axom::copy(pointer, oldPointer, copysize); + axom::deallocate(oldPointer); + } + else + { + pointer = static_cast(rm.reallocate(pointer, numbytes)); + } } #else From d9a7fc35905c129414e0aa7cf72710f0b4708ff3 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Sun, 6 Oct 2024 07:01:31 -0700 Subject: [PATCH 27/43] Autoformat and change parameter comment. --- src/axom/quest/DiscreteShape.cpp | 9 +++++++-- src/axom/quest/DiscreteShape.hpp | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 4ceec6a3ed..10ec8b5e1f 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -91,9 +91,14 @@ DiscreteShape::DiscreteShape(const axom::klee::Shape& shape, setPrefixPath(prefixPath); setParentGroup(parentGroup); - if (parentGroup == nullptr && m_shape.getGeometry().getFormat() == "blueprint-tets") + if(parentGroup == nullptr && + m_shape.getGeometry().getFormat() == "blueprint-tets") { - SLIC_ERROR("DiscreteShape: Support for Blueprint-mesh shape format currently requires a non-null parentGroup in the constructor. This restriction can be lifted with additional coding, so please file an issue with the Axom team if you need this feature."); + SLIC_ERROR( + "DiscreteShape: Support for Blueprint-mesh shape format currently " + "requires a non-null parentGroup in the constructor. This restriction " + "can be lifted with additional coding, so please file an issue with the " + "Axom team if you need this feature."); } } diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index 18d2332bb6..48447245d6 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -51,8 +51,9 @@ class DiscreteShape @brief Constructor. @param shape The Klee specifications for the shape. - @param parentGroup Group under which to put the discrete mesh. - If null, don't use sidre. + @param parentGroup Group under which to put the discrete mesh + and support blueprint-tets shapes. + If null, don't use sidre and don't support blueprint-tets. @param prefixPath Path prefix for shape files specified with a relative path. From 8d6db24616253d31df2a64b8561c9df97a3075a6 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 9 Oct 2024 08:14:27 -0700 Subject: [PATCH 28/43] Temporarily disable a sanity check that causes weird crash in the docker gcc image, klee_units_test, even if the check is not executed. --- src/axom/klee/Geometry.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index acb074c521..c32ff98af7 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -44,10 +44,12 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, , m_operator(std::move(operator_)) { #ifdef AXOM_DEBUG +#if 0 SLIC_ASSERT_MSG(isBlueprintTetMesh(m_meshGroup), "Mesh provided to Geometry is not a valid blueprint " "unstructured tetrahedral mesh."); #endif +#endif } Geometry::Geometry(const TransformableGeometryProperties& startProperties, @@ -165,6 +167,7 @@ const std::string& Geometry::getBlueprintTopology() const return m_topology; } +#if 0 bool Geometry::isBlueprintTetMesh(const axom::sidre::Group* meshGroup) const { conduit::Node bpMesh; @@ -172,6 +175,8 @@ bool Geometry::isBlueprintTetMesh(const axom::sidre::Group* meshGroup) const conduit::Node info; bool isValid = conduit::blueprint::mesh::verify(bpMesh, info); + // The above call to verify causes the crash, even though this + // function is never entered. if(!isValid) { return false; @@ -204,6 +209,7 @@ bool Geometry::isBlueprintTetMesh(const axom::sidre::Group* meshGroup) const return true; } +#endif } // namespace klee } // namespace axom From 848a44908295c0598a03e23d26da06e75c6b9dc4 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 9 Oct 2024 05:15:49 -0700 Subject: [PATCH 29/43] Fix error in non-Umpire build. --- src/axom/quest/DiscreteShape.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 10ec8b5e1f..39070f48ce 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -246,7 +246,11 @@ void DiscreteShape::createBlueprintTetsRepresentation() // Put the in-memory geometry in m_meshRep. const axom::sidre::Group* inputGroup = geometry.getBlueprintMesh(); +#ifdef AXOM_USE_UMPIRE int allocID = inputGroup->getDefaultAllocatorID(); +#else + int allocID = axom::execution_space::allocatorID(); +#endif std::string modName = inputGroup->getName() + "_modified"; while(m_sidreGroup->hasGroup(modName)) From 5c76ae5fd6634a2b8dc7525dec918660cbfea7e2 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 9 Oct 2024 05:26:39 -0700 Subject: [PATCH 30/43] In-memory shaping example requires RAJA, so disable if no raja. --- src/axom/quest/examples/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 75a358928c..1cab86c547 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -267,7 +267,7 @@ endif() # Shaping in-memory example ------------------------------------------------------ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_MFEM_SIDRE_DATACOLLECTION - AND AXOM_ENABLE_KLEE) + AND AXOM_ENABLE_KLEE AND RAJA_FOUND) axom_add_executable( NAME quest_shape_in_memory_ex SOURCES quest_shape_in_memory.cpp From 8b3889119ccb7394db6e63bcef1b9f9933316f54 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 11 Oct 2024 05:46:38 -0700 Subject: [PATCH 31/43] Fix non-umpire build. --- src/axom/klee/Geometry.cpp | 4 +- src/axom/quest/IntersectionShaper.hpp | 38 +++++++++---------- .../quest/examples/quest_shape_in_memory.cpp | 9 +++-- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index c32ff98af7..79333f2eed 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -44,11 +44,11 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, , m_operator(std::move(operator_)) { #ifdef AXOM_DEBUG -#if 0 + #if 0 SLIC_ASSERT_MSG(isBlueprintTetMesh(m_meshGroup), "Mesh provided to Geometry is not a valid blueprint " "unstructured tetrahedral mesh."); -#endif + #endif #endif } diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 506516f0ce..573288eba6 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -41,7 +41,7 @@ #endif // clang-format off -#if defined (AXOM_USE_RAJA) && defined (AXOM_USE_UMPIRE) +#if defined (AXOM_USE_RAJA) using seq_exec = axom::SEQ_EXEC; #if defined(AXOM_USE_OPENMP) @@ -50,14 +50,14 @@ using omp_exec = seq_exec; #endif - #if defined(AXOM_USE_CUDA) + #if defined(AXOM_USE_CUDA) && defined (AXOM_USE_UMPIRE) constexpr int CUDA_BLOCK_SIZE = 256; using cuda_exec = axom::CUDA_EXEC; #else using cuda_exec = seq_exec; #endif - #if defined(AXOM_USE_HIP) + #if defined(AXOM_USE_HIP) && defined (AXOM_USE_UMPIRE) constexpr int HIP_BLOCK_SIZE = 64; using hip_exec = axom::HIP_EXEC; #else @@ -376,7 +376,7 @@ class IntersectionShaper : public Shaper //@{ //! @name Functions related to the stages for a given shape -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) +#if defined(AXOM_USE_RAJA) // Prepares the tet mesh mesh cells for the spatial index template @@ -619,7 +619,7 @@ class IntersectionShaper : public Shaper } #endif -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) +#if defined(AXOM_USE_RAJA) template void runShapeQueryImpl(const klee::Shape& shape, axom::Array& shapes, @@ -997,7 +997,7 @@ class IntersectionShaper : public Shaper } // end of runShapeQuery() function #endif -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) +#if defined(AXOM_USE_RAJA) // These methods are private in support of replacement rules. private: /*! @@ -1408,7 +1408,7 @@ class IntersectionShaper : public Shaper switch(m_execPolicy) { -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) +#if defined(AXOM_USE_RAJA) case RuntimePolicy::seq: applyReplacementRulesImpl(shape); break; @@ -1417,12 +1417,12 @@ class IntersectionShaper : public Shaper applyReplacementRulesImpl(shape); break; #endif // AXOM_USE_OPENMP - #if defined(AXOM_USE_CUDA) + #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE) case RuntimePolicy::cuda: applyReplacementRulesImpl(shape); break; #endif // AXOM_USE_CUDA - #if defined(AXOM_USE_HIP) + #if defined(AXOM_USE_HIP) && defined(AXOM_USE_UMPIRE) case RuntimePolicy::hip: applyReplacementRulesImpl(shape); break; @@ -1464,7 +1464,7 @@ class IntersectionShaper : public Shaper // Now that the mesh is refined, dispatch to device implementations. switch(m_execPolicy) { -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) +#if defined(AXOM_USE_RAJA) case RuntimePolicy::seq: prepareShapeQueryImpl(shapeDimension, shape); break; @@ -1473,12 +1473,12 @@ class IntersectionShaper : public Shaper prepareShapeQueryImpl(shapeDimension, shape); break; #endif // AXOM_USE_OPENMP - #if defined(AXOM_USE_CUDA) + #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE) case RuntimePolicy::cuda: prepareShapeQueryImpl(shapeDimension, shape); break; #endif // AXOM_USE_CUDA - #if defined(AXOM_USE_HIP) + #if defined(AXOM_USE_HIP) && defined(AXOM_USE_UMPIRE) case RuntimePolicy::hip: prepareShapeQueryImpl(shapeDimension, shape); break; @@ -1505,7 +1505,7 @@ class IntersectionShaper : public Shaper { switch(m_execPolicy) { -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) +#if defined(AXOM_USE_RAJA) case RuntimePolicy::seq: runShapeQueryImpl(shape, m_tets, m_tetcount); break; @@ -1514,12 +1514,12 @@ class IntersectionShaper : public Shaper runShapeQueryImpl(shape, m_tets, m_tetcount); break; #endif // AXOM_USE_OPENMP - #if defined(AXOM_USE_CUDA) + #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE) case RuntimePolicy::cuda: runShapeQueryImpl(shape, m_tets, m_tetcount); break; #endif // AXOM_USE_CUDA - #if defined(AXOM_USE_HIP) + #if defined(AXOM_USE_HIP) && defined(AXOM_USE_UMPIRE) case RuntimePolicy::hip: runShapeQueryImpl(shape, m_tets, m_tetcount); break; @@ -1531,7 +1531,7 @@ class IntersectionShaper : public Shaper { switch(m_execPolicy) { -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) +#if defined(AXOM_USE_RAJA) case RuntimePolicy::seq: runShapeQueryImpl(shape, m_octs, m_octcount); break; @@ -1540,12 +1540,12 @@ class IntersectionShaper : public Shaper runShapeQueryImpl(shape, m_octs, m_octcount); break; #endif // AXOM_USE_OPENMP - #if defined(AXOM_USE_CUDA) + #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_UMPIRE) case RuntimePolicy::cuda: runShapeQueryImpl(shape, m_octs, m_octcount); break; #endif // AXOM_USE_CUDA - #if defined(AXOM_USE_HIP) + #if defined(AXOM_USE_HIP) && defined(AXOM_USE_UMPIRE) case RuntimePolicy::hip: runShapeQueryImpl(shape, m_octs, m_octcount); break; @@ -2054,7 +2054,7 @@ class IntersectionShaper : public Shaper axom::Array m_hex_volumes; axom::Array m_overlap_volumes; -#if defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) +#if defined(AXOM_USE_RAJA) double m_vertexWeldThreshold {1.e-10}; int m_octcount {0}; int m_tetcount {0}; diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index ddfc5f04e4..a9a0c91dd1 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -1302,7 +1302,9 @@ int main(int argc, char** argv) // Compute and print volumes of each material's volume fraction //--------------------------------------------------------------------------- using axom::utilities::string::startsWith; - for(auto& kv : shaper->getDC()->GetFieldMap()) + auto dc = shaper->getDC(); + auto& fieldMap = dc->GetFieldMap(); + for(auto& kv : fieldMap) { if(startsWith(kv.first, "vol_frac_")) { @@ -1326,8 +1328,9 @@ int main(int argc, char** argv) int failCounts = 0; - auto* volFracGroups = - shapingDC.GetBPGroup()->getGroup("matsets/material/volume_fractions"); + auto matsetsGrp = shapingDC.GetBPGroup()->getGroup("matsets"); + auto materialGrp = matsetsGrp->getGroup("material"); + auto volFracGroups = materialGrp->getGroup("volume_fractions"); //--------------------------------------------------------------------------- // Correctness test: volume fractions should be in [0,1]. From c17f1024b3332e9dfddc035fc4df74b5342a83da Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 11 Oct 2024 11:37:41 -0700 Subject: [PATCH 32/43] Undo the work-around for the (now fixed) Umpire reallocate crash. --- src/axom/core/memory_management.hpp | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/axom/core/memory_management.hpp b/src/axom/core/memory_management.hpp index 5991a77fee..3475694e6a 100644 --- a/src/axom/core/memory_management.hpp +++ b/src/axom/core/memory_management.hpp @@ -235,28 +235,7 @@ inline T* reallocate(T* pointer, std::size_t n, int allocID) noexcept } else { - auto oldPointer = pointer; - auto foundAllocator = rm.getAllocator(pointer); - auto size = foundAllocator.getSize(pointer); - constexpr bool workAround = true; - if(workAround) - { - /* - This empty-buffer work-around addresses issue #1287 and PR - #1271. The reproducer is the immediate_ug_reserve test in - file axom/src/axom/quest/test/quest_initialize.cpp. This - work-around doesn't address the actual cause of the problem, - something we should try to identify and fix. - */ - pointer = static_cast(foundAllocator.allocate(numbytes)); - auto copysize = std::min(size, numbytes); - axom::copy(pointer, oldPointer, copysize); - axom::deallocate(oldPointer); - } - else - { - pointer = static_cast(rm.reallocate(pointer, numbytes)); - } + pointer = static_cast(rm.reallocate(pointer, numbytes)); } #else From 6bc441d067412ee953e2da9ee8ae9640189d1516 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Fri, 11 Oct 2024 12:22:33 -0700 Subject: [PATCH 33/43] Comment changes from code review. --- src/axom/klee/Geometry.hpp | 13 ++++--------- src/axom/quest/DiscreteShape.hpp | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/axom/klee/Geometry.hpp b/src/axom/klee/Geometry.hpp index 4206dd0eb2..d794ee7132 100644 --- a/src/axom/klee/Geometry.hpp +++ b/src/axom/klee/Geometry.hpp @@ -82,12 +82,10 @@ class Geometry * * \param startProperties the transformable properties before any * operators are applied - * \param meshGroup a simplex geometry in blueprint format. + * \param meshGroup the geometry in blueprint format. * The elements should be segments, triangles or tetrahedra. * \param topology The blueprint topology to use. * \param operator_ a possibly null operator to apply to the geometry. - * - * \internal TODO: Is this the simplex requirement overly restrictive? */ Geometry(const TransformableGeometryProperties &startProperties, const axom::sidre::Group *meshGroup, @@ -127,8 +125,6 @@ class Geometry * \param levelOfRefinement Number of refinement levels to use for * discretizing the sphere. * \param operator_ a possibly null operator to apply to the geometry. - * - * \internal TODO: Is this the simplex requirement overly restrictive? */ Geometry(const TransformableGeometryProperties &startProperties, const axom::primal::Sphere &sphere, @@ -145,15 +141,13 @@ class Geometry * \param vorBase Coordinates of the base of the VOR. * \param vorDirection VOR axis, in the direction of increasing z. * \param levelOfRefinement Number of refinement levels to use for - * discretizing the sphere. + * discretizing the VOR. * \param operator_ a possibly null operator to apply to the geometry. * * The \c discreteFunction should be an Nx2 array, interpreted as * (z,r) pairs, where z is the axial distance and r is the radius. * The \c vorBase coordinates corresponds to z=0. * \c vorAxis should point in the direction of increasing z. - * - * \internal TODO: Is this the simplex requirement overly restrictive? */ Geometry(const TransformableGeometryProperties &startProperties, const axom::Array &discreteFunction, @@ -196,7 +190,8 @@ class Geometry * \return the format of the shape * * TODO: Depending on the specified geometry, some members are not - * used. It may clarify make each supported geometry a subclass. + * used. It may clarify if we make each supported geometry a + * subclass. */ const std::string &getFormat() const { return m_format; } diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index 48447245d6..e7a03614ab 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -115,7 +115,7 @@ class DiscreteShape If the sidre parent group was used in the constructor, the mesh data is stored under that group. - If the discrete mesh isn't generated yet (for analytical shapes) + If the discrete mesh isn't generated yet (for analytical shapes), generate it. */ std::shared_ptr createMeshRepresentation(); From 4b8d9f3dc00fe6dbd96852b3a53e1bb8e933adc3 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 14 Oct 2024 12:04:40 -0700 Subject: [PATCH 34/43] Remove support for reading in MFEM mesh in the new test. The feature was never used and we are moving away from MFEM mesh in favor of blueprint mesh. --- .../quest/examples/quest_shape_in_memory.cpp | 43 ++++--------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index a9a0c91dd1..bb1c6d499d 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -70,7 +70,6 @@ using RuntimePolicy = axom::runtime_policy::Policy; struct Input { public: - std::string meshFile; std::string outputFile; std::vector center {0.0, 0.0, 0.0}; @@ -82,6 +81,9 @@ struct Input // Shape transformation parameters std::vector scaleFactors; + // Mesh format: mfem or blueprint + std::string meshFormat = "mfem"; + // Inline mesh parameters std::vector boxMins; std::vector boxMaxs; @@ -190,32 +192,16 @@ struct Input std::unique_ptr loadComputationalMesh() { constexpr bool dc_owns_data = true; - mfem::Mesh* mesh = meshFile.empty() ? createBoxMesh() : nullptr; - std::string name = meshFile.empty() ? "mesh" : getDCMeshName(); + mfem::Mesh* mesh = createBoxMesh(); + std::string name = "mesh"; auto dc = std::unique_ptr( new sidre::MFEMSidreDataCollection(name, mesh, dc_owns_data)); dc->SetComm(MPI_COMM_WORLD); - if(!meshFile.empty()) - { - dc->Load(meshFile, "sidre_hdf5"); - } - return dc; } - std::string getDCMeshName() const - { - using axom::utilities::string::removeSuffix; - - // Remove the parent directories and file suffix - std::string name = axom::Path(meshFile).baseName(); - name = removeSuffix(name, ".root"); - - return name; - } - void parse(int argc, char** argv, axom::CLI::App& app) { app.add_option("-o,--outputFile", outputFile) @@ -289,12 +275,6 @@ struct Input // use either an input mesh file or a simple inline Cartesian mesh { - auto* mesh_file = - app.add_option("-m,--mesh-file", meshFile) - ->description( - "Path to computational mesh (generated by MFEMSidreDataCollection)") - ->check(axom::CLI::ExistingFile); - auto* inline_mesh_subcommand = app.add_subcommand("inline_mesh") ->description("Options for setting up a simple inline mesh") @@ -314,15 +294,10 @@ struct Input ->expected(2, 3) ->required(); - auto* inline_mesh_dim = - inline_mesh_subcommand->add_option("-d,--dimension", boxDim) - ->description("Dimension of the box mesh") - ->check(axom::CLI::PositiveNumber) - ->required(); - - // we want either the mesh_file or an inline mesh - mesh_file->excludes(inline_mesh_dim); - inline_mesh_dim->excludes(mesh_file); + inline_mesh_subcommand->add_option("-d,--dimension", boxDim) + ->description("Dimension of the box mesh") + ->check(axom::CLI::PositiveNumber) + ->required(); } app.add_option("--background-material", backgroundMaterial) From 8f12aa97b405d432885a6bb6735e3afb6d15da71 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 14 Oct 2024 12:28:01 -0700 Subject: [PATCH 35/43] Remove unused code (for sampling shaping) in the new test. --- src/axom/quest/examples/CMakeLists.txt | 2 +- .../quest/examples/quest_shape_in_memory.cpp | 127 +----------------- 2 files changed, 2 insertions(+), 127 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 1cab86c547..e8032d21aa 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -298,7 +298,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI COMMAND quest_shape_in_memory_ex --policy ${_policy} --testShape ${_testshape} - --method intersection --refinements 2 + --refinements 2 --scale 0.75 0.75 0.75 inline_mesh --min -3 -3 -3 --max 3 3 3 --res 20 20 20 -d 3 NUM_MPI_TASKS ${_nranks}) diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index bb1c6d499d..e054da24a8 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -53,17 +53,8 @@ namespace quest = axom::quest; namespace slic = axom::slic; namespace sidre = axom::sidre; -using VolFracSampling = quest::shaping::VolFracSampling; - //------------------------------------------------------------------------------ -/// Struct to help choose our shaping method: sampling or intersection for now -enum class ShapingMethod : int -{ - Sampling, - Intersection -}; - using RuntimePolicy = axom::runtime_policy::Policy; /// Struct to parse and store the input parameters @@ -102,7 +93,6 @@ struct Input "hex", "plane"}; - ShapingMethod shapingMethod {ShapingMethod::Sampling}; RuntimePolicy policy {RuntimePolicy::seq}; int quadratureOrder {5}; int outputOrder {2}; @@ -113,8 +103,6 @@ struct Input std::string backgroundMaterial; - VolFracSampling vfSampling {VolFracSampling::SAMPLE_AT_QPTS}; - private: bool m_verboseOutput {false}; @@ -225,16 +213,6 @@ struct Input ->check(axom::CLI::PositiveNumber) ->capture_default_str(); - std::map methodMap { - {"sampling", ShapingMethod::Sampling}, - {"intersection", ShapingMethod::Intersection}}; - app.add_option("--method", shapingMethod) - ->description( - "Determines the shaping method -- either sampling or intersection") - ->capture_default_str() - ->transform( - axom::CLI::CheckedTransformer(methodMap, axom::CLI::ignore_case)); - app.add_option("-s,--testShape", testShape) ->description("The shape to run") ->check(axom::CLI::IsMember(availableShapes)); @@ -303,38 +281,6 @@ struct Input app.add_option("--background-material", backgroundMaterial) ->description("Sets the name of the background material"); - // parameters that only apply to the sampling method - { - auto* sampling_options = - app.add_option_group("sampling", - "Options related to sampling-based queries"); - - sampling_options->add_option("-o,--order", outputOrder) - ->description("Order of the output grid function") - ->capture_default_str() - ->check(axom::CLI::NonNegativeNumber); - - sampling_options->add_option("-q,--quadrature-order", quadratureOrder) - ->description( - "Quadrature order for sampling the inout field. \n" - "Determines number of samples per element in determining " - "volume fraction field") - ->capture_default_str() - ->check(axom::CLI::PositiveNumber); - - std::map vfsamplingMap { - {"qpts", VolFracSampling::SAMPLE_AT_QPTS}, - {"dofs", VolFracSampling::SAMPLE_AT_DOFS}}; - sampling_options->add_option("-s,--sampling-type", vfSampling) - ->description( - "Sampling strategy. \n" - "Sampling either at quadrature points or collocated with " - "degrees of freedom") - ->capture_default_str() - ->transform( - axom::CLI::CheckedTransformer(vfsamplingMap, axom::CLI::ignore_case)); - } - // parameters that only apply to the intersection method { auto* intersection_options = @@ -1128,15 +1074,7 @@ int main(int argc, char** argv) //--------------------------------------------------------------------------- AXOM_ANNOTATE_BEGIN("setup shaping problem"); quest::Shaper* shaper = nullptr; - switch(params.shapingMethod) - { - case ShapingMethod::Sampling: - shaper = new quest::SamplingShaper(shapeSet, &shapingDC); - break; - case ShapingMethod::Intersection: - shaper = new quest::IntersectionShaper(shapeSet, &shapingDC); - break; - } + shaper = new quest::IntersectionShaper(shapeSet, &shapingDC); SLIC_ASSERT_MSG(shaper != nullptr, "Invalid shaping method selected!"); // Set generic parameters for the base Shaper instance @@ -1152,25 +1090,6 @@ int main(int argc, char** argv) // the data collection is written, a matset will be created. shaper->getDC()->AssociateMaterialSet("vol_frac", "material"); - // Set specific parameters for a SamplingShaper, if appropriate - if(auto* samplingShaper = dynamic_cast(shaper)) - { - samplingShaper->setSamplingType(params.vfSampling); - samplingShaper->setQuadratureOrder(params.quadratureOrder); - samplingShaper->setVolumeFractionOrder(params.outputOrder); - - // register a point projector - if(shapingDC.GetMesh()->Dimension() == 3 && shapeDim == klee::Dimensions::Two) - { - samplingShaper->setPointProjector([](primal::Point pt) { - const double& x = pt[0]; - const double& y = pt[1]; - const double& z = pt[2]; - return primal::Point {z, sqrt(x * x + y * y)}; - }); - } - } - // Set specific parameters here for IntersectionShaper if(auto* intersectionShaper = dynamic_cast(shaper)) { @@ -1187,42 +1106,6 @@ int main(int argc, char** argv) } } - //--------------------------------------------------------------------------- - // Project initial volume fractions, if applicable - //--------------------------------------------------------------------------- - if(auto* samplingShaper = dynamic_cast(shaper)) - { - AXOM_ANNOTATE_SCOPE("import initial volume fractions"); - std::map initial_grid_functions; - - // Generate a background material (w/ volume fractions set to 1) if user provided a name - if(!params.backgroundMaterial.empty()) - { - auto material = params.backgroundMaterial; - auto name = axom::fmt::format("vol_frac_{}", material); - - const int order = params.outputOrder; - const int dim = shapingMesh->Dimension(); - const auto basis = mfem::BasisType::Positive; - - auto* coll = new mfem::L2_FECollection(order, dim, basis); - auto* fes = new mfem::FiniteElementSpace(shapingDC.GetMesh(), coll); - const int sz = fes->GetVSize(); - - auto* view = shapingDC.AllocNamedBuffer(name, sz); - auto* volFrac = new mfem::GridFunction(fes, view->getArray()); - volFrac->MakeOwner(coll); - - (*volFrac) = 1.; - - shapingDC.RegisterField(name, volFrac); - - initial_grid_functions[material] = shapingDC.GetField(name); - } - - // Project provided volume fraction grid functions as quadrature point data - samplingShaper->importInitialVolumeFractions(initial_grid_functions); - } AXOM_ANNOTATE_END("setup shaping problem"); AXOM_ANNOTATE_END("init"); @@ -1408,14 +1291,6 @@ int main(int argc, char** argv) //--------------------------------------------------------------------------- // Save meshes and fields //--------------------------------------------------------------------------- - if(params.isVerbose()) - { - if(auto* samplingShaper = dynamic_cast(shaper)) - { - SLIC_INFO(axom::fmt::format("{:-^80}", "")); - samplingShaper->printRegisteredFieldNames(" -- after shaping"); - } - } #ifdef MFEM_USE_MPI if(!params.outputFile.empty()) From 4b99328d7d488a2f5cd0586df8e7bf982177df81 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 14 Oct 2024 13:09:55 -0700 Subject: [PATCH 36/43] Re-enable blueprint check on input mesh. --- src/axom/klee/Geometry.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/axom/klee/Geometry.cpp b/src/axom/klee/Geometry.cpp index 79333f2eed..9f9171530d 100644 --- a/src/axom/klee/Geometry.cpp +++ b/src/axom/klee/Geometry.cpp @@ -44,11 +44,9 @@ Geometry::Geometry(const TransformableGeometryProperties& startProperties, , m_operator(std::move(operator_)) { #ifdef AXOM_DEBUG - #if 0 SLIC_ASSERT_MSG(isBlueprintTetMesh(m_meshGroup), "Mesh provided to Geometry is not a valid blueprint " "unstructured tetrahedral mesh."); - #endif #endif } @@ -167,7 +165,6 @@ const std::string& Geometry::getBlueprintTopology() const return m_topology; } -#if 0 bool Geometry::isBlueprintTetMesh(const axom::sidre::Group* meshGroup) const { conduit::Node bpMesh; @@ -209,7 +206,6 @@ bool Geometry::isBlueprintTetMesh(const axom::sidre::Group* meshGroup) const return true; } -#endif } // namespace klee } // namespace axom From 9af7adfc970a7fd7394bc870c4525ea123eed21b Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 21 Oct 2024 12:16:23 -0700 Subject: [PATCH 37/43] Minor changes from code review comments. --- src/axom/primal/operators/split.hpp | 4 ++-- src/axom/quest/DiscreteShape.hpp | 4 ++-- src/axom/quest/Shaper.hpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/axom/primal/operators/split.hpp b/src/axom/primal/operators/split.hpp index 37200ea1f4..2425df78a7 100644 --- a/src/axom/primal/operators/split.hpp +++ b/src/axom/primal/operators/split.hpp @@ -84,8 +84,8 @@ void split(const Octahedron& oct, * \tparam Tp the coordinate type, such double or float * \tparam NDIMS the number of spatial dimensions (must be 3). * \param [in] oct The Octahedron to split - * \param [out] out C array of 8 Tetrahedron objects; the fragments of - * oct are appended to out. + * \param [out] outPtr C-style array of 8 Tetrahedron objects; + * the fragments of oct are appended to out. * * \pre NDIMS == 3 * diff --git a/src/axom/quest/DiscreteShape.hpp b/src/axom/quest/DiscreteShape.hpp index e7a03614ab..d64ae81f39 100644 --- a/src/axom/quest/DiscreteShape.hpp +++ b/src/axom/quest/DiscreteShape.hpp @@ -3,8 +3,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_KLEE_DISCRETE_SHAPE_HPP -#define AXOM_KLEE_DISCRETE_SHAPE_HPP +#ifndef AXOM_QUEST_DISCRETE_SHAPE_HPP +#define AXOM_QUEST_DISCRETE_SHAPE_HPP #include #include diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index da8eed8341..cf8ee7d27f 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -109,7 +109,7 @@ class Shaper protected: /*! - * \brief Loads the shape from file into m_surfaceMesh and, if its a C2D + * \brief Loads the shape from file into m_surfaceMesh and, if its a C2C * contour, computes a revolvedVolume for the shape. * \param shape The shape. * \param percentError A percent error to use when refining the shape. If it From fa541037c427f254cec1a13967a4116c6c8ec034 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Wed, 6 Nov 2024 04:43:35 -0800 Subject: [PATCH 38/43] Remove redundant line left in by merge. --- src/axom/sidre/core/View.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/axom/sidre/core/View.cpp b/src/axom/sidre/core/View.cpp index b670c99b76..07baca1642 100644 --- a/src/axom/sidre/core/View.cpp +++ b/src/axom/sidre/core/View.cpp @@ -1164,7 +1164,6 @@ void View::deepCopyView(View* copy, int allocID) const { copy->describe(getTypeID(), getNumDimensions(), m_shape.data()); } - copy->describe(getTypeID(), getNumDimensions(), m_shape.data()); } switch(m_state) From ac42dc5321b48c29503cd52d2f69284a4dd1504d Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 25 Nov 2024 15:21:19 -0800 Subject: [PATCH 39/43] Add test option "all" to test multiple shapes and let more shape respond to direction. Multiple shape tests checks the mechanism for saving some mesh-dependent but not shape-dependent data between calls to the shaper. Letting some shapes change direction makes the test a bit less trivial. --- src/axom/quest/examples/CMakeLists.txt | 5 +- .../quest/examples/quest_shape_in_memory.cpp | 273 +++++++++++------- 2 files changed, 171 insertions(+), 107 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index e8032d21aa..034c3260c8 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -288,7 +288,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI blt_list_append(TO _policies ELEMENTS "hip" IF AXOM_ENABLE_HIP) endif() - set(_testshapes "tetmesh" "tet" "hex" "sphere" "cyl" "cone" "vor" "plane") + set(_testshapes "tetmesh" "tet" "hex" "sphere" "cyl" "cone" "vor" "all" "plane") foreach(_policy ${_policies}) foreach(_testshape ${_testshapes}) @@ -300,7 +300,8 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI --testShape ${_testshape} --refinements 2 --scale 0.75 0.75 0.75 - inline_mesh --min -3 -3 -3 --max 3 3 3 --res 20 20 20 -d 3 + --dir 0.2 0.4 0.8 + inline_mesh --min -2 -2 -2 --max 2 2 2 --res 30 30 30 -d 3 NUM_MPI_TASKS ${_nranks}) endforeach() endforeach() diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index e054da24a8..cf8935063c 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -58,16 +58,17 @@ namespace sidre = axom::sidre; using RuntimePolicy = axom::runtime_policy::Policy; /// Struct to parse and store the input parameters +// Some parameters are used to override defaults. struct Input { public: std::string outputFile; - std::vector center {0.0, 0.0, 0.0}; - double radius {1.0}; - double radius2 {0.3}; - double length {2.0}; - std::vector direction {0.0, 0.0, 1.0}; + std::vector center; + double radius {-1.0}; + double radius2 {-0.3}; + double length {-2.0}; + std::vector direction; // Shape transformation parameters std::vector scaleFactors; @@ -76,9 +77,9 @@ struct Input std::string meshFormat = "mfem"; // Inline mesh parameters - std::vector boxMins; - std::vector boxMaxs; - std::vector boxResolution; + std::vector boxMins {-2, -2, -2}; + std::vector boxMaxs {2, 2, 2}; + std::vector boxResolution {20, 20, 20}; int boxDim {-1}; // The shape to run. @@ -91,10 +92,10 @@ struct Input "vor", "tet", "hex", - "plane"}; + "plane", + "all"}; RuntimePolicy policy {RuntimePolicy::seq}; - int quadratureOrder {5}; int outputOrder {2}; int refinementLevel {7}; double weldThresh {1e-9}; @@ -239,7 +240,7 @@ struct Input ->check(axom::CLI::PositiveNumber); app.add_option("--dir", direction) - ->description("Direction of axis of cone/cyl/VOR (x,y[,z]) shape") + ->description("Direction of axis of rotation (cone/cyl/VOR (x,y[,z])), or rotated x-axis (hex, tet, tetmesh, and sphere), or positive normal direction (plane).") ->expected(2, 3); app.add_option("--radius2", radius2) @@ -323,6 +324,57 @@ struct Input }; // struct Input Input params; + +// Start property for all 3D shapes. +axom::klee::TransformableGeometryProperties startProp { + axom::klee::Dimensions::Three, + axom::klee::LengthUnit::unspecified}; + +// Add scale operator if specified by input parameters. +void addScaleOperator(axom::klee::CompositeOperator& compositeOp) +{ + SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); + if(!params.scaleFactors.empty()) + { + std::shared_ptr scaleOp = + std::make_shared(params.scaleFactors[0], + params.scaleFactors[1], + params.scaleFactors[2], + startProp); + compositeOp.addOperator(scaleOp); + } +} + +// Add translate operator. +void addTranslateOperator(axom::klee::CompositeOperator& compositeOp, + double shiftx, + double shifty, + double shiftz) +{ + primal::Vector3D shift({shiftx, shifty, shiftz}); + auto translateOp = std::make_shared(shift, startProp); + compositeOp.addOperator(translateOp); +} + +// Add operator to rotate x-axis to params.direction, if it is given. +void addRotateOperator(axom::klee::CompositeOperator& compositeOp) +{ + if(!params.direction.empty()) + { + static const primal::Point3D center {0.0, 0.0, 0.0}; + static const primal::Vector3D x {1.0, 0.0, 0.0}; + primal::Vector3D rotateTo(params.direction.data()); + // Note that the rotation matrix is not unique. + primal::Vector3D a = rotateTo.unitVector(); + primal::Vector3D u; // Rotation vector, the cross product of x and a. + axom::numerics::cross_product(x.data(), a.data(), u.data()); + double angle = asin(u.norm()) * 180 / M_PI; + + auto rotateOp = std::make_shared(angle, center, u, startProp); + compositeOp.addOperator(rotateOp); + } +} + /** * \brief Print some info about the mesh * @@ -483,24 +535,21 @@ axom::klee::ShapeSet create2DShapeSet(sidre::DataStore& ds) axom::klee::Shape createShape_Sphere() { - axom::primal::Sphere sphere {params.center.data(), params.radius}; + Point3D center = params.center.empty() ? Point3D{0, 0, 0} : Point3D{params.center.data()}; + double radius = params.radius < 0 ? 0.8 : params.radius; + axom::primal::Sphere sphere {center, radius}; axom::klee::TransformableGeometryProperties prop { axom::klee::Dimensions::Three, axom::klee::LengthUnit::unspecified}; - SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); - std::shared_ptr scaleOp; - if(!params.scaleFactors.empty()) - { - scaleOp = std::make_shared(params.scaleFactors[0], - params.scaleFactors[1], - params.scaleFactors[2], - prop); - } + auto compositeOp = std::make_shared(startProp); + addScaleOperator(*compositeOp); + addRotateOperator(*compositeOp); + addTranslateOperator(*compositeOp, 1, 1, 1); const axom::IndexType levelOfRefinement = params.refinementLevel; - axom::klee::Geometry sphereGeometry(prop, sphere, levelOfRefinement, scaleOp); + axom::klee::Geometry sphereGeometry(prop, sphere, levelOfRefinement, compositeOp); axom::klee::Shape sphereShape("sphere", "AU", {}, {}, sphereGeometry); return sphereShape; @@ -508,7 +557,7 @@ axom::klee::Shape createShape_Sphere() axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) { - // Shape a single tetrahedron. + // Shape a tetrahedal mesh. sidre::Group* meshGroup = ds.getRoot()->createGroup("tetMesh"); AXOM_UNUSED_VAR(meshGroup); // variable is only referenced in debug configs const std::string topo = "mesh"; @@ -520,13 +569,13 @@ axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) topo, coordset); - double lll = 4.0; + double lll = params.length < 0 ? 0.7 : params.length; // Insert tet at origin. - tetMesh.appendNode(0.0, 0.0, 0.0); - tetMesh.appendNode(lll, 0.0, 0.0); - tetMesh.appendNode(0.0, lll, 0.0); - tetMesh.appendNode(0.0, 0.0, lll); + tetMesh.appendNode(-lll, -lll, -lll); + tetMesh.appendNode(+lll, -lll, -lll); + tetMesh.appendNode(-lll, +lll, -lll); + tetMesh.appendNode(-lll, -lll, +lll); tetMesh.appendNode(lll, lll, 0.0); axom::IndexType conn0[4] = {0, 1, 2, 3}; tetMesh.appendCell(conn0); @@ -539,20 +588,15 @@ axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) axom::klee::Dimensions::Three, axom::klee::LengthUnit::unspecified}; - SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); - std::shared_ptr scaleOp; - if(!params.scaleFactors.empty()) - { - scaleOp = std::make_shared(params.scaleFactors[0], - params.scaleFactors[1], - params.scaleFactors[2], - prop); - } + auto compositeOp = std::make_shared(startProp); + addScaleOperator(*compositeOp); + addRotateOperator(*compositeOp); + addTranslateOperator(*compositeOp, -1 ,1, 1); axom::klee::Geometry tetMeshGeometry(prop, tetMesh.getSidreGroup(), topo, - {scaleOp}); + compositeOp); axom::klee::Shape tetShape("tetmesh", "TETMESH", {}, {}, tetMeshGeometry); return tetShape; @@ -560,21 +604,14 @@ axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) axom::klee::Geometry createGeometry_Vor(axom::primal::Point& vorBase, axom::primal::Vector& vorDirection, - axom::Array& discreteFunction) + axom::Array& discreteFunction, + std::shared_ptr& compositeOp) { axom::klee::TransformableGeometryProperties prop { axom::klee::Dimensions::Three, axom::klee::LengthUnit::unspecified}; SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); - std::shared_ptr scaleOp; - if(!params.scaleFactors.empty()) - { - scaleOp = std::make_shared(params.scaleFactors[0], - params.scaleFactors[1], - params.scaleFactors[2], - prop); - } const axom::IndexType levelOfRefinement = params.refinementLevel; axom::klee::Geometry vorGeometry(prop, @@ -582,24 +619,31 @@ axom::klee::Geometry createGeometry_Vor(axom::primal::Point& vorBase, vorBase, vorDirection, levelOfRefinement, - scaleOp); + compositeOp); return vorGeometry; } axom::klee::Shape createShape_Vor() { - Point3D vorBase {params.center.data()}; - axom::primal::Vector vorDirection {params.direction.data()}; + Point3D vorBase = params.center.empty() ? Point3D{0.0, 0.0, 0.0} : Point3D{params.center.data()}; + axom::primal::Vector vorDirection = params.direction.empty() ? primal::Vector3D{0.1, 0.2, 0.4} : primal::Vector3D{params.direction.data()}; axom::Array discreteFunction({3, 2}, axom::ArrayStrideOrder::ROW); - discreteFunction[0][0] = 0.0; - discreteFunction[0][1] = 1.0; - discreteFunction[1][0] = 0.5 * params.length; - discreteFunction[1][1] = 0.8; - discreteFunction[2][0] = params.length; - discreteFunction[2][1] = 1.0; + double zLen = params.length < 0 ? 0.7 : params.length; + double zShift = -zLen/2; + double r = params.radius < 0 ? 0.75 : params.radius; + discreteFunction[0][0] = zShift + 0.0; + discreteFunction[0][1] = 1.0 * r; + discreteFunction[1][0] = zShift + 0.5 * zLen; + discreteFunction[1][1] = 0.8 * r; + discreteFunction[2][0] = zShift + zLen; + discreteFunction[2][1] = 1.0 * r; + + auto compositeOp = std::make_shared(startProp); + addScaleOperator(*compositeOp); + addTranslateOperator(*compositeOp, -1, -1, 1); axom::klee::Geometry vorGeometry = - createGeometry_Vor(vorBase, vorDirection, discreteFunction); + createGeometry_Vor(vorBase, vorDirection, discreteFunction, compositeOp); axom::klee::Shape vorShape("vor", "VOR", {}, {}, vorGeometry); @@ -608,18 +652,22 @@ axom::klee::Shape createShape_Vor() axom::klee::Shape createShape_Cylinder() { - Point3D vorBase {params.center.data()}; - axom::primal::Vector vorDirection {params.direction.data()}; + Point3D vorBase = params.center.empty() ? Point3D{0.0, 0.0, 0.0} : Point3D{params.center.data()}; + axom::primal::Vector vorDirection = params.direction.empty() ? primal::Vector3D{0.1, 0.2, 0.4} : primal::Vector3D{params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); - double radius = params.radius; - double height = params.length; - discreteFunction[0][0] = 0.0; + double radius = params.radius < 0 ? 0.5 : params.radius; + double height = params.length < 0 ? 0.8 : params.length; + discreteFunction[0][0] = -height/2; discreteFunction[0][1] = radius; - discreteFunction[1][0] = height; + discreteFunction[1][0] = height/2; discreteFunction[1][1] = radius; + auto compositeOp = std::make_shared(startProp); + addScaleOperator(*compositeOp); + addTranslateOperator(*compositeOp, 1, -1, 1); + axom::klee::Geometry vorGeometry = - createGeometry_Vor(vorBase, vorDirection, discreteFunction); + createGeometry_Vor(vorBase, vorDirection, discreteFunction, compositeOp); axom::klee::Shape vorShape("cyl", "CYL", {}, {}, vorGeometry); @@ -628,19 +676,23 @@ axom::klee::Shape createShape_Cylinder() axom::klee::Shape createShape_Cone() { - Point3D vorBase {params.center.data()}; - axom::primal::Vector vorDirection {params.direction.data()}; + Point3D vorBase = params.center.empty() ? Point3D{0.0, 0.0, 0.0} : Point3D{params.center.data()}; + axom::primal::Vector vorDirection = params.direction.empty() ? primal::Vector3D{0.1, 0.2, 0.4} : primal::Vector3D{params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); - double baseRadius = params.radius; - double topRadius = params.radius2; - double height = params.length; - discreteFunction[0][0] = 0.0; + double baseRadius = params.radius < 0 ? 0.5 : params.radius; + double topRadius = params.radius2 < 0 ? 0.1 : params.radius2; + double height = params.length < 0 ? 0.8 : params.length; + discreteFunction[0][0] = -height/2; discreteFunction[0][1] = baseRadius; - discreteFunction[1][0] = height; + discreteFunction[1][0] = height/2; discreteFunction[1][1] = topRadius; + auto compositeOp = std::make_shared(startProp); + addScaleOperator(*compositeOp); + addTranslateOperator(*compositeOp, 1, 1, -1); + axom::klee::Geometry vorGeometry = - createGeometry_Vor(vorBase, vorDirection, discreteFunction); + createGeometry_Vor(vorBase, vorDirection, discreteFunction, compositeOp); axom::klee::Shape vorShape("cone", "CONE", {}, {}, vorGeometry); @@ -654,23 +706,21 @@ axom::klee::Shape createShape_Tet() axom::klee::LengthUnit::unspecified}; SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); - std::shared_ptr scaleOp; - if(!params.scaleFactors.empty()) - { - scaleOp = std::make_shared(params.scaleFactors[0], - params.scaleFactors[1], - params.scaleFactors[2], - prop); - } - const double len = params.length; - const Point3D a {0.0, 0.0, 0.0}; - const Point3D b {len, 0.0, 0.0}; - const Point3D c {len, 1.0, 0.0}; - const Point3D d {0.0, 1.0, 0.0}; + // Tetrahedron at origin. + const double len = params.length < 0 ? 0.8 : params.length; + const Point3D a {-len, -len, -len}; + const Point3D b {+len, -len, -len}; + const Point3D c {+len, +len, -len}; + const Point3D d {-len, +len, +len}; const primal::Tetrahedron tet {a, b, c, d}; - axom::klee::Geometry tetGeometry(prop, tet, scaleOp); + auto compositeOp = std::make_shared(startProp); + addScaleOperator(*compositeOp); + addRotateOperator(*compositeOp); + addTranslateOperator(*compositeOp, -1, 1, -1); + + axom::klee::Geometry tetGeometry(prop, tet, compositeOp); axom::klee::Shape tetShape("tet", "TET", {}, {}, tetGeometry); return tetShape; @@ -683,27 +733,26 @@ axom::klee::Shape createShape_Hex() axom::klee::LengthUnit::unspecified}; SLIC_ASSERT(params.scaleFactors.empty() || params.scaleFactors.size() == 3); - std::shared_ptr scaleOp; - if(!params.scaleFactors.empty()) - { - scaleOp = std::make_shared(params.scaleFactors[0], - params.scaleFactors[1], - params.scaleFactors[2], - prop); - } - const double len = params.length; - const Point3D p {0.0, 0.0, 0.0}; - const Point3D q {len, 0.0, 0.0}; - const Point3D r {len, 1.0, 0.0}; - const Point3D s {0.0, 1.0, 0.0}; - const Point3D t {0.0, 0.0, 1.0}; - const Point3D u {len, 0.0, 1.0}; - const Point3D v {len, 1.0, 1.0}; - const Point3D w {0.0, 1.0, 1.0}; + const double md = params.length < 0 ? 0.6 : params.length; + const double lg = 1.2 * md; + const double sm = 0.8 * md; + const Point3D p {-lg, -md, -sm}; + const Point3D q {+lg, -md, -sm}; + const Point3D r {+lg, +md, -sm}; + const Point3D s {-lg, +md, -sm}; + const Point3D t {-lg, -md, +sm}; + const Point3D u {+lg, -md, +sm}; + const Point3D v {+lg, +md, +sm}; + const Point3D w {-lg, +md, +sm}; const primal::Hexahedron hex {p, q, r, s, t, u, v, w}; - axom::klee::Geometry hexGeometry(prop, hex, scaleOp); + auto compositeOp = std::make_shared(startProp); + addScaleOperator(*compositeOp); + addRotateOperator(*compositeOp); + addTranslateOperator(*compositeOp, -1, -1, -1); + + axom::klee::Geometry hexGeometry(prop, hex, compositeOp); axom::klee::Shape hexShape("hex", "HEX", {}, {}, hexGeometry); return hexShape; @@ -994,6 +1043,19 @@ int main(int argc, char** argv) { shapeSet = createShapeSet(createShape_Plane()); } + else if(params.testShape == "all") + { + std::vector shapesVec; + shapesVec.push_back(createShape_TetMesh(ds)); + shapesVec.push_back(createShape_Tet()); + shapesVec.push_back(createShape_Hex()); + shapesVec.push_back(createShape_Sphere()); + shapesVec.push_back(createShape_Vor()); + shapesVec.push_back(createShape_Cylinder()); + shapesVec.push_back(createShape_Cone()); + shapeSet.setShapes(shapesVec); + shapeSet.setDimensions(axom::klee::Dimensions::Three); + } break; } @@ -1248,7 +1310,8 @@ int main(int argc, char** argv) //--------------------------------------------------------------------------- // Correctness test: shape volume in shapingMesh should match volume of the - // shape mesh for closes shape. + // shape mesh for closes shape. As long as the shapes don't overlap, this + // should be a good correctness check. //--------------------------------------------------------------------------- auto* meshVerificationGroup = ds.getRoot()->createGroup("meshVerification"); for(const auto& shape : shapeSet.getShapes()) From 72e876957acc1f5668d8300986bd2cf89c6a0277 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 25 Nov 2024 16:37:33 -0800 Subject: [PATCH 40/43] Remove need to enter explicit mesh dimension. --- src/axom/quest/examples/CMakeLists.txt | 2 +- .../quest/examples/quest_shape_in_memory.cpp | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 034c3260c8..9835592167 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -301,7 +301,7 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI --refinements 2 --scale 0.75 0.75 0.75 --dir 0.2 0.4 0.8 - inline_mesh --min -2 -2 -2 --max 2 2 2 --res 30 30 30 -d 3 + inline_mesh --min -2 -2 -2 --max 2 2 2 --res 30 30 30 NUM_MPI_TASKS ${_nranks}) endforeach() endforeach() diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index cf8935063c..bbed8259f1 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -80,7 +80,13 @@ struct Input std::vector boxMins {-2, -2, -2}; std::vector boxMaxs {2, 2, 2}; std::vector boxResolution {20, 20, 20}; - int boxDim {-1}; + int getBoxDim() const + { + auto d = boxResolution.size(); + SLIC_ASSERT(boxMins.size() == d); + SLIC_ASSERT(boxMaxs.size() == d); + return int(d); + } // The shape to run. std::string testShape {"tetmesh"}; @@ -125,7 +131,7 @@ struct Input { mfem::Mesh* mesh = nullptr; - switch(boxDim) + switch(getBoxDim()) { case 2: { @@ -272,11 +278,6 @@ struct Input ->description("Resolution of the box mesh (i,j[,k])") ->expected(2, 3) ->required(); - - inline_mesh_subcommand->add_option("-d,--dimension", boxDim) - ->description("Dimension of the box mesh") - ->check(axom::CLI::PositiveNumber) - ->required(); } app.add_option("--background-material", backgroundMaterial) @@ -779,7 +780,7 @@ axom::klee::Shape createShape_Plane() Point3D center {0.5 * (primal::NumericArray(params.boxMins.data()) + primal::NumericArray(params.boxMaxs.data()))}; - primal::Vector normal {1.0, 0.0, 0.0}; + primal::Vector normal = params.direction.empty() ? primal::Vector3D{1.0, 0.0, 0.0} : primal::Vector3D{params.direction.data()}.unitVector(); const primal::Plane plane {normal, center, true}; axom::klee::Geometry planeGeometry(prop, plane, scaleOp); @@ -1005,7 +1006,8 @@ int main(int argc, char** argv) // Create simple ShapeSet for the example. //--------------------------------------------------------------------------- axom::klee::ShapeSet shapeSet; - switch(params.boxDim) + SLIC_ERROR_IF(params.getBoxDim() != 3, "This example is only in 3D."); + switch(params.getBoxDim()) { case 2: shapeSet = create2DShapeSet(ds); From 7cb17c66ced3d963e2701454aceb8c4fe947b133 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 25 Nov 2024 16:37:59 -0800 Subject: [PATCH 41/43] Add TODO note about problems with using huge tets to represent plane. --- src/axom/quest/DiscreteShape.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/DiscreteShape.cpp b/src/axom/quest/DiscreteShape.cpp index 39070f48ce..cc45a2915d 100644 --- a/src/axom/quest/DiscreteShape.cpp +++ b/src/axom/quest/DiscreteShape.cpp @@ -377,8 +377,22 @@ void DiscreteShape::createPlaneRepresentation() const auto& plane = geometry.getPlane(); // Generate a big bounding hex on the positive side of the plane. + /* + TODO: Use a specialized plane representation. Plane is very + simple, but representing it as huge tets has 2 problems. + 1. The shaper runs slow because the huge tets contains huge + numbers of cells, disbling attempts at divide-and-conquer + with the BVH. + + 2. When the tets are orders of magnitude larger then the + computational mesh, I've seen weird overlap computation + errors that affect certain resolutions and don't follow + any trends. I think it's due to round-off, but I've not + verified. + */ axom::primal::Hexahedron boundingHex; - const double len = 1e6; // Big enough to contain anticipated mesh. + const double len = 1e2; // Big enough to contain anticipated mesh, + // but too big invites weird round-off errors. // We should compute based on the mesh. // clang-format off boundingHex[0] = Point3D{0.0, -len, -len}; From 6a497aaf564ad242a9d7d1df15a118c50865a529 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 25 Nov 2024 16:58:26 -0800 Subject: [PATCH 42/43] Autoformat. --- .../quest/examples/quest_shape_in_memory.cpp | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index bbed8259f1..4891b8f507 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -246,7 +246,10 @@ struct Input ->check(axom::CLI::PositiveNumber); app.add_option("--dir", direction) - ->description("Direction of axis of rotation (cone/cyl/VOR (x,y[,z])), or rotated x-axis (hex, tet, tetmesh, and sphere), or positive normal direction (plane).") + ->description( + "Direction of axis of rotation (cone/cyl/VOR (x,y[,z])), or rotated " + "x-axis (hex, tet, tetmesh, and sphere), or positive normal direction " + "(plane).") ->expected(2, 3); app.add_option("--radius2", radius2) @@ -325,7 +328,6 @@ struct Input }; // struct Input Input params; - // Start property for all 3D shapes. axom::klee::TransformableGeometryProperties startProp { axom::klee::Dimensions::Three, @@ -371,7 +373,8 @@ void addRotateOperator(axom::klee::CompositeOperator& compositeOp) axom::numerics::cross_product(x.data(), a.data(), u.data()); double angle = asin(u.norm()) * 180 / M_PI; - auto rotateOp = std::make_shared(angle, center, u, startProp); + auto rotateOp = + std::make_shared(angle, center, u, startProp); compositeOp.addOperator(rotateOp); } } @@ -536,7 +539,8 @@ axom::klee::ShapeSet create2DShapeSet(sidre::DataStore& ds) axom::klee::Shape createShape_Sphere() { - Point3D center = params.center.empty() ? Point3D{0, 0, 0} : Point3D{params.center.data()}; + Point3D center = + params.center.empty() ? Point3D {0, 0, 0} : Point3D {params.center.data()}; double radius = params.radius < 0 ? 0.8 : params.radius; axom::primal::Sphere sphere {center, radius}; @@ -592,7 +596,7 @@ axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) auto compositeOp = std::make_shared(startProp); addScaleOperator(*compositeOp); addRotateOperator(*compositeOp); - addTranslateOperator(*compositeOp, -1 ,1, 1); + addTranslateOperator(*compositeOp, -1, 1, 1); axom::klee::Geometry tetMeshGeometry(prop, tetMesh.getSidreGroup(), @@ -603,10 +607,11 @@ axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) return tetShape; } -axom::klee::Geometry createGeometry_Vor(axom::primal::Point& vorBase, - axom::primal::Vector& vorDirection, - axom::Array& discreteFunction, - std::shared_ptr& compositeOp) +axom::klee::Geometry createGeometry_Vor( + axom::primal::Point& vorBase, + axom::primal::Vector& vorDirection, + axom::Array& discreteFunction, + std::shared_ptr& compositeOp) { axom::klee::TransformableGeometryProperties prop { axom::klee::Dimensions::Three, @@ -626,11 +631,14 @@ axom::klee::Geometry createGeometry_Vor(axom::primal::Point& vorBase, axom::klee::Shape createShape_Vor() { - Point3D vorBase = params.center.empty() ? Point3D{0.0, 0.0, 0.0} : Point3D{params.center.data()}; - axom::primal::Vector vorDirection = params.direction.empty() ? primal::Vector3D{0.1, 0.2, 0.4} : primal::Vector3D{params.direction.data()}; + Point3D vorBase = params.center.empty() ? Point3D {0.0, 0.0, 0.0} + : Point3D {params.center.data()}; + axom::primal::Vector vorDirection = params.direction.empty() + ? primal::Vector3D {0.1, 0.2, 0.4} + : primal::Vector3D {params.direction.data()}; axom::Array discreteFunction({3, 2}, axom::ArrayStrideOrder::ROW); double zLen = params.length < 0 ? 0.7 : params.length; - double zShift = -zLen/2; + double zShift = -zLen / 2; double r = params.radius < 0 ? 0.75 : params.radius; discreteFunction[0][0] = zShift + 0.0; discreteFunction[0][1] = 1.0 * r; @@ -653,14 +661,17 @@ axom::klee::Shape createShape_Vor() axom::klee::Shape createShape_Cylinder() { - Point3D vorBase = params.center.empty() ? Point3D{0.0, 0.0, 0.0} : Point3D{params.center.data()}; - axom::primal::Vector vorDirection = params.direction.empty() ? primal::Vector3D{0.1, 0.2, 0.4} : primal::Vector3D{params.direction.data()}; + Point3D vorBase = params.center.empty() ? Point3D {0.0, 0.0, 0.0} + : Point3D {params.center.data()}; + axom::primal::Vector vorDirection = params.direction.empty() + ? primal::Vector3D {0.1, 0.2, 0.4} + : primal::Vector3D {params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); double radius = params.radius < 0 ? 0.5 : params.radius; double height = params.length < 0 ? 0.8 : params.length; - discreteFunction[0][0] = -height/2; + discreteFunction[0][0] = -height / 2; discreteFunction[0][1] = radius; - discreteFunction[1][0] = height/2; + discreteFunction[1][0] = height / 2; discreteFunction[1][1] = radius; auto compositeOp = std::make_shared(startProp); @@ -677,15 +688,18 @@ axom::klee::Shape createShape_Cylinder() axom::klee::Shape createShape_Cone() { - Point3D vorBase = params.center.empty() ? Point3D{0.0, 0.0, 0.0} : Point3D{params.center.data()}; - axom::primal::Vector vorDirection = params.direction.empty() ? primal::Vector3D{0.1, 0.2, 0.4} : primal::Vector3D{params.direction.data()}; + Point3D vorBase = params.center.empty() ? Point3D {0.0, 0.0, 0.0} + : Point3D {params.center.data()}; + axom::primal::Vector vorDirection = params.direction.empty() + ? primal::Vector3D {0.1, 0.2, 0.4} + : primal::Vector3D {params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); double baseRadius = params.radius < 0 ? 0.5 : params.radius; double topRadius = params.radius2 < 0 ? 0.1 : params.radius2; double height = params.length < 0 ? 0.8 : params.length; - discreteFunction[0][0] = -height/2; + discreteFunction[0][0] = -height / 2; discreteFunction[0][1] = baseRadius; - discreteFunction[1][0] = height/2; + discreteFunction[1][0] = height / 2; discreteFunction[1][1] = topRadius; auto compositeOp = std::make_shared(startProp); @@ -780,7 +794,9 @@ axom::klee::Shape createShape_Plane() Point3D center {0.5 * (primal::NumericArray(params.boxMins.data()) + primal::NumericArray(params.boxMaxs.data()))}; - primal::Vector normal = params.direction.empty() ? primal::Vector3D{1.0, 0.0, 0.0} : primal::Vector3D{params.direction.data()}.unitVector(); + primal::Vector normal = params.direction.empty() + ? primal::Vector3D {1.0, 0.0, 0.0} + : primal::Vector3D {params.direction.data()}.unitVector(); const primal::Plane plane {normal, center, true}; axom::klee::Geometry planeGeometry(prop, plane, scaleOp); From 1b873703bad59c9f0c9a8edef60bf81b09308ce1 Mon Sep 17 00:00:00 2001 From: "Brian T.N. Gunney" Date: Mon, 25 Nov 2024 17:31:16 -0800 Subject: [PATCH 43/43] Tweak shapes to make them about the same size. Want to avoid having small ones that aren't well resolved with a coarse mesh. --- .../quest/examples/quest_shape_in_memory.cpp | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/axom/quest/examples/quest_shape_in_memory.cpp b/src/axom/quest/examples/quest_shape_in_memory.cpp index 4891b8f507..19ea3d7435 100644 --- a/src/axom/quest/examples/quest_shape_in_memory.cpp +++ b/src/axom/quest/examples/quest_shape_in_memory.cpp @@ -541,7 +541,7 @@ axom::klee::Shape createShape_Sphere() { Point3D center = params.center.empty() ? Point3D {0, 0, 0} : Point3D {params.center.data()}; - double radius = params.radius < 0 ? 0.8 : params.radius; + double radius = params.radius < 0 ? 0.6 : params.radius; axom::primal::Sphere sphere {center, radius}; axom::klee::TransformableGeometryProperties prop { @@ -576,15 +576,18 @@ axom::klee::Shape createShape_TetMesh(sidre::DataStore& ds) double lll = params.length < 0 ? 0.7 : params.length; - // Insert tet at origin. + // Insert tets around origin. tetMesh.appendNode(-lll, -lll, -lll); tetMesh.appendNode(+lll, -lll, -lll); tetMesh.appendNode(-lll, +lll, -lll); tetMesh.appendNode(-lll, -lll, +lll); - tetMesh.appendNode(lll, lll, 0.0); + tetMesh.appendNode(+lll, +lll, +lll); + tetMesh.appendNode(-lll, +lll, +lll); + tetMesh.appendNode(+lll, +lll, -lll); + tetMesh.appendNode(+lll, -lll, +lll); axom::IndexType conn0[4] = {0, 1, 2, 3}; tetMesh.appendCell(conn0); - axom::IndexType conn1[4] = {4, 1, 2, 3}; + axom::IndexType conn1[4] = {4, 5, 6, 7}; tetMesh.appendCell(conn1); SLIC_ASSERT(axom::mint::blueprint::isValidRootGroup(meshGroup)); @@ -636,16 +639,25 @@ axom::klee::Shape createShape_Vor() axom::primal::Vector vorDirection = params.direction.empty() ? primal::Vector3D {0.1, 0.2, 0.4} : primal::Vector3D {params.direction.data()}; - axom::Array discreteFunction({3, 2}, axom::ArrayStrideOrder::ROW); - double zLen = params.length < 0 ? 0.7 : params.length; + const int numIntervals = 5; + axom::Array discreteFunction({numIntervals + 1, 2}, + axom::ArrayStrideOrder::ROW); + double zLen = params.length < 0 ? 1.6 : params.length; double zShift = -zLen / 2; - double r = params.radius < 0 ? 0.75 : params.radius; - discreteFunction[0][0] = zShift + 0.0; - discreteFunction[0][1] = 1.0 * r; - discreteFunction[1][0] = zShift + 0.5 * zLen; - discreteFunction[1][1] = 0.8 * r; - discreteFunction[2][0] = zShift + zLen; - discreteFunction[2][1] = 1.0 * r; + double maxR = params.radius < 0 ? 0.75 : params.radius; + double dz = zLen / numIntervals; + discreteFunction[0][0] = 0 * dz + zShift; + discreteFunction[0][1] = 0.0 * maxR; + discreteFunction[1][0] = 1 * dz + zShift; + discreteFunction[1][1] = 0.8 * maxR; + discreteFunction[2][0] = 2 * dz + zShift; + discreteFunction[2][1] = 0.4 * maxR; + discreteFunction[3][0] = 3 * dz + zShift; + discreteFunction[3][1] = 0.5 * maxR; + discreteFunction[4][0] = 4 * dz + zShift; + discreteFunction[4][1] = 1.0 * maxR; + discreteFunction[5][0] = 5 * dz + zShift; + discreteFunction[5][1] = 0.0; auto compositeOp = std::make_shared(startProp); addScaleOperator(*compositeOp); @@ -668,7 +680,7 @@ axom::klee::Shape createShape_Cylinder() : primal::Vector3D {params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); double radius = params.radius < 0 ? 0.5 : params.radius; - double height = params.length < 0 ? 0.8 : params.length; + double height = params.length < 0 ? 1.2 : params.length; discreteFunction[0][0] = -height / 2; discreteFunction[0][1] = radius; discreteFunction[1][0] = height / 2; @@ -694,9 +706,9 @@ axom::klee::Shape createShape_Cone() ? primal::Vector3D {0.1, 0.2, 0.4} : primal::Vector3D {params.direction.data()}; axom::Array discreteFunction({2, 2}, axom::ArrayStrideOrder::ROW); - double baseRadius = params.radius < 0 ? 0.5 : params.radius; + double baseRadius = params.radius < 0 ? 0.7 : params.radius; double topRadius = params.radius2 < 0 ? 0.1 : params.radius2; - double height = params.length < 0 ? 0.8 : params.length; + double height = params.length < 0 ? 1.3 : params.length; discreteFunction[0][0] = -height / 2; discreteFunction[0][1] = baseRadius; discreteFunction[1][0] = height / 2;