From 0157f35183cc808233821cbe52c683cf729c106f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 May 2026 11:46:01 -0700 Subject: [PATCH 01/72] Small initial refactor. --- src/axom/quest/Shaper.cpp | 83 ++++++++++++++++++++++----------------- src/axom/quest/Shaper.hpp | 50 +++++++++++++++++------ 2 files changed, 85 insertions(+), 48 deletions(-) diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 5e7b005ec5..ff0b86ef8d 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -38,23 +38,24 @@ Shaper::Shaper(RuntimePolicy execPolicy, ? allocatorId : axom::policyToDefaultAllocatorID(execPolicy)) , m_shapeSet(shapeSet) - , m_dc(dc) + , m_mfem_state() #if defined(AXOM_USE_CONDUIT) - , m_bpGrp(nullptr) - , m_bpTopo() - , m_bpNodeExt(nullptr) - , m_bpNodeInt() + , m_bp_state() #endif { + m_mfem_state = createMFEMState(); + m_mfem_state->dc = dc; + #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) - m_comm = m_dc->GetComm(); + m_comm = m_mfem_state->m_dc->GetComm(); #endif - m_cellCount = m_dc->GetMesh()->GetNE(); + m_cellCount = m_mfem_state->m_dc->GetMesh()->GetNE(); setFilePath(shapeSet.getPath()); } #endif +#if defined(AXOM_USE_CONDUIT) Shaper::Shaper(RuntimePolicy execPolicy, int allocatorId, const klee::ShapeSet& shapeSet, @@ -65,27 +66,32 @@ Shaper::Shaper(RuntimePolicy execPolicy, ? allocatorId : axom::policyToDefaultAllocatorID(execPolicy)) , m_shapeSet(shapeSet) -#if defined(AXOM_USE_CONDUIT) - , m_bpGrp(bpGrp) - , m_bpTopo(topo.empty() ? bpGrp->getGroup("topologies")->getGroupName(0) : topo) - , m_bpNodeExt(nullptr) - , m_bpNodeInt() -#endif -#if defined(AXOM_USE_MPI) + #if defined(AXOM_USE_MFEM) + , m_mfem_state() + #endif + , m_bp_state() + #if defined(AXOM_USE_MPI) , m_comm(MPI_COMM_WORLD) -#endif + #endif { - SLIC_ASSERT(m_bpTopo != sidre::InvalidName); + m_bp_state = createBlueprintState(); + m_bp_state->m_bpGrp = bpGrp; + m_bp_state->m_bpTopo = topo.empty() ? bpGrp->getGroup("topologies")->getGroupName(0) : topo; + m_bp_state->m_bpNodeExt = nullptr; + + SLIC_ASSERT(m_bp_state->m_bpTopo != sidre::InvalidName); // This may take too long if there are repeated construction. - m_bpGrp->createNativeLayout(m_bpNodeInt); + m_bp_state->m_bpGrp->createNativeLayout(m_bpNodeInt); m_cellCount = conduit::blueprint::mesh::topology::length( - m_bpNodeInt.fetch_existing("topologies").fetch_existing(m_bpTopo)); + m_bpNodeInt.fetch_existing("topologies").fetch_existing(m_bp_state->m_bpTopo)); setFilePath(shapeSet.getPath()); } +#endif +#if defined(AXOM_USE_CONDUIT) Shaper::Shaper(RuntimePolicy execPolicy, int allocatorId, const klee::ShapeSet& shapeSet, @@ -96,40 +102,44 @@ Shaper::Shaper(RuntimePolicy execPolicy, ? allocatorId : axom::policyToDefaultAllocatorID(execPolicy)) , m_shapeSet(shapeSet) -#if defined(AXOM_USE_CONDUIT) - , m_bpGrp(nullptr) - , m_bpTopo(topo.empty() ? bpNode.fetch_existing("topologies").child(0).name() : topo) - , m_bpNodeExt(&bpNode) - , m_bpNodeInt() -#endif + #if defined(AXOM_USE_MFEM) + , m_mfem_state() + #endif + , m_bp_state() #if defined(AXOM_USE_MPI) , m_comm(MPI_COMM_WORLD) #endif { AXOM_ANNOTATE_SCOPE("Shaper::Shaper_Node"); - m_bpGrp = m_dataStore.getRoot()->createGroup("internalGrp"); - m_bpGrp->setDefaultArrayAllocator(m_allocatorId); - m_bpGrp->importConduitTreeExternal(bpNode); + + m_bp_state = createBlueprintState(); + m_bp_state->m_bpGrp = nullptr; + m_bp_state->m_bpTopo = topo.empty() ? bpNode.fetch_existing("topologies").child(0).name() : topo; + m_bp_state->m_bpNodeExt = &bpNode; + + m_bp_state->m_bpGrp = m_dataStore.getRoot()->createGroup("internalGrp"); + m_bp_state->m_bpGrp->setDefaultArrayAllocator(m_allocatorId); + m_bp_state->m_bpGrp->importConduitTreeExternal(bpNode); // We want unstructured topo but can accomodate structured. - const std::string topoType = - bpNode.fetch_existing("topologies").fetch_existing(m_bpTopo).fetch_existing("type").as_string(); + const conduit::Node &n_topo = bpNode.fetch_existing("topologies").fetch_existing(m_bp_state->m_bpTopo); + const std::string topoType = n_topo.fetch_existing("type").as_string(); if(topoType == "structured") { AXOM_ANNOTATE_SCOPE("Shaper::convertStructured"); - const std::string shapeType = bpNode.fetch_existing("topologies/mesh/elements/shape").as_string(); + const std::string shapeType = n_topo.fetch_existing("elements/shape").as_string(); if(shapeType == "hex") { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d(m_bpGrp, - m_bpTopo, + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d(m_bp_state->m_bpGrp, + m_bp_state->m_bpTopo, m_execPolicy); } else if(shapeType == "quad") { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d(m_bpGrp, - m_bpTopo, + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d(m_bp_state->m_bpGrp, + m_bp_state->m_bpTopo, m_execPolicy); } else @@ -138,13 +148,14 @@ Shaper::Shaper(RuntimePolicy execPolicy, } } - m_bpGrp->createNativeLayout(m_bpNodeInt); + m_bp_state->m_bpGrp->createNativeLayout(m_bp_state->m_bpNodeInt); m_cellCount = conduit::blueprint::mesh::topology::length( - bpNode.fetch_existing("topologies").fetch_existing(m_bpTopo)); + bpNode.fetch_existing("topologies").fetch_existing(m_bp_state->m_bpTopo)); setFilePath(shapeSet.getPath()); } +#endif Shaper::~Shaper() { } diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index a6de63f9eb..74829588d2 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -50,6 +50,25 @@ class Shaper { public: using RuntimePolicy = axom::runtime_policy::Policy; +#if defined(AXOM_USE_MFEM) + struct MFEMState + { + // For mesh represented as MFEMSidreDataCollection + sidre::MFEMSidreDataCollection* m_dc {nullptr}; + }; +#endif +#if defined(AXOM_USE_CONDUIT) + struct BlueprintState + { + //! @brief Version of the mesh for computations. + axom::sidre::Group* m_bpGrp {nullptr}; + std::string m_bpTopo; + //! @brief Mesh in an external Node, when provided as a Node. + conduit::Node* m_bpNodeExt {nullptr}; + //! @brief Initial copy of mesh in an internal Node storage. + conduit::Node m_bpNodeInt; + }; +#endif #if defined(AXOM_USE_MFEM) /// @brief Construct Shaper to operate on an MFEM mesh. @@ -59,6 +78,7 @@ class Shaper sidre::MFEMSidreDataCollection* dc); #endif +#if defined(AXOM_USE_CONDUIT) /*! * @brief Construct Shaper to operate on a blueprint-formatted mesh * stored in a sidre Group. @@ -83,6 +103,7 @@ class Shaper const klee::ShapeSet& shapeSet, conduit::Node& bpNode, const std::string& topo = ""); +#endif virtual ~Shaper(); @@ -123,8 +144,8 @@ class Shaper bool isVerbose() const { return m_verboseOutput; } #ifdef AXOM_USE_MFEM - sidre::MFEMSidreDataCollection* getDC() { return m_dc; } - const sidre::MFEMSidreDataCollection* getDC() const { return m_dc; } + sidre::MFEMSidreDataCollection* getDC() { return m_mfem_state->m_dc; } + const sidre::MFEMSidreDataCollection* getDC() const { return m_mfem_state->m_dc; } #endif /*! @@ -231,6 +252,19 @@ class Shaper */ int getRank() const; +#if defined(AXOM_USE_MFEM) + virtual std::unique_ptr createMFEMState() + { + return std::make_unique(); + } +#endif +#if defined(AXOM_USE_CONDUIT) + virtual std::unique_ptr createBlueprintState() + { + return std::make_unique(); + } +#endif + protected: RuntimePolicy m_execPolicy; int m_allocatorId; @@ -244,18 +278,10 @@ class Shaper std::string m_prefixPath; #if defined(AXOM_USE_MFEM) - // For mesh represented as MFEMSidreDataCollection - sidre::MFEMSidreDataCollection* m_dc {nullptr}; + std::unique_ptr m_mfem_state; #endif - #if defined(AXOM_USE_CONDUIT) - //! @brief Version of the mesh for computations. - axom::sidre::Group* m_bpGrp {nullptr}; - const std::string m_bpTopo; - //! @brief Mesh in an external Node, when provided as a Node. - conduit::Node* m_bpNodeExt {nullptr}; - //! @brief Initial copy of mesh in an internal Node storage. - conduit::Node m_bpNodeInt; + std::unique_ptr m_bp_state; #endif //! @brief Number of cells in computational mesh (m_dc or m_bpGrp). From 087dd11b94da301c3f68ca58f5ab58c207f8d3cf Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 May 2026 14:32:14 -0700 Subject: [PATCH 02/72] Started to consolidate state into MFEM and Blueprint state objects and use them. --- src/axom/quest/IntersectionShaper.hpp | 69 +++-- src/axom/quest/SamplingShaper.hpp | 285 +++++++++++++----- src/axom/quest/Shaper.cpp | 66 ++-- src/axom/quest/Shaper.hpp | 44 +-- .../quest/detail/shaping/InOutSampler.hpp | 30 +- .../quest/detail/shaping/PrimitiveSampler.hpp | 34 ++- .../detail/shaping/WindingNumberSampler.hpp | 38 ++- .../quest/detail/shaping/shaping_helpers.cpp | 17 ++ .../quest/detail/shaping/shaping_helpers.hpp | 88 +++++- 9 files changed, 456 insertions(+), 215 deletions(-) diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index ea12ad1499..267c1c22a6 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -1953,7 +1953,7 @@ class IntersectionShaper : public Shaper { std::vector materialNames; #if defined(AXOM_USE_MFEM) - if(m_dc) + if(getDC() != nullptr) { for(auto it : this->getDC()->GetFieldMap()) { @@ -1966,9 +1966,9 @@ class IntersectionShaper : public Shaper } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bpGrp) + if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) { - auto fieldsGrp = m_bpGrp->getGroup("fields"); + auto fieldsGrp = m_bp_state->m_group_ptr->getGroup("fields"); if(fieldsGrp != nullptr) { for(auto& group : fieldsGrp->groups()) @@ -2501,16 +2501,16 @@ class IntersectionShaper : public Shaper { bool has = false; #if defined(AXOM_USE_MFEM) - if(m_dc != nullptr) + if(getDC() != nullptr) { - has = m_dc->HasField(fieldName); + has = getDC()->HasField(fieldName); } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bpGrp != nullptr) + if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) { std::string fieldPath = axom::fmt::format("fields/{}", fieldName); - has = m_bpGrp->hasGroup(fieldPath); + has = m_bp_state->m_group_ptr->hasGroup(fieldPath); } #endif return has; @@ -2532,40 +2532,40 @@ class IntersectionShaper : public Shaper axom::ArrayView rval; #if defined(AXOM_USE_MFEM) - if(m_dc != nullptr) + if(getDC() != nullptr) { mfem::GridFunction* gridFunc = nullptr; - if(m_dc->HasField(fieldName)) + if(getDC()->HasField(fieldName)) { - gridFunc = m_dc->GetField(fieldName); + gridFunc = getDC()->GetField(fieldName); } else { gridFunc = newVolFracGridFunction(); - m_dc->RegisterField(fieldName, gridFunc); + getDC()->RegisterField(fieldName, gridFunc); } rval = axom::ArrayView(gridFunc->GetData(), gridFunc->Size()); } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bpGrp != nullptr) + if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) { std::string fieldPath = "fields/" + fieldName; auto dtype = conduit::DataType::float64(m_cellCount); axom::sidre::View* valuesView = nullptr; - if(m_bpGrp->hasGroup(fieldPath)) + if(m_bp_state->m_group_ptr->hasGroup(fieldPath)) { - auto* fieldGrp = m_bpGrp->getGroup(fieldPath); + auto* fieldGrp = m_bp_state->m_group_ptr->getGroup(fieldPath); valuesView = fieldGrp->getView("values"); SLIC_ASSERT(fieldGrp->getView("association")->getString() == std::string("element")); - SLIC_ASSERT(fieldGrp->getView("topology")->getString() == m_bpTopo); + SLIC_ASSERT(fieldGrp->getView("topology")->getString() == m_bp_state->m_topology_name); SLIC_ASSERT(valuesView->getNumElements() == m_cellCount); SLIC_ASSERT(valuesView->getNode().dtype().id() == dtype.id()); } else { - if(m_bpNodeExt != nullptr) + if(m_bp_state->m_external_node_ptr != nullptr) { /* If the computational mesh is an external conduit::Node, it @@ -2574,7 +2574,7 @@ class IntersectionShaper : public Shaper the allocator id for only array data. conduit::Node doesn't have this capability. */ - SLIC_WARNING_IF(m_bpNodeExt != nullptr, + SLIC_WARNING_IF(m_bp_state->m_external_node_ptr != nullptr, "For a computational mesh in a conduit::Node, all" " output fields must be preallocated before shaping." " IntersectionShaper will NOT contravene the user's" @@ -2590,12 +2590,12 @@ class IntersectionShaper : public Shaper { constexpr axom::IndexType componentCount = 1; axom::IndexType shape[2] = {m_cellCount, componentCount}; - auto* fieldGrp = m_bpGrp->createGroup(fieldPath); + auto* fieldGrp = m_bp_state->m_group_ptr->createGroup(fieldPath); // valuesView = fieldGrp->createView("values"); valuesView = fieldGrp->createViewWithShape("values", axom::sidre::DataTypeId::FLOAT64_ID, 2, shape); fieldGrp->createView("association")->setString("element"); - fieldGrp->createView("topology")->setString(m_bpTopo); + fieldGrp->createView("topology")->setString(m_bp_state->m_topology_name); fieldGrp->createView("volume_dependent") ->setString(std::string(volumeDependent ? "true" : "false")); valuesView->allocate(); @@ -2626,13 +2626,13 @@ class IntersectionShaper : public Shaper allocId); #if defined(AXOM_USE_MFEM) - if(m_dc != nullptr) + if(getDC() != nullptr) { populateVertCoordsFromMFEMMesh(vertCoords, 2); } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bpGrp != nullptr) + if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) { populateVertCoordsFromBlueprintMesh2D(vertCoords); } @@ -2672,13 +2672,13 @@ class IntersectionShaper : public Shaper allocId); #if defined(AXOM_USE_MFEM) - if(m_dc != nullptr) + if(getDC() != nullptr) { populateVertCoordsFromMFEMMesh(vertCoords, 3); } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bpGrp != nullptr) + if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) { populateVertCoordsFromBlueprintMesh3D(vertCoords); } @@ -2720,9 +2720,11 @@ class IntersectionShaper : public Shaper // Put mesh in Node so we can use conduit::blueprint utilities. // conduit::Node meshNode; - // m_bpGrp->createNativeLayout(m_bpNodeInt); + // m_group_ptr->createNativeLayout(m_internal_node); - const conduit::Node& topoNode = m_bpNodeInt.fetch_existing("topologies").fetch_existing(m_bpTopo); + const conduit::Node& topoNode = + m_bp_state->m_internal_node.fetch_existing("topologies") + .fetch_existing(m_bp_state->m_topology_name); const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); // Assume unstructured and hexahedral @@ -2742,7 +2744,7 @@ class IntersectionShaper : public Shaper const auto* connPtr = static_cast(connNode.data_ptr()); axom::ArrayView conn(connPtr, m_cellCount, NUM_VERTS_PER_QUAD); - const conduit::Node& coordNode = m_bpNodeInt["coordsets"][coordsetName]; + const conduit::Node& coordNode = m_bp_state->m_internal_node["coordsets"][coordsetName]; const conduit::Node& coordValues = coordNode.fetch_existing("values"); axom::IndexType vertexCount = coordValues["x"].dtype().number_of_elements(); bool isInterleaved = conduit::blueprint::mcarray::is_interleaved(coordValues); @@ -2792,9 +2794,11 @@ class IntersectionShaper : public Shaper // Put mesh in Node so we can use conduit::blueprint utilities. // conduit::Node meshNode; - // m_bpGrp->createNativeLayout(m_bpNodeInt); + // m_group_ptr->createNativeLayout(m_internal_node); - const conduit::Node& topoNode = m_bpNodeInt.fetch_existing("topologies").fetch_existing(m_bpTopo); + const conduit::Node& topoNode = + m_bp_state->m_internal_node.fetch_existing("topologies") + .fetch_existing(m_bp_state->m_topology_name); const conduit::Node& topoCoordsetNode = topoNode.fetch_existing("coordset"); const std::string coordsetName = topoCoordsetNode.as_string(); @@ -2815,7 +2819,7 @@ class IntersectionShaper : public Shaper const auto* connPtr = static_cast(connNode.data_ptr()); axom::ArrayView conn(connPtr, m_cellCount, NUM_VERTS_PER_HEX); - const conduit::Node& coordNode = m_bpNodeInt["coordsets"][coordsetName]; + const conduit::Node& coordNode = m_bp_state->m_internal_node["coordsets"][coordsetName]; const conduit::Node& coordValues = coordNode.fetch_existing("values"); axom::IndexType vertexCount = coordValues["x"].dtype().number_of_elements(); bool isInterleaved = conduit::blueprint::mcarray::is_interleaved(coordValues); @@ -2964,15 +2968,16 @@ class IntersectionShaper : public Shaper { int dim = -1; #if defined(AXOM_USE_MFEM) - if(m_dc != nullptr) + if(getDC() != nullptr) { dim = this->getDC()->GetMesh()->SpaceDimension(); } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bpGrp != nullptr) + if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) { - std::string mesh_type = m_bpGrp->getView("topologies/mesh/elements/shape")->getString(); + std::string mesh_type = + m_bp_state->m_group_ptr->getView("topologies/mesh/elements/shape")->getString(); if(mesh_type == "hex") { dim = 3; diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 4a7dbfd5db..3b843b4f66 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -44,7 +44,6 @@ namespace axom { namespace quest { - /// \brief Concrete class for sample based shaping class SamplingShaper : public Shaper { @@ -135,23 +134,12 @@ class SamplingShaper : public Shaper const klee::ShapeSet& shapeSet, sidre::MFEMSidreDataCollection* dc) : Shaper(execPolicy, allocatorId, shapeSet, dc) - { } - - ~SamplingShaper() { - m_inoutShapeQFuncs.DeleteData(true); - m_inoutShapeQFuncs.clear(); - - m_inoutMaterialQFuncs.DeleteData(true); - m_inoutMaterialQFuncs.clear(); - - m_inoutTensors.DeleteData(true); - m_inoutTensors.clear(); - - m_inoutArrays.DeleteData(true); - m_inoutArrays.clear(); + initializeSamplingMFEMState(); } + ~SamplingShaper() override = default; + ///@{ //! @name Functions to get and set shaping parameters related to sampling; supplements parameters in base class @@ -250,15 +238,71 @@ class SamplingShaper : public Shaper /// Returns a pointer to the quadrature function associated with shape \a name if it exists, else nullptr mfem::QuadratureFunction* getShapeQFunction(const std::string& name) const { - return m_inoutShapeQFuncs.Get(name); + return shapeQFuncs().Get(name); } /// Returns a pointer to the quadrature function associated with material \a name if it exists, else nullptr mfem::QuadratureFunction* getMaterialQFunction(const std::string& name) const { - return m_inoutMaterialQFuncs.Get(name); + return materialQFuncs().Get(name); } private: + std::unique_ptr createMFEMState() override + { + return std::make_unique(); + } + + void initializeSamplingMFEMState() + { + // Shaper constructs its MFEM state in the base constructor, so upgrade it + // here rather than relying on virtual dispatch during base construction. + auto samplingState = std::make_unique(); + if(m_mfem_state != nullptr) + { + samplingState->m_dc = m_mfem_state->m_dc; + } + m_mfem_state = std::move(samplingState); + } + + shaping::SamplingMFEMState& samplingMFEMState() + { + SLIC_ASSERT(m_mfem_state != nullptr); + return static_cast(*m_mfem_state); + } + + const shaping::SamplingMFEMState& samplingMFEMState() const + { + SLIC_ASSERT(m_mfem_state != nullptr); + return static_cast(*m_mfem_state); + } + + shaping::QFunctionCollection& shapeQFuncs() { return samplingMFEMState().m_inoutShapeQFuncs; } + const shaping::QFunctionCollection& shapeQFuncs() const + { + return samplingMFEMState().m_inoutShapeQFuncs; + } + + shaping::QFunctionCollection& materialQFuncs() + { + return samplingMFEMState().m_inoutMaterialQFuncs; + } + const shaping::QFunctionCollection& materialQFuncs() const + { + return samplingMFEMState().m_inoutMaterialQFuncs; + } + + shaping::DenseTensorCollection& tensors() { return samplingMFEMState().m_inoutTensors; } + const shaping::DenseTensorCollection& tensors() const + { + return samplingMFEMState().m_inoutTensors; + } + + shaping::MFEMArrayCollection& arrays() { return samplingMFEMState().m_inoutArrays; } + const shaping::MFEMArrayCollection& arrays() const + { + return samplingMFEMState().m_inoutArrays; + } + bool hasValidSampler() const { return !std::holds_alternative(m_sampler); } klee::Dimensions getShapeDimension() const @@ -485,7 +529,7 @@ class SamplingShaper : public Shaper if(shape.getGeometry().hasGeometry()) { // Get inout qfunc for this shape - shapeQFunc = m_inoutShapeQFuncs.Get(axom::fmt::format("inout_{}", shapeName)); + shapeQFunc = shapeQFuncs().Get(axom::fmt::format("inout_{}", shapeName)); SLIC_ERROR_IF(shapeQFunc == nullptr, axom::fmt::format("Missing inout samples for shape '{}'. " @@ -496,7 +540,7 @@ class SamplingShaper : public Shaper else { // No input geometry for the shape, get inout qfunc for associated material - shapeQFunc = m_inoutMaterialQFuncs.Get(axom::fmt::format("mat_inout_{}", thisMatName)); + shapeQFunc = materialQFuncs().Get(axom::fmt::format("mat_inout_{}", thisMatName)); SLIC_ERROR_IF(shapeQFunc == nullptr, axom::fmt::format("Missing inout samples for material '{}' while applying " @@ -531,7 +575,7 @@ class SamplingShaper : public Shaper shouldReplace ? "yes" : "no")); auto* otherMatQFunc = - m_inoutMaterialQFuncs.Get(axom::fmt::format("mat_inout_{}", otherMatName)); + materialQFuncs().Get(axom::fmt::format("mat_inout_{}", otherMatName)); SLIC_ERROR_IF(otherMatQFunc == nullptr, axom::fmt::format("Missing inout samples for material '{}' while applying " "replacement rules for shape '{}'.", @@ -543,15 +587,15 @@ class SamplingShaper : public Shaper // Get inout qfunc for the current material const std::string materialQFuncName = axom::fmt::format("mat_inout_{}", thisMatName); - if(!m_inoutMaterialQFuncs.Has(materialQFuncName)) + if(!materialQFuncs().Has(materialQFuncName)) { // initialize material from shape inout, the QFunc registry takes ownership - m_inoutMaterialQFuncs.Register(materialQFuncName, shapeQFuncCopy, true); + materialQFuncs().Register(materialQFuncName, shapeQFuncCopy, true); } else { // copy shape data into current material and delete the copy - auto* matQFunc = m_inoutMaterialQFuncs.Get(materialQFuncName); + auto* matQFunc = materialQFuncs().Get(materialQFuncName); SLIC_ERROR_IF(matQFunc == nullptr, axom::fmt::format("Missing inout samples for material '{}' while updating " "the material field for shape '{}'.", @@ -600,17 +644,10 @@ class SamplingShaper : public Shaper internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug : slic::message::Warning); - auto* mesh = m_dc->GetMesh(); - - // ensure we have a starting quadrature field for the positions - if(!m_inoutShapeQFuncs.Has("positions")) - { - shaping::generatePositionsQFunction(mesh, - m_inoutShapeQFuncs, - m_sampleResolution, - m_quadratureType); - } - auto* positionsQSpace = m_inoutShapeQFuncs.Get("positions")->GetSpace(); + auto& mfemState = samplingMFEMState(); + auto* mesh = mfemState.m_dc->GetMesh(); + ensurePositionsQFunction(mfemState); + auto* positionsQSpace = mfemState.m_inoutShapeQFuncs.Get("positions")->GetSpace(); // Interpolate grid functions at quadrature points & register material quad functions // assume all elements have same integration rule @@ -657,7 +694,7 @@ class SamplingShaper : public Shaper } const auto matName = axom::fmt::format("mat_inout_{}", name); - m_inoutMaterialQFuncs.Register(matName, matQFunc, true); + materialQFuncs().Register(matName, matQFunc, true); } } @@ -668,7 +705,7 @@ class SamplingShaper : public Shaper internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug : slic::message::Warning); - for(auto& mat : m_inoutMaterialQFuncs) + for(auto& mat : materialQFuncs()) { const std::string matName = mat.first; SLIC_INFO_ROOT( @@ -709,8 +746,8 @@ class SamplingShaper : public Shaper "\n\t* Data collection qfuncs: {}" "\n\t* Known materials: {}", initialMessage, - axom::fmt::join(extractKeys(m_dc->GetFieldMap()), ", "), - axom::fmt::join(extractKeys(m_dc->GetQFieldMap()), ", "), + axom::fmt::join(extractKeys(getDC()->GetFieldMap()), ", "), + axom::fmt::join(extractKeys(getDC()->GetQFieldMap()), ", "), axom::fmt::join(m_knownMaterials, ", ")); if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) @@ -718,25 +755,60 @@ class SamplingShaper : public Shaper axom::fmt::format_to(std::back_inserter(out), "\n\t* Shape qfuncs: {}" "\n\t* Mat qfuncs: {}", - axom::fmt::join(extractKeys(m_inoutShapeQFuncs), ", "), - axom::fmt::join(extractKeys(m_inoutMaterialQFuncs), ", ")); + axom::fmt::join(extractKeys(shapeQFuncs()), ", "), + axom::fmt::join(extractKeys(materialQFuncs()), ", ")); } else if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_DOFS) { axom::fmt::format_to(std::back_inserter(out), "\n\t* Shaping tensors: {}", - axom::fmt::join(extractKeys(m_inoutTensors), ", ")); + axom::fmt::join(extractKeys(tensors()), ", ")); } SLIC_INFO_ROOT(axom::fmt::to_string(out)); } private: + void ensurePositionsQFunction(shaping::SamplingMFEMState& mfemState) + { + if(!mfemState.m_inoutShapeQFuncs.Has("positions")) + { + shaping::generatePositionsQFunction(mfemState, m_sampleResolution, m_quadratureType); + } + } + +#if defined(AXOM_USE_CONDUIT) + void ensurePositionsQFunction(shaping::BlueprintState& bpState) + { + shaping::generatePositionsQFunction(bpState, m_sampleResolution, m_quadratureType); + } +#endif + + static int meshDimension(const shaping::SamplingMFEMState& mfemState) + { + return mfemState.m_dc->GetMesh()->Dimension(); + } + +#if defined(AXOM_USE_CONDUIT) + static int meshDimension(const shaping::BlueprintState& bpState) + { + const conduit::Node& topoNode = + bpState.m_internal_node.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); + const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); + return bpState.m_internal_node["coordsets"][coordsetName]["values"].number_of_children(); + } +#endif + // Handles 2D or 3D shaping for compatible samplers, based on the template and associated parameter - template - void runShapeQueryImplSampler(SamplerType* sampler) + template + void runShapeQueryImplSampler(SamplerType* sampler, MeshState& meshState) { // Sample the InOut field at the mesh quadrature points - const int meshDim = m_dc->GetMesh()->Dimension(); + if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) + { + ensurePositionsQFunction(meshState); + } + + const int meshDim = meshDimension(meshState); switch(m_vfSampling) { case shaping::VolFracSampling::SAMPLE_AT_QPTS: @@ -745,16 +817,14 @@ class SamplingShaper : public Shaper case 2: if(meshDim == 2) { - sampler->template sampleInOutField<2, 2>(m_dc, - m_inoutShapeQFuncs, + sampler->template sampleInOutField<2, 2>(meshState, m_sampleResolution, m_quadratureType, m_projector22); } else if(meshDim == 3) { - sampler->template sampleInOutField<3, 2>(m_dc, - m_inoutShapeQFuncs, + sampler->template sampleInOutField<3, 2>(meshState, m_sampleResolution, m_quadratureType, m_projector32); @@ -763,16 +833,14 @@ class SamplingShaper : public Shaper case 3: if(meshDim == 2) { - sampler->template sampleInOutField<2, 3>(m_dc, - m_inoutShapeQFuncs, + sampler->template sampleInOutField<2, 3>(meshState, m_sampleResolution, m_quadratureType, m_projector23); } else if(meshDim == 3) { - sampler->template sampleInOutField<3, 3>(m_dc, - m_inoutShapeQFuncs, + sampler->template sampleInOutField<3, 3>(meshState, m_sampleResolution, m_quadratureType, m_projector33); @@ -786,21 +854,29 @@ class SamplingShaper : public Shaper case 2: if(meshDim == 2) { - sampler->template computeVolumeFractionsBaseline<2, 2>(m_dc, m_volfracOrder, m_projector22); + sampler->template computeVolumeFractionsBaseline<2, 2>(meshState, + m_volfracOrder, + m_projector22); } else if(meshDim == 3) { - sampler->template computeVolumeFractionsBaseline<3, 2>(m_dc, m_volfracOrder, m_projector32); + sampler->template computeVolumeFractionsBaseline<3, 2>(meshState, + m_volfracOrder, + m_projector32); } break; case 3: if(meshDim == 2) { - sampler->template computeVolumeFractionsBaseline<2, 3>(m_dc, m_volfracOrder, m_projector23); + sampler->template computeVolumeFractionsBaseline<2, 3>(meshState, + m_volfracOrder, + m_projector23); } else if(meshDim == 3) { - sampler->template computeVolumeFractionsBaseline<3, 3>(m_dc, m_volfracOrder, m_projector33); + sampler->template computeVolumeFractionsBaseline<3, 3>(meshState, + m_volfracOrder, + m_projector33); } break; } @@ -812,22 +888,55 @@ class SamplingShaper : public Shaper template void runShapeQueryImpl(shaping::InOutSampler* sampler) { - runShapeQueryImplSampler(sampler); +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) + { + runShapeQueryImplSampler(sampler, samplingMFEMState()); + return; + } +#endif +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + runShapeQueryImplSampler(sampler, *m_bp_state); + return; + } +#endif + SLIC_ERROR("No mesh state is available for SamplingShaper."); } // Handles 2D or 3D shaping for InOutSampler, based on the template and associated parameter template void runShapeQueryImpl(shaping::WindingNumberSampler* sampler) { - runShapeQueryImplSampler(sampler); + #if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) + { + runShapeQueryImplSampler(sampler, samplingMFEMState()); + return; + } +#endif +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + runShapeQueryImplSampler(sampler, *m_bp_state); + return; + } +#endif + SLIC_ERROR("No mesh state is available for SamplingShaper."); } // Handles 2D or 3D shaping for PrimitiveSampler, based on the template and associated parameter template void runShapeQueryImpl(shaping::PrimitiveSampler* sampler) { - // Sample the InOut field at the mesh quadrature points - const int meshDim = m_dc->GetMesh()->Dimension(); + auto runImpl = [this, sampler](auto& meshState) { + const int meshDim = meshDimension(meshState); + if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) + { + ensurePositionsQFunction(meshState); + } + switch(m_vfSampling) { case shaping::VolFracSampling::SAMPLE_AT_QPTS: @@ -839,16 +948,14 @@ class SamplingShaper : public Shaper case 3: if(meshDim == 2) { - sampler->template sampleInOutField<2, 3>(m_dc, - m_inoutShapeQFuncs, + sampler->template sampleInOutField<2, 3>(meshState, m_sampleResolution, m_quadratureType, m_projector23); } else if(meshDim == 3) { - sampler->template sampleInOutField<3, 3>(m_dc, - m_inoutShapeQFuncs, + sampler->template sampleInOutField<3, 3>(meshState, m_sampleResolution, m_quadratureType, m_projector33); @@ -860,6 +967,23 @@ class SamplingShaper : public Shaper SLIC_ERROR("Not implemented yet!"); break; } + }; + +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) + { + runImpl(samplingMFEMState()); + return; + } +#endif +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + runImpl(*m_bp_state); + return; + } +#endif + SLIC_ERROR("No mesh state is available for SamplingShaper."); } /** @@ -875,7 +999,7 @@ class SamplingShaper : public Shaper // Retrieve the inout samples QFunc SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); - mfem::QuadratureFunction* inout = m_inoutMaterialQFuncs.Get(matField); + mfem::QuadratureFunction* inout = materialQFuncs().Get(matField); const auto& sampleIR = inout->GetSpace()->GetIntRule(0); // assume all elements are the same const int sampleOrder = sampleIR.GetOrder(); @@ -883,7 +1007,7 @@ class SamplingShaper : public Shaper const int sampleSZ = inout->GetSpace()->GetSize(); // extract some properties from computational mesh - mfem::Mesh* mesh = m_dc->GetMesh(); + mfem::Mesh* mesh = getDC()->GetMesh(); const int dim = mesh->Dimension(); const int NE = mesh->GetNE(); const auto geom = mesh->GetTypicalElementGeometry(); @@ -915,7 +1039,7 @@ class SamplingShaper : public Shaper // Access or create a registered volume fraction grid function from the data collection const auto vf_name = axom::fmt::format("vol_frac_{}", matField.substr(10)); - mfem::GridFunction* vf = shaping::getOrAllocateL2GridFunction(m_dc, + mfem::GridFunction* vf = shaping::getOrAllocateL2GridFunction(getDC(), vf_name, m_volfracOrder, dim, @@ -926,9 +1050,9 @@ class SamplingShaper : public Shaper // access or compute the mass matrix mfem::DenseTensor* mass_mat {nullptr}; const std::string mass_matrix_name = "shaping_mass_matrix"; - if(this->m_inoutTensors.Has(mass_matrix_name)) + if(this->tensors().Has(mass_matrix_name)) { - mass_mat = m_inoutTensors.Get(mass_matrix_name); + mass_mat = tensors().Get(mass_matrix_name); } else { @@ -973,7 +1097,7 @@ class SamplingShaper : public Shaper mfem::Swap(mass_mat->GetMemory(), mass_vec.GetMemory()); } - m_inoutTensors.Register(mass_matrix_name, mass_mat, true); + tensors().Register(mass_matrix_name, mass_mat, true); } SLIC_ASSERT(mass_mat->SizeI() == dofs); SLIC_ASSERT(mass_mat->SizeJ() == dofs); @@ -984,10 +1108,10 @@ class SamplingShaper : public Shaper mfem::Array* mass_mat_pivots {nullptr}; const std::string minv_name = "shaping_mass_matrix_inv"; const std::string pivots_name = "shaping_mass_matrix_pivots"; - if(this->m_inoutTensors.Has(minv_name) && this->m_inoutArrays.Has(pivots_name)) + if(this->tensors().Has(minv_name) && this->arrays().Has(pivots_name)) { - mass_mat_inv = this->m_inoutTensors.Get(minv_name); - mass_mat_pivots = this->m_inoutArrays.Get(pivots_name); + mass_mat_inv = this->tensors().Get(minv_name); + mass_mat_pivots = this->arrays().Get(pivots_name); } else { @@ -1002,8 +1126,8 @@ class SamplingShaper : public Shaper mass_mat_pivots->Write(); mfem::BatchLUFactor(*mass_mat_inv, *mass_mat_pivots); - m_inoutTensors.Register(minv_name, mass_mat_inv, true); - m_inoutArrays.Register(pivots_name, mass_mat_pivots, true); + tensors().Register(minv_name, mass_mat_inv, true); + arrays().Register(pivots_name, mass_mat_pivots, true); } SLIC_ASSERT(mass_mat_inv->SizeJ() == dofs); SLIC_ASSERT(mass_mat_inv->SizeI() == dofs); @@ -1012,9 +1136,9 @@ class SamplingShaper : public Shaper mfem::DenseTensor* shaping_scratch_buffer {nullptr}; const std::string scratch_buffer_name = "shaping_scratch_buffer"; - if(this->m_inoutTensors.Has(scratch_buffer_name)) + if(this->tensors().Has(scratch_buffer_name)) { - shaping_scratch_buffer = this->m_inoutTensors.Get(scratch_buffer_name); + shaping_scratch_buffer = this->tensors().Get(scratch_buffer_name); } else { @@ -1024,7 +1148,7 @@ class SamplingShaper : public Shaper shaping_scratch_buffer->HostWrite(); (*shaping_scratch_buffer) = 0.; - m_inoutTensors.Register(scratch_buffer_name, shaping_scratch_buffer, true); + tensors().Register(scratch_buffer_name, shaping_scratch_buffer, true); } SLIC_ASSERT(shaping_scratch_buffer->SizeJ() == dofs); SLIC_ASSERT(shaping_scratch_buffer->SizeI() == dofs); @@ -1148,11 +1272,6 @@ class SamplingShaper : public Shaper } private: - shaping::QFunctionCollection m_inoutShapeQFuncs; - shaping::QFunctionCollection m_inoutMaterialQFuncs; - shaping::DenseTensorCollection m_inoutTensors; - shaping::MFEMArrayCollection m_inoutArrays; - // Holds an instance of the 2D or 3D sampler; only one can be active at a time SamplerVariant m_sampler; axom::Array>> m_contours; diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index ff0b86ef8d..f250b477a3 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -44,7 +44,7 @@ Shaper::Shaper(RuntimePolicy execPolicy, #endif { m_mfem_state = createMFEMState(); - m_mfem_state->dc = dc; + m_mfem_state->m_dc = dc; #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) m_comm = m_mfem_state->m_dc->GetComm(); @@ -75,17 +75,19 @@ Shaper::Shaper(RuntimePolicy execPolicy, #endif { m_bp_state = createBlueprintState(); - m_bp_state->m_bpGrp = bpGrp; - m_bp_state->m_bpTopo = topo.empty() ? bpGrp->getGroup("topologies")->getGroupName(0) : topo; - m_bp_state->m_bpNodeExt = nullptr; + m_bp_state->m_group_ptr = bpGrp; + m_bp_state->m_topology_name = + topo.empty() ? bpGrp->getGroup("topologies")->getGroupName(0) : topo; + m_bp_state->m_external_node_ptr = nullptr; - SLIC_ASSERT(m_bp_state->m_bpTopo != sidre::InvalidName); + SLIC_ASSERT(m_bp_state->m_topology_name != sidre::InvalidName); // This may take too long if there are repeated construction. - m_bp_state->m_bpGrp->createNativeLayout(m_bpNodeInt); + m_bp_state->m_group_ptr->createNativeLayout(m_bp_state->m_internal_node); m_cellCount = conduit::blueprint::mesh::topology::length( - m_bpNodeInt.fetch_existing("topologies").fetch_existing(m_bp_state->m_bpTopo)); + m_bp_state->m_internal_node.fetch_existing("topologies") + .fetch_existing(m_bp_state->m_topology_name)); setFilePath(shapeSet.getPath()); } @@ -113,16 +115,18 @@ Shaper::Shaper(RuntimePolicy execPolicy, AXOM_ANNOTATE_SCOPE("Shaper::Shaper_Node"); m_bp_state = createBlueprintState(); - m_bp_state->m_bpGrp = nullptr; - m_bp_state->m_bpTopo = topo.empty() ? bpNode.fetch_existing("topologies").child(0).name() : topo; - m_bp_state->m_bpNodeExt = &bpNode; + m_bp_state->m_group_ptr = nullptr; + m_bp_state->m_topology_name = + topo.empty() ? bpNode.fetch_existing("topologies").child(0).name() : topo; + m_bp_state->m_external_node_ptr = &bpNode; - m_bp_state->m_bpGrp = m_dataStore.getRoot()->createGroup("internalGrp"); - m_bp_state->m_bpGrp->setDefaultArrayAllocator(m_allocatorId); - m_bp_state->m_bpGrp->importConduitTreeExternal(bpNode); + m_bp_state->m_group_ptr = m_dataStore.getRoot()->createGroup("internalGrp"); + m_bp_state->m_group_ptr->setDefaultArrayAllocator(m_allocatorId); + m_bp_state->m_group_ptr->importConduitTreeExternal(bpNode); // We want unstructured topo but can accomodate structured. - const conduit::Node &n_topo = bpNode.fetch_existing("topologies").fetch_existing(m_bp_state->m_bpTopo); + const conduit::Node& n_topo = + bpNode.fetch_existing("topologies").fetch_existing(m_bp_state->m_topology_name); const std::string topoType = n_topo.fetch_existing("type").as_string(); if(topoType == "structured") @@ -132,15 +136,17 @@ Shaper::Shaper(RuntimePolicy execPolicy, if(shapeType == "hex") { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d(m_bp_state->m_bpGrp, - m_bp_state->m_bpTopo, - m_execPolicy); + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d( + m_bp_state->m_group_ptr, + m_bp_state->m_topology_name, + m_execPolicy); } else if(shapeType == "quad") { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d(m_bp_state->m_bpGrp, - m_bp_state->m_bpTopo, - m_execPolicy); + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d( + m_bp_state->m_group_ptr, + m_bp_state->m_topology_name, + m_execPolicy); } else { @@ -148,10 +154,10 @@ Shaper::Shaper(RuntimePolicy execPolicy, } } - m_bp_state->m_bpGrp->createNativeLayout(m_bp_state->m_bpNodeInt); + m_bp_state->m_group_ptr->createNativeLayout(m_bp_state->m_internal_node); m_cellCount = conduit::blueprint::mesh::topology::length( - bpNode.fetch_existing("topologies").fetch_existing(m_bp_state->m_bpTopo)); + bpNode.fetch_existing("topologies").fetch_existing(m_bp_state->m_topology_name)); setFilePath(shapeSet.getPath()); } @@ -273,23 +279,27 @@ bool Shaper::verifyInputMesh(std::string& whyBad) const bool rval = true; #if defined(AXOM_USE_CONDUIT) - if(m_bpGrp != nullptr) + if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) { conduit::Node info; - // Conduit's verify should work even if m_bpNodeInt has array data on + // Conduit's verify should work even if m_internal_node has array data on // devices. because the verification doesn't dereference array data. // If this changes in the future, more care must be taken. - rval = conduit::blueprint::mesh::verify(m_bpNodeInt, info); + rval = conduit::blueprint::mesh::verify(m_bp_state->m_internal_node, info); if(rval) { - std::string topoType = m_bpNodeInt.fetch("topologies")[m_bpTopo]["type"].as_string(); + std::string topoType = + m_bp_state->m_internal_node.fetch("topologies")[m_bp_state->m_topology_name]["type"] + .as_string(); rval = topoType == "unstructured"; info[0].set_string("Topology is not unstructured."); } if(rval) { std::string elemShape = - m_bpNodeInt.fetch("topologies")[m_bpTopo]["elements"]["shape"].as_string(); + m_bp_state->m_internal_node.fetch("topologies")[m_bp_state->m_topology_name]["elements"] + ["shape"] + .as_string(); rval = (elemShape == "hex") || (elemShape == "quad"); info[0].set_string("Topology elements are not hex or quad."); } @@ -298,7 +308,7 @@ bool Shaper::verifyInputMesh(std::string& whyBad) const #endif #if defined(AXOM_USE_MFEM) - if(m_dc != nullptr) + if(getDC() != nullptr) { // No specific requirements for MFEM mesh. } diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index 74829588d2..6cd3b12e55 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -26,6 +26,7 @@ #include "axom/klee.hpp" #include "axom/mint.hpp" #include "axom/quest/DiscreteShape.hpp" +#include "axom/quest/detail/shaping/shaping_helpers.hpp" #include "axom/core/execution/runtime_policy.hpp" #if defined(AXOM_USE_MFEM) @@ -50,25 +51,6 @@ class Shaper { public: using RuntimePolicy = axom::runtime_policy::Policy; -#if defined(AXOM_USE_MFEM) - struct MFEMState - { - // For mesh represented as MFEMSidreDataCollection - sidre::MFEMSidreDataCollection* m_dc {nullptr}; - }; -#endif -#if defined(AXOM_USE_CONDUIT) - struct BlueprintState - { - //! @brief Version of the mesh for computations. - axom::sidre::Group* m_bpGrp {nullptr}; - std::string m_bpTopo; - //! @brief Mesh in an external Node, when provided as a Node. - conduit::Node* m_bpNodeExt {nullptr}; - //! @brief Initial copy of mesh in an internal Node storage. - conduit::Node m_bpNodeInt; - }; -#endif #if defined(AXOM_USE_MFEM) /// @brief Construct Shaper to operate on an MFEM mesh. @@ -144,8 +126,14 @@ class Shaper bool isVerbose() const { return m_verboseOutput; } #ifdef AXOM_USE_MFEM - sidre::MFEMSidreDataCollection* getDC() { return m_mfem_state->m_dc; } - const sidre::MFEMSidreDataCollection* getDC() const { return m_mfem_state->m_dc; } + sidre::MFEMSidreDataCollection* getDC() + { + return m_mfem_state != nullptr ? m_mfem_state->m_dc : nullptr; + } + const sidre::MFEMSidreDataCollection* getDC() const + { + return m_mfem_state != nullptr ? m_mfem_state->m_dc : nullptr; + } #endif /*! @@ -253,15 +241,15 @@ class Shaper int getRank() const; #if defined(AXOM_USE_MFEM) - virtual std::unique_ptr createMFEMState() + virtual std::unique_ptr createMFEMState() { - return std::make_unique(); + return std::make_unique(); } #endif #if defined(AXOM_USE_CONDUIT) - virtual std::unique_ptr createBlueprintState() + virtual std::unique_ptr createBlueprintState() { - return std::make_unique(); + return std::make_unique(); } #endif @@ -278,13 +266,13 @@ class Shaper std::string m_prefixPath; #if defined(AXOM_USE_MFEM) - std::unique_ptr m_mfem_state; + std::unique_ptr m_mfem_state; #endif #if defined(AXOM_USE_CONDUIT) - std::unique_ptr m_bp_state; + std::unique_ptr m_bp_state; #endif - //! @brief Number of cells in computational mesh (m_dc or m_bpGrp). + //! @brief Number of cells in the computational mesh. axom::IndexType m_cellCount; std::shared_ptr m_surfaceMesh; diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index a2f87ef1bd..534e1facf8 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -114,8 +114,7 @@ class InOutSampler * \note \a ToDim must be equal to \a DIM, the dimension of the spatial index */ template - std::enable_if_t sampleInOutField(mfem::DataCollection* dc, - shaping::QFunctionCollection& inoutQFuncs, + std::enable_if_t sampleInOutField(shaping::SamplingMFEMState& mfemState, int sampleRes[3], int quadratureType, PointProjector projector = {}) @@ -125,8 +124,7 @@ class InOutSampler const InOutOctreeType* octree = m_octree; auto checkInside = [=](const PointType& pt) -> bool { return octree->within(pt); }; shaping::sampleInOutField(m_shapeName, - dc, - inoutQFuncs, + mfemState, sampleRes, quadratureType, checkInside, @@ -138,8 +136,7 @@ class InOutSampler * defined to support various callback specializations for the \a PointProjector. */ template - std::enable_if_t sampleInOutField(mfem::DataCollection*, - shaping::QFunctionCollection&, + std::enable_if_t sampleInOutField(shaping::SamplingMFEMState&, int AXOM_UNUSED_PARAM(sampleRes)[3], int AXOM_UNUSED_PARAM(quadratureType), PointProjector) @@ -155,7 +152,7 @@ class InOutSampler */ template std::enable_if_t computeVolumeFractionsBaseline( - mfem::DataCollection* dc, + shaping::SamplingMFEMState& mfemState, int outputOrder, PointProjector projector = {}) { @@ -163,7 +160,7 @@ class InOutSampler const InOutOctreeType* octree = m_octree; auto checkInside = [=](const PointType& pt) -> bool { return octree->within(pt); }; shaping::computeVolumeFractionsBaseline(m_shapeName, - dc, + mfemState, outputOrder, checkInside, projector); @@ -175,7 +172,7 @@ class InOutSampler */ template std::enable_if_t computeVolumeFractionsBaseline( - mfem::DataCollection* AXOM_UNUSED_PARAM(dc), + shaping::SamplingMFEMState& AXOM_UNUSED_PARAM(mfemState), int AXOM_UNUSED_PARAM(outputOrder), PointProjector AXOM_UNUSED_PARAM(projector)) { @@ -184,6 +181,21 @@ class InOutSampler "Projector's return dimension (ToDim), must match class dimension (DIM)"); } +#if defined(AXOM_USE_CONDUIT) + template + void sampleInOutField(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), + PointProjector AXOM_UNUSED_PARAM(projector) = {}) + { } + + template + void computeVolumeFractionsBaseline(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), + int AXOM_UNUSED_PARAM(outputOrder), + PointProjector AXOM_UNUSED_PARAM(projector) = {}) + { } +#endif + private: DISABLE_COPY_AND_ASSIGNMENT(InOutSampler); DISABLE_MOVE_AND_ASSIGNMENT(InOutSampler); diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index 4889cd0760..679934bf28 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -177,8 +177,7 @@ class PrimitiveSampler * \note \a ToDim must be equal to \a DIM, the dimension of the spatial index */ template - std::enable_if_t sampleInOutField(mfem::DataCollection* dc, - shaping::QFunctionCollection& inoutQFuncs, + std::enable_if_t sampleInOutField(shaping::SamplingMFEMState& mfemState, int sampleRes[3], int quadratureType, PointProjector projector = {}) @@ -190,16 +189,15 @@ class PrimitiveSampler SLIC_ERROR_IF(FromDim != ToDim && !projector, "A projector callback function is required when FromDim != ToDim"); - auto* mesh = dc->GetMesh(); + auto* mesh = mfemState.m_dc->GetMesh(); SLIC_ASSERT(mesh != nullptr); //const int NE = mesh->GetNE(); //const int dim = mesh->Dimension(); - // Generate a Quadrature Function with the geometric positions, if not already available - if(!inoutQFuncs.Has("positions")) - { - shaping::generatePositionsQFunction(mesh, inoutQFuncs, sampleRes, quadratureType); - } + AXOM_UNUSED_VAR(sampleRes); + AXOM_UNUSED_VAR(quadratureType); + auto& inoutQFuncs = mfemState.m_inoutShapeQFuncs; + SLIC_ASSERT(inoutQFuncs.Has("positions")); // Access the positions QFunc and associated QuadratureSpace mfem::QuadratureFunction* pos_coef = inoutQFuncs.Get("positions"); @@ -291,8 +289,7 @@ class PrimitiveSampler * defined to support various callback specializations for the \a PointProjector. */ template - std::enable_if_t sampleInOutField(mfem::DataCollection*, - shaping::QFunctionCollection&, + std::enable_if_t sampleInOutField(shaping::SamplingMFEMState&, int AXOM_UNUSED_PARAM(sampleRes)[3], int AXOM_UNUSED_PARAM(quadratureType), PointProjector) @@ -308,7 +305,7 @@ class PrimitiveSampler * \warning Not yet implemented */ template - void computeVolumeFractionsBaseline(mfem::DataCollection* AXOM_UNUSED_PARAM(dc), + void computeVolumeFractionsBaseline(shaping::SamplingMFEMState& AXOM_UNUSED_PARAM(mfemState), int AXOM_UNUSED_PARAM(outputOrder), PointProjector AXOM_UNUSED_PARAM(projector)) { @@ -316,6 +313,21 @@ class PrimitiveSampler SLIC_WARNING_ROOT("computeVolumeFractionsBaseline() not implemented yet"); } +#if defined(AXOM_USE_CONDUIT) + template + void sampleInOutField(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), + PointProjector AXOM_UNUSED_PARAM(projector) = {}) + { } + + template + void computeVolumeFractionsBaseline(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), + int AXOM_UNUSED_PARAM(outputOrder), + PointProjector AXOM_UNUSED_PARAM(projector) = {}) + { } +#endif + private: DISABLE_COPY_AND_ASSIGNMENT(PrimitiveSampler); DISABLE_MOVE_AND_ASSIGNMENT(PrimitiveSampler); diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index 5a1aa27990..1809367ef5 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -146,8 +146,7 @@ class WindingNumberSampler * \note \a ToDim must be equal to \a DIM, the dimension of the spatial index */ template - std::enable_if_t sampleInOutField(mfem::DataCollection* dc, - shaping::QFunctionCollection& inoutQFuncs, + std::enable_if_t sampleInOutField(shaping::SamplingMFEMState& mfemState, int sampleRes[3], int quadratureType, PointProjector projector = {}) @@ -163,16 +162,15 @@ class WindingNumberSampler SLIC_ERROR_IF(FromDim != ToDim && !projector, "A projector callback function is required when FromDim != ToDim"); - auto* mesh = dc->GetMesh(); + auto* mesh = mfemState.m_dc->GetMesh(); SLIC_ASSERT(mesh != nullptr); const int NE = mesh->GetNE(); const int dim = mesh->Dimension(); - // Generate a Quadrature Function with the geometric positions, if not already available - if(!inoutQFuncs.Has("positions")) - { - shaping::generatePositionsQFunction(mesh, inoutQFuncs, sampleRes, quadratureType); - } + AXOM_UNUSED_VAR(sampleRes); + AXOM_UNUSED_VAR(quadratureType); + auto& inoutQFuncs = mfemState.m_inoutShapeQFuncs; + SLIC_ASSERT(inoutQFuncs.Has("positions")); // Access the positions QFunc and associated QuadratureSpace mfem::QuadratureFunction* pos_coef = inoutQFuncs.Get("positions"); @@ -264,8 +262,7 @@ class WindingNumberSampler * defined to support various callback specializations for the \a PointProjector. */ template - std::enable_if_t sampleInOutField(mfem::DataCollection*, - shaping::QFunctionCollection&, + std::enable_if_t sampleInOutField(shaping::SamplingMFEMState&, int AXOM_UNUSED_PARAM(sampleRes)[3], int AXOM_UNUSED_PARAM(quadratureType), PointProjector) @@ -281,7 +278,7 @@ class WindingNumberSampler */ template std::enable_if_t computeVolumeFractionsBaseline( - mfem::DataCollection* dc, + shaping::SamplingMFEMState& mfemState, int outputOrder, PointProjector projector = {}) { @@ -299,7 +296,7 @@ class WindingNumberSampler return inside; }; shaping::computeVolumeFractionsBaseline(m_shapeName, - dc, + mfemState, outputOrder, checkInside, projector); @@ -311,7 +308,7 @@ class WindingNumberSampler */ template std::enable_if_t computeVolumeFractionsBaseline( - mfem::DataCollection* AXOM_UNUSED_PARAM(dc), + shaping::SamplingMFEMState& AXOM_UNUSED_PARAM(mfemState), int AXOM_UNUSED_PARAM(outputOrder), PointProjector AXOM_UNUSED_PARAM(projector)) { @@ -320,6 +317,21 @@ class WindingNumberSampler "Projector's return dimension (ToDim), must match class dimension (DIM)"); } +#if defined(AXOM_USE_CONDUIT) + template + void sampleInOutField(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), + PointProjector AXOM_UNUSED_PARAM(projector) = {}) + { } + + template + void computeVolumeFractionsBaseline(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), + int AXOM_UNUSED_PARAM(outputOrder), + PointProjector AXOM_UNUSED_PARAM(projector) = {}) + { } +#endif + private: DISABLE_COPY_AND_ASSIGNMENT(WindingNumberSampler); DISABLE_MOVE_AND_ASSIGNMENT(WindingNumberSampler); diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 7dbe7fe0a4..e6821a83d5 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -335,6 +335,23 @@ void generatePositionsQFunction(mfem::Mesh* mesh, inoutQFuncs.Register("positions", pos_coef, true); } +void generatePositionsQFunction(SamplingMFEMState& mfemState, + int sampleResolution[3], + int quadratureType) +{ + generatePositionsQFunction(mfemState.m_dc->GetMesh(), + mfemState.m_inoutShapeQFuncs, + sampleResolution, + quadratureType); +} + +#if defined(AXOM_USE_CONDUIT) +void generatePositionsQFunction(BlueprintState& AXOM_UNUSED_PARAM(bpState), + int AXOM_UNUSED_PARAM(sampleResolution)[3], + int AXOM_UNUSED_PARAM(quadratureType)) +{ } +#endif + void FCT_correct(const double* M, // Mass matrix const int s, // num dofs const double* m, // rhs (incorporating the inout samples) diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 4d4d1f56a2..1beb1133e1 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -16,11 +16,15 @@ #include "axom/config.hpp" #include "axom/core.hpp" #include "axom/primal.hpp" +#include "axom/sidre.hpp" #if defined(AXOM_USE_MFEM) #include "mfem.hpp" #include "mfem/linalg/dtensor.hpp" #endif +#if defined(AXOM_USE_CONDUIT) + #include "conduit_node.hpp" +#endif namespace axom { @@ -150,6 +154,55 @@ using QFunctionCollection = mfem::NamedFieldsMap; using DenseTensorCollection = mfem::NamedFieldsMap; using MFEMArrayCollection = mfem::NamedFieldsMap>; +struct MFEMState +{ + virtual ~MFEMState() = default; + + // For mesh represented as MFEMSidreDataCollection + sidre::MFEMSidreDataCollection* m_dc {nullptr}; +}; + +struct SamplingMFEMState : public MFEMState +{ + ~SamplingMFEMState() override + { + m_inoutShapeQFuncs.DeleteData(true); + m_inoutShapeQFuncs.clear(); + + m_inoutMaterialQFuncs.DeleteData(true); + m_inoutMaterialQFuncs.clear(); + + m_inoutTensors.DeleteData(true); + m_inoutTensors.clear(); + + m_inoutArrays.DeleteData(true); + m_inoutArrays.clear(); + } + + QFunctionCollection m_inoutShapeQFuncs; + QFunctionCollection m_inoutMaterialQFuncs; + DenseTensorCollection m_inoutTensors; + MFEMArrayCollection m_inoutArrays; +}; +#endif + +#if defined(AXOM_USE_CONDUIT) +struct BlueprintState +{ + virtual ~BlueprintState() = default; + + //! @brief Version of the mesh for computations. + axom::sidre::Group* m_group_ptr {nullptr}; + std::string m_topology_name; + //! @brief Mesh in an external Node, when provided as a Node. + conduit::Node* m_external_node_ptr {nullptr}; + //! @brief Internal Node representation used for blueprint operations. + conduit::Node m_internal_node; +}; +#endif + +#if defined(AXOM_USE_MFEM) + enum class VolFracSampling : int { SAMPLE_AT_DOFS, @@ -213,6 +266,22 @@ void generatePositionsQFunction(mfem::Mesh* mesh, int sampleResolution[3], int quadratureType); +/** + * \brief Generates a "position" quadrature function for the supplied MFEM state. + */ +void generatePositionsQFunction(SamplingMFEMState& mfemState, + int sampleResolution[3], + int quadratureType); + +#if defined(AXOM_USE_CONDUIT) +/** + * \brief Placeholder overload for future Blueprint-backed position generation. + */ +void generatePositionsQFunction(BlueprintState& bpState, + int sampleResolution[3], + int quadratureType); +#endif + /** * Implements flux-corrected transport (FCT) to correct the solution obtained * when converting from inout samples (ones and zeros) to a grid function @@ -268,10 +337,9 @@ void computeVolumeFractionsIdentity(mfem::DataCollection* dc, */ template void sampleInOutField(const std::string shapeName, - mfem::DataCollection* dc, - shaping::QFunctionCollection& inoutQFuncs, - int sampleRes[3], - int quadratureType, + shaping::SamplingMFEMState& mfemState, + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), InsideFunc&& checkInside, PointProjector projector = {}) { @@ -282,16 +350,13 @@ void sampleInOutField(const std::string shapeName, SLIC_ERROR_IF(FromDim != ToDim && !projector, "A projector callback function is required when FromDim != ToDim"); - auto* mesh = dc->GetMesh(); + auto* mesh = mfemState.m_dc->GetMesh(); SLIC_ASSERT(mesh != nullptr); const int NE = mesh->GetNE(); const int dim = mesh->Dimension(); - // Generate a Quadrature Function with the geometric positions, if not already available - if(!inoutQFuncs.Has("positions")) - { - shaping::generatePositionsQFunction(mesh, inoutQFuncs, sampleRes, quadratureType); - } + auto& inoutQFuncs = mfemState.m_inoutShapeQFuncs; + SLIC_ASSERT(inoutQFuncs.Has("positions")); // Access the positions QFunc and associated QuadratureSpace mfem::QuadratureFunction* pos_coef = inoutQFuncs.Get("positions"); @@ -366,7 +431,7 @@ void sampleInOutField(const std::string shapeName, */ template void computeVolumeFractionsBaseline(const std::string& shapeName, - mfem::DataCollection* dc, + shaping::SamplingMFEMState& mfemState, int outputOrder, InsideFunc&& checkInside, PointProjector projector = {}) @@ -376,6 +441,7 @@ void computeVolumeFractionsBaseline(const std::string& shapeName, AXOM_ANNOTATE_SCOPE("computeVolumeFractionsBaseline"); // Step 1 -- generate a QField w/ the spatial coordinates + mfem::DataCollection* dc = mfemState.m_dc; mfem::Mesh* mesh = dc->GetMesh(); const int NE = mesh->GetNE(); const int dim = mesh->Dimension(); From a48c69f96c64b1b84fb6bd4f66a82232ebf0adc7 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 May 2026 15:23:52 -0700 Subject: [PATCH 03/72] Added 2 new quadrature types. --- src/axom/core/numerics/quadrature.cpp | 149 ++++++++++++++++++-- src/axom/core/numerics/quadrature.hpp | 59 ++++++++ src/axom/core/tests/numerics_quadrature.hpp | 98 ++++++++++++- 3 files changed, 294 insertions(+), 12 deletions(-) diff --git a/src/axom/core/numerics/quadrature.cpp b/src/axom/core/numerics/quadrature.cpp index 709992ed95..4e460fdfc0 100644 --- a/src/axom/core/numerics/quadrature.cpp +++ b/src/axom/core/numerics/quadrature.cpp @@ -14,6 +14,7 @@ #include "axom/config.hpp" #include +#include #include #include @@ -22,20 +23,92 @@ namespace axom namespace numerics { +void compute_gauss_legendre_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID); + namespace { -struct GaussLegendreRuleStorage +struct RuleStorage { axom::Array nodes; axom::Array weights; }; -std::uint64_t make_gauss_legendre_key(int npts, int allocatorID) +std::uint64_t make_rule_key(int npts, int allocatorID) { const auto n = static_cast(static_cast(npts)); const auto a = static_cast(static_cast(allocatorID)); return (a << 32) | n; } + +template +RuleStorage& get_cached_rule_storage(int npts, + int allocatorID, + axom::FlatMap& rule_library, + std::mutex& rule_library_mutex, + ComputeRuleData&& computeRuleData) +{ + const std::lock_guard lock(rule_library_mutex); + const std::uint64_t key = make_rule_key(npts, allocatorID); + + auto [it, inserted] = rule_library.try_emplace(key); + if(inserted) + { + computeRuleData(npts, it->second.nodes, it->second.weights, allocatorID); + } + + return it->second; +} + +void compute_interpolatory_weights(const axom::Array& nodes, + axom::Array& weights, + int allocatorID) +{ + const int npts = nodes.size(); + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + + weights = axom::Array(npts, npts, allocatorID); + + if(npts == 1) + { + weights[0] = 1.0; + return; + } + + if(npts == 2) + { + weights[0] = 0.5; + weights[1] = 0.5; + return; + } + + axom::Array glNodes; + axom::Array glWeights; + compute_gauss_legendre_data((npts + 1) / 2, glNodes, glWeights, allocatorID); + + for(int j = 0; j < npts; ++j) + { + double wj = 0.0; + for(int q = 0; q < glNodes.size(); ++q) + { + const double x = glNodes[q]; + double basis = 1.0; + for(int k = 0; k < npts; ++k) + { + if(k == j) + { + continue; + } + + basis *= (x - nodes[k]) / (nodes[j] - nodes[k]); + } + wj += glWeights[q] * basis; + } + weights[j] = wj; + } +} } // namespace /*! @@ -148,6 +221,46 @@ void compute_gauss_legendre_data(int npts, } } +void compute_open_uniform_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + + nodes = axom::Array(npts, npts, allocatorID); + for(int i = 0; i < npts; ++i) + { + nodes[i] = static_cast(i + 1) / static_cast(npts + 1); + } + + compute_interpolatory_weights(nodes, weights, allocatorID); +} + +void compute_closed_uniform_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + + nodes = axom::Array(npts, npts, allocatorID); + if(npts == 1) + { + nodes[0] = 0.5; + weights = axom::Array(1, 1, allocatorID); + weights[0] = 1.0; + return; + } + + for(int i = 0; i < npts; ++i) + { + nodes[i] = static_cast(i) / static_cast(npts - 1); + } + + compute_interpolatory_weights(nodes, weights, allocatorID); +} + /*! * \brief Computes or accesses a precomputed 1D quadrature rule of Gauss-Legendre points * @@ -168,19 +281,33 @@ QuadratureRule get_gauss_legendre(int npts, int allocatorID) assert("Quadrature rules must have >= 1 point" && (npts >= 1)); // Store cached rules keyed by (npts, allocatorID). - static axom::FlatMap rule_library(64); + static axom::FlatMap rule_library(64); static std::mutex rule_library_mutex; - const std::lock_guard lock(rule_library_mutex); + auto& storage = get_cached_rule_storage( + npts, allocatorID, rule_library, rule_library_mutex, compute_gauss_legendre_data); + return QuadratureRule {storage.nodes.view(), storage.weights.view()}; +} + +QuadratureRule get_open_uniform(int npts, int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); - const std::uint64_t key = make_gauss_legendre_key(npts, allocatorID); + static axom::FlatMap rule_library(64); + static std::mutex rule_library_mutex; + auto& storage = get_cached_rule_storage( + npts, allocatorID, rule_library, rule_library_mutex, compute_open_uniform_data); + return QuadratureRule {storage.nodes.view(), storage.weights.view()}; +} - auto [it, inserted] = rule_library.try_emplace(key); - if(inserted) - { - compute_gauss_legendre_data(npts, it->second.nodes, it->second.weights, allocatorID); - } +QuadratureRule get_closed_uniform(int npts, int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); - return QuadratureRule {it->second.nodes.view(), it->second.weights.view()}; + static axom::FlatMap rule_library(64); + static std::mutex rule_library_mutex; + auto& storage = get_cached_rule_storage( + npts, allocatorID, rule_library, rule_library_mutex, compute_closed_uniform_data); + return QuadratureRule {storage.nodes.view(), storage.weights.view()}; } } /* end namespace numerics */ diff --git a/src/axom/core/numerics/quadrature.hpp b/src/axom/core/numerics/quadrature.hpp index b8db612e11..7ac7b644b0 100644 --- a/src/axom/core/numerics/quadrature.hpp +++ b/src/axom/core/numerics/quadrature.hpp @@ -30,6 +30,8 @@ class QuadratureRule { // Define friend functions so rules can only be created via get_rule() methods friend QuadratureRule get_gauss_legendre(int, int); + friend QuadratureRule get_open_uniform(int, int); + friend QuadratureRule get_closed_uniform(int, int); public: //! \brief Accessor for the full array of quadrature nodes @@ -97,6 +99,63 @@ void compute_gauss_legendre_data(int npts, */ QuadratureRule get_gauss_legendre(int npts, int allocatorID = axom::getDefaultAllocatorID()); +/*! + * \brief Computes a 1D quadrature rule of open uniform Newton-Cotes points. + * + * \param [in] npts The number of points in the rule + * \param [out] nodes The array of 1D nodes + * \param [out] weights The array of weights + * + * The points are placed at `x_i = (i + 1) / (npts + 1)` for `i = 0, ..., npts - 1`. + * This matches MFEM's `QuadratureFunctions1D::OpenUniform`. + * + * The rule order matches MFEM's convention: `npts - 1 + npts % 2`. + */ +void compute_open_uniform_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID = axom::getDefaultAllocatorID()); + +/*! + * \brief Computes or accesses a precomputed 1D quadrature rule of open uniform + * Newton-Cotes points. + * + * \param [in] npts The number of points in the rule + * + * \return The `QuadratureRule` object which contains axom::ArrayView's + * of stored nodes and weights + */ +QuadratureRule get_open_uniform(int npts, int allocatorID = axom::getDefaultAllocatorID()); + +/*! + * \brief Computes a 1D quadrature rule of closed uniform Newton-Cotes points. + * + * \param [in] npts The number of points in the rule + * \param [out] nodes The array of 1D nodes + * \param [out] weights The array of weights + * + * For `npts > 1`, the points are placed at `x_i = i / (npts - 1)` for + * `i = 0, ..., npts - 1`. For `npts == 1`, the rule is the midpoint rule at + * `x = 0.5` with weight `1.0`, matching MFEM's `ClosedUniform`. + * + * The rule order matches MFEM's convention: `npts - 1 + npts % 2`. + */ +void compute_closed_uniform_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID = axom::getDefaultAllocatorID()); + +/*! + * \brief Computes or accesses a precomputed 1D quadrature rule of closed + * uniform Newton-Cotes points. + * + * \param [in] npts The number of points in the rule + * + * \return The `QuadratureRule` object which contains axom::ArrayView's + * of stored nodes and weights + */ +QuadratureRule get_closed_uniform(int npts, int allocatorID = axom::getDefaultAllocatorID()); + } /* end namespace numerics */ } /* end namespace axom */ diff --git a/src/axom/core/tests/numerics_quadrature.hpp b/src/axom/core/tests/numerics_quadrature.hpp index 163e83e9a1..539300d25d 100644 --- a/src/axom/core/tests/numerics_quadrature.hpp +++ b/src/axom/core/tests/numerics_quadrature.hpp @@ -7,6 +7,7 @@ #include "gtest/gtest.h" #include "axom/config.hpp" +#include "axom/core/Array.hpp" #include "axom/core/numerics/quadrature.hpp" #include "axom/core/utilities/Utilities.hpp" @@ -135,4 +136,99 @@ TEST(numerics_quadrature, get_nodes_cuda) { test_device_quadrature>::test(); } -#endif \ No newline at end of file +#endif + +namespace +{ +template +void check_polynomial_exactness(RuleGetter&& getRule, int maxNpts) +{ + for(int npts = 1; npts <= maxNpts; ++npts) + { + const auto rule = getRule(npts); + const int exactDegree = npts - 1 + npts % 2; + axom::Array coeffs(exactDegree + 1, exactDegree + 1); + + for(int j = 0; j <= exactDegree; ++j) + { + coeffs[j] = axom::utilities::random_real(-1.0, 1.0, 1000 * npts + j); + } + + double analyticResult = 0.0; + for(int j = 0; j <= exactDegree; ++j) + { + analyticResult += coeffs[j] / (j + 1); + } + + auto evalPolynomial = [&coeffs, exactDegree](double x) { + double result = coeffs[exactDegree]; + for(int i = exactDegree - 1; i >= 0; --i) + { + result = result * x + coeffs[i]; + } + return result; + }; + + double quadratureResult = 0.0; + double weightSum = 0.0; + for(int j = 0; j < npts; ++j) + { + quadratureResult += rule.weight(j) * evalPolynomial(rule.node(j)); + weightSum += rule.weight(j); + if(j > 0) + { + EXPECT_GT(rule.node(j), rule.node(j - 1)); + } + } + + EXPECT_NEAR(quadratureResult, analyticResult, 1e-12); + EXPECT_NEAR(weightSum, 1.0, 1e-12); + } +} +} // namespace + +TEST(numerics_quadrature, open_uniform_small_rules) +{ + auto rule = axom::numerics::get_open_uniform(1); + ASSERT_EQ(rule.getNumPoints(), 1); + EXPECT_DOUBLE_EQ(rule.node(0), 0.5); + EXPECT_DOUBLE_EQ(rule.weight(0), 1.0); + + rule = axom::numerics::get_open_uniform(3); + ASSERT_EQ(rule.getNumPoints(), 3); + EXPECT_DOUBLE_EQ(rule.node(0), 0.25); + EXPECT_DOUBLE_EQ(rule.node(1), 0.5); + EXPECT_DOUBLE_EQ(rule.node(2), 0.75); + EXPECT_DOUBLE_EQ(rule.weight(0), 2.0 / 3.0); + EXPECT_DOUBLE_EQ(rule.weight(1), -1.0 / 3.0); + EXPECT_DOUBLE_EQ(rule.weight(2), 2.0 / 3.0); +} + +TEST(numerics_quadrature, closed_uniform_small_rules) +{ + auto rule = axom::numerics::get_closed_uniform(1); + ASSERT_EQ(rule.getNumPoints(), 1); + EXPECT_DOUBLE_EQ(rule.node(0), 0.5); + EXPECT_DOUBLE_EQ(rule.weight(0), 1.0); + + rule = axom::numerics::get_closed_uniform(3); + ASSERT_EQ(rule.getNumPoints(), 3); + EXPECT_DOUBLE_EQ(rule.node(0), 0.0); + EXPECT_DOUBLE_EQ(rule.node(1), 0.5); + EXPECT_DOUBLE_EQ(rule.node(2), 1.0); + EXPECT_DOUBLE_EQ(rule.weight(0), 1.0 / 6.0); + EXPECT_DOUBLE_EQ(rule.weight(1), 4.0 / 6.0); + EXPECT_DOUBLE_EQ(rule.weight(2), 1.0 / 6.0); +} + +TEST(numerics_quadrature, open_uniform_exactness) +{ + check_polynomial_exactness( + [](int npts) { return axom::numerics::get_open_uniform(npts); }, 10); +} + +TEST(numerics_quadrature, closed_uniform_exactness) +{ + check_polynomial_exactness( + [](int npts) { return axom::numerics::get_closed_uniform(npts); }, 10); +} From b56f02b9a99880ff3697349456f7e6fc381e3cc0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 May 2026 16:39:52 -0700 Subject: [PATCH 04/72] Incremental progress toward making Blueprint quadrature point mesh. --- src/axom/quest/Shaper.cpp | 2 + .../quest/detail/shaping/shaping_helpers.cpp | 175 +++++++++++++++++- .../quest/detail/shaping/shaping_helpers.hpp | 4 +- 3 files changed, 176 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index f250b477a3..8f06231dd3 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -76,6 +76,7 @@ Shaper::Shaper(RuntimePolicy execPolicy, { m_bp_state = createBlueprintState(); m_bp_state->m_group_ptr = bpGrp; + m_bp_state->m_allocator_id = m_allocatorId; m_bp_state->m_topology_name = topo.empty() ? bpGrp->getGroup("topologies")->getGroupName(0) : topo; m_bp_state->m_external_node_ptr = nullptr; @@ -116,6 +117,7 @@ Shaper::Shaper(RuntimePolicy execPolicy, m_bp_state = createBlueprintState(); m_bp_state->m_group_ptr = nullptr; + m_bp_state->m_allocator_id = m_allocatorId; m_bp_state->m_topology_name = topo.empty() ? bpNode.fetch_existing("topologies").child(0).name() : topo; m_bp_state->m_external_node_ptr = &bpNode; diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index e6821a83d5..72daa17eda 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -5,9 +5,11 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "shaping_helpers.hpp" +#include "GenerateQuadratureMesh.hpp" #include "axom/config.hpp" #include "axom/core.hpp" +#include "axom/core/numerics/quadrature.hpp" #include "axom/slic.hpp" #include "axom/sidre.hpp" @@ -15,6 +17,11 @@ #include +#if defined(AXOM_USE_CONDUIT) + #include "axom/bump/views/dispatch_coordset.hpp" + #include "axom/bump/views/dispatch_unstructured_topology.hpp" +#endif + #if defined(AXOM_USE_MFEM) #include "mfem/linalg/dtensor.hpp" #endif @@ -25,6 +32,71 @@ namespace quest { namespace shaping { +#if defined(AXOM_USE_CONDUIT) +namespace +{ + +constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; +constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; +constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; + +numerics::QuadratureRule getBlueprintQuadratureRule(int npts, int quadratureType, int allocatorID) +{ + SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); + +#if defined(AXOM_USE_MFEM) + switch(quadratureType) + { + case mfem::Quadrature1D::Invalid: + case mfem::Quadrature1D::GaussLegendre: + return numerics::get_gauss_legendre(npts, allocatorID); + case mfem::Quadrature1D::OpenUniform: + return numerics::get_open_uniform(npts, allocatorID); + case mfem::Quadrature1D::ClosedUniform: + return numerics::get_closed_uniform(npts, allocatorID); + default: + SLIC_ERROR(axom::fmt::format( + "Quadrature type {} is not supported for Blueprint quadrature meshes.", quadratureType)); + return numerics::get_gauss_legendre(npts, allocatorID); + } +#else + AXOM_UNUSED_VAR(quadratureType); + return numerics::get_gauss_legendre(npts, allocatorID); +#endif +} + +template +void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, + const conduit::Node& coordsetNode, + const CoordsetView& coordsetView, + int allocatorID, + const numerics::QuadratureRule& ruleX, + const numerics::QuadratureRule& ruleY, + const numerics::QuadratureRule& ruleZ, + conduit::Node& meshNode) +{ + using namespace axom::bump::views; + constexpr int SupportedShapes = encode_shapes(Quad_ShapeID, Hex_ShapeID); + + dispatch_unstructured_topology(topoNode, [&](const auto&, auto topoView) { + GenerateQuadratureMesh generator(topoView, + coordsetView); + generator.setAllocatorID(allocatorID); + generator.execute(topoNode, + coordsetNode, + QUADRATURE_TOPOLOGY_NAME, + QUADRATURE_COORDSET_NAME, + ORIGINAL_ELEMENTS_FIELD_NAME, + ruleX, + ruleY, + ruleZ, + meshNode); + }); +} + +} // namespace +#endif + #if defined(AXOM_USE_MFEM) namespace @@ -346,10 +418,105 @@ void generatePositionsQFunction(SamplingMFEMState& mfemState, } #if defined(AXOM_USE_CONDUIT) -void generatePositionsQFunction(BlueprintState& AXOM_UNUSED_PARAM(bpState), - int AXOM_UNUSED_PARAM(sampleResolution)[3], - int AXOM_UNUSED_PARAM(quadratureType)) -{ } +void generatePositionsQFunction(BlueprintState& bpState, + int sampleResolution[3], + int quadratureType) +{ + if(bpState.m_internal_node.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) + { + return; + } + + const conduit::Node& topoNode = + bpState.m_internal_node.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); + const std::string topoType = topoNode.fetch_existing("type").as_string(); + SLIC_ERROR_IF(topoType != "unstructured", + axom::fmt::format("Unsupported Blueprint topology type '{}' for quadrature mesh generation.", + topoType)); + + const std::string shape = topoNode.fetch_existing("elements/shape").as_string(); + SLIC_ERROR_IF(shape != "quad" && shape != "hex", + axom::fmt::format("Unsupported Blueprint element shape '{}' for quadrature mesh generation.", + shape)); + + const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); + const conduit::Node& coordsetNode = + bpState.m_internal_node.fetch_existing("coordsets").fetch_existing(coordsetName); + const std::string coordsetType = coordsetNode.fetch_existing("type").as_string(); + SLIC_ERROR_IF(coordsetType != "explicit", + axom::fmt::format("Unsupported Blueprint coordset type '{}' for quadrature mesh generation.", + coordsetType)); + + int allocatorID = bpState.m_allocator_id; + if(!axom::execution_space::usesAllocId(allocatorID) && + !axom::execution_space::usesAllocId(allocatorID) +#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + && !axom::execution_space::usesAllocId(allocatorID) +#endif +#if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + && !axom::execution_space::usesAllocId(allocatorID) +#endif + ) + { + allocatorID = axom::execution_space::allocatorID(); + } + + auto ruleX = getBlueprintQuadratureRule(sampleResolution[0], quadratureType, allocatorID); + auto ruleY = getBlueprintQuadratureRule(sampleResolution[1], quadratureType, allocatorID); + auto ruleZ = getBlueprintQuadratureRule(sampleResolution[2], quadratureType, allocatorID); + + axom::bump::views::dispatch_explicit_coordset(coordsetNode, [&](auto coordsetView) { +#if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + if(axom::execution_space::usesAllocId(allocatorID)) + { + buildBlueprintQuadratureMesh(topoNode, + coordsetNode, + coordsetView, + allocatorID, + ruleX, + ruleY, + ruleZ, + bpState.m_internal_node); + return; + } +#endif +#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + if(axom::execution_space::usesAllocId(allocatorID)) + { + buildBlueprintQuadratureMesh(topoNode, + coordsetNode, + coordsetView, + allocatorID, + ruleX, + ruleY, + ruleZ, + bpState.m_internal_node); + return; + } +#endif + if(axom::execution_space::usesAllocId(allocatorID)) + { + buildBlueprintQuadratureMesh(topoNode, + coordsetNode, + coordsetView, + allocatorID, + ruleX, + ruleY, + ruleZ, + bpState.m_internal_node); + return; + } + + buildBlueprintQuadratureMesh(topoNode, + coordsetNode, + coordsetView, + allocatorID, + ruleX, + ruleY, + ruleZ, + bpState.m_internal_node); + }); +} #endif void FCT_correct(const double* M, // Mass matrix diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 1beb1133e1..72ed77e41d 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -193,6 +193,7 @@ struct BlueprintState //! @brief Version of the mesh for computations. axom::sidre::Group* m_group_ptr {nullptr}; + int m_allocator_id {axom::getDefaultAllocatorID()}; std::string m_topology_name; //! @brief Mesh in an external Node, when provided as a Node. conduit::Node* m_external_node_ptr {nullptr}; @@ -275,7 +276,8 @@ void generatePositionsQFunction(SamplingMFEMState& mfemState, #if defined(AXOM_USE_CONDUIT) /** - * \brief Placeholder overload for future Blueprint-backed position generation. + * \brief Generates a derived Blueprint quadrature point mesh for the supplied + * Blueprint state. */ void generatePositionsQFunction(BlueprintState& bpState, int sampleResolution[3], From 69e930e0fa068324d466e7fc080a5f7a0c7c3d00 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 May 2026 18:33:47 -0700 Subject: [PATCH 05/72] Quadrature improvements, added GenerateQuadratureMesh for Blueprint --- src/axom/core/numerics/quadrature.cpp | 60 ++++ src/axom/core/numerics/quadrature.hpp | 42 +++ src/axom/core/tests/numerics_quadrature.hpp | 23 ++ src/axom/quest/SamplingShaper.hpp | 35 ++- .../detail/shaping/GenerateQuadratureMesh.hpp | 258 ++++++++++++++++++ .../quest/detail/shaping/shaping_helpers.cpp | 153 ++++++----- .../quest/detail/shaping/shaping_helpers.hpp | 33 ++- src/axom/quest/examples/shaping_driver.cpp | 18 +- src/axom/quest/tests/CMakeLists.txt | 15 + .../tests/quest_blueprint_quadrature_mesh.cpp | 194 +++++++++++++ .../quest/tests/quest_sampling_shaper.cpp | 28 +- 11 files changed, 751 insertions(+), 108 deletions(-) create mode 100644 src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp create mode 100644 src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp diff --git a/src/axom/core/numerics/quadrature.cpp b/src/axom/core/numerics/quadrature.cpp index 4e460fdfc0..3939fb89fc 100644 --- a/src/axom/core/numerics/quadrature.cpp +++ b/src/axom/core/numerics/quadrature.cpp @@ -23,6 +23,41 @@ namespace axom namespace numerics { +bool is_valid_quadrature_type(int quadratureType) +{ + switch(static_cast(quadratureType)) + { + case QuadratureType::Invalid: + case QuadratureType::GaussLegendre: + case QuadratureType::GaussLobatto: + case QuadratureType::OpenUniform: + case QuadratureType::ClosedUniform: + case QuadratureType::OpenHalfUniform: + case QuadratureType::ClosedGL: + return true; + default: + return false; + } +} + +bool is_supported_quadrature_type(QuadratureType quadratureType) +{ + switch(quadratureType) + { + case QuadratureType::Invalid: + case QuadratureType::GaussLegendre: + case QuadratureType::OpenUniform: + case QuadratureType::ClosedUniform: + return true; + case QuadratureType::GaussLobatto: + case QuadratureType::OpenHalfUniform: + case QuadratureType::ClosedGL: + return false; + } + + return false; +} + void compute_gauss_legendre_data(int npts, axom::Array& nodes, axom::Array& weights, @@ -288,6 +323,31 @@ QuadratureRule get_gauss_legendre(int npts, int allocatorID) return QuadratureRule {storage.nodes.view(), storage.weights.view()}; } +QuadratureRule get_quadrature_rule(QuadratureType quadratureType, int npts, int allocatorID) +{ + assert("Invalid Axom quadrature type." && + is_valid_quadrature_type(static_cast(quadratureType))); + assert("Unsupported Axom quadrature type." && is_supported_quadrature_type(quadratureType)); + + switch(quadratureType) + { + case QuadratureType::Invalid: + case QuadratureType::GaussLegendre: + return get_gauss_legendre(npts, allocatorID); + case QuadratureType::OpenUniform: + return get_open_uniform(npts, allocatorID); + case QuadratureType::ClosedUniform: + return get_closed_uniform(npts, allocatorID); + case QuadratureType::GaussLobatto: + case QuadratureType::OpenHalfUniform: + case QuadratureType::ClosedGL: + break; + } + + assert("Unsupported Axom quadrature type." && false); + return get_gauss_legendre(npts, allocatorID); +} + QuadratureRule get_open_uniform(int npts, int allocatorID) { assert("Quadrature rules must have >= 1 point" && (npts >= 1)); diff --git a/src/axom/core/numerics/quadrature.hpp b/src/axom/core/numerics/quadrature.hpp index 7ac7b644b0..e4405e4127 100644 --- a/src/axom/core/numerics/quadrature.hpp +++ b/src/axom/core/numerics/quadrature.hpp @@ -21,6 +21,34 @@ namespace axom namespace numerics { +/*! + * \brief Enumerates the 1D quadrature families implemented in Axom. + */ +enum class QuadratureType : int +{ + Invalid = -1, + GaussLegendre = 0, + GaussLobatto = 1, + OpenUniform = 2, + ClosedUniform = 3, + OpenHalfUniform = 4, + ClosedGL = 5 +}; + +/*! + * \brief Returns true when the supplied integer corresponds to a valid + * `QuadratureType` enumerator. + */ +bool is_valid_quadrature_type(int quadratureType); + +/*! + * \brief Returns true when the supplied quadrature family is currently + * implemented in Axom core numerics. + * + * \note Families may be valid enum values but not yet implemented. + */ +bool is_supported_quadrature_type(QuadratureType quadratureType); + /*! * \class QuadratureRule * @@ -99,6 +127,20 @@ void compute_gauss_legendre_data(int npts, */ QuadratureRule get_gauss_legendre(int npts, int allocatorID = axom::getDefaultAllocatorID()); +/*! + * \brief Returns an Axom quadrature rule by family. + * + * \param [in] quadratureType The quadrature family to construct. + * \param [in] npts The number of quadrature points in the rule. + * + * \note `QuadratureType::Invalid` selects Axom's default rule, which is + * currently Gauss-Legendre. Only currently-supported Axom quadrature + * families may be passed here. + */ +QuadratureRule get_quadrature_rule(QuadratureType quadratureType, + int npts, + int allocatorID = axom::getDefaultAllocatorID()); + /*! * \brief Computes a 1D quadrature rule of open uniform Newton-Cotes points. * diff --git a/src/axom/core/tests/numerics_quadrature.hpp b/src/axom/core/tests/numerics_quadrature.hpp index 539300d25d..696ef16347 100644 --- a/src/axom/core/tests/numerics_quadrature.hpp +++ b/src/axom/core/tests/numerics_quadrature.hpp @@ -221,6 +221,29 @@ TEST(numerics_quadrature, closed_uniform_small_rules) EXPECT_DOUBLE_EQ(rule.weight(2), 1.0 / 6.0); } +TEST(numerics_quadrature, quadrature_type_dispatch) +{ + using axom::numerics::QuadratureType; + + auto rule = axom::numerics::get_quadrature_rule(QuadratureType::Invalid, 2); + EXPECT_DOUBLE_EQ(rule.node(0), 0.21132486540518711775); + EXPECT_DOUBLE_EQ(rule.node(1), 0.78867513459481288225); + + rule = axom::numerics::get_quadrature_rule(QuadratureType::GaussLegendre, 2); + EXPECT_DOUBLE_EQ(rule.node(0), 0.21132486540518711775); + EXPECT_DOUBLE_EQ(rule.node(1), 0.78867513459481288225); + + rule = axom::numerics::get_quadrature_rule(QuadratureType::OpenUniform, 3); + EXPECT_DOUBLE_EQ(rule.node(0), 0.25); + EXPECT_DOUBLE_EQ(rule.node(1), 0.5); + EXPECT_DOUBLE_EQ(rule.node(2), 0.75); + + rule = axom::numerics::get_quadrature_rule(QuadratureType::ClosedUniform, 3); + EXPECT_DOUBLE_EQ(rule.node(0), 0.0); + EXPECT_DOUBLE_EQ(rule.node(1), 0.5); + EXPECT_DOUBLE_EQ(rule.node(2), 1.0); +} + TEST(numerics_quadrature, open_uniform_exactness) { check_polynomial_exactness( diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 3b843b4f66..771204c411 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -150,28 +150,25 @@ class SamplingShaper : public Shaper /*! * \brief Sets the 1D quadrature family used to generate custom sample points. * - * Passing `mfem::Quadrature1D::Invalid` selects Axom's default MFEM quadrature - * behavior. Any other accepted value must correspond to a valid - * `mfem::Quadrature1D` enum in the inclusive range - * `[mfem::Quadrature1D::Invalid, mfem::Quadrature1D::ClosedGL]`. + * Passing `axom::numerics::QuadratureType::Invalid` selects the default + * quadrature behavior. Other values request a specific quadrature family. * For uniform point sampling over the full zone, including the element - * edges, `mfem::Quadrature1D::ClosedUniform` is often a good choice. Users + * edges, `axom::numerics::QuadratureType::ClosedUniform` is often a good + * choice. Users * can experiment with other quadrature families when different sample point * patterns are desired. * - * \param [in] qtype Integer value corresponding to an `mfem::Quadrature1D` - * enum entry. + * \param [in] qtype Quadrature family selection. */ - void setQuadratureType(int qtype) + void setQuadratureType(axom::numerics::QuadratureType qtype) { - if(qtype >= static_cast(mfem::Quadrature1D::Invalid) && - qtype <= static_cast(mfem::Quadrature1D::ClosedGL)) + if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) { m_quadratureType = qtype; } else { - SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", qtype)); + SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); } } @@ -819,14 +816,14 @@ class SamplingShaper : public Shaper { sampler->template sampleInOutField<2, 2>(meshState, m_sampleResolution, - m_quadratureType, + static_cast(m_quadratureType), m_projector22); } else if(meshDim == 3) { sampler->template sampleInOutField<3, 2>(meshState, m_sampleResolution, - m_quadratureType, + static_cast(m_quadratureType), m_projector32); } break; @@ -835,14 +832,14 @@ class SamplingShaper : public Shaper { sampler->template sampleInOutField<2, 3>(meshState, m_sampleResolution, - m_quadratureType, + static_cast(m_quadratureType), m_projector23); } else if(meshDim == 3) { sampler->template sampleInOutField<3, 3>(meshState, m_sampleResolution, - m_quadratureType, + static_cast(m_quadratureType), m_projector33); } break; @@ -950,14 +947,14 @@ class SamplingShaper : public Shaper { sampler->template sampleInOutField<2, 3>(meshState, m_sampleResolution, - m_quadratureType, + static_cast(m_quadratureType), m_projector23); } else if(meshDim == 3) { sampler->template sampleInOutField<3, 3>(meshState, m_sampleResolution, - m_quadratureType, + static_cast(m_quadratureType), m_projector33); } break; @@ -1224,7 +1221,7 @@ class SamplingShaper : public Shaper bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh) const { - if(m_quadratureType == static_cast(mfem::Quadrature1D::Invalid)) + if(m_quadratureType == axom::numerics::QuadratureType::Invalid) { return false; } @@ -1284,7 +1281,7 @@ class SamplingShaper : public Shaper shaping::PointProjector<3, 3> m_projector33 {}; shaping::VolFracSampling m_vfSampling {shaping::VolFracSampling::SAMPLE_AT_QPTS}; - int m_quadratureType {static_cast(mfem::Quadrature1D::Invalid)}; + axom::numerics::QuadratureType m_quadratureType {axom::numerics::QuadratureType::Invalid}; int m_sampleResolution[3] = {5, 5, 5}; int m_volfracOrder {2}; SamplingMethod m_samplingMethod {SamplingMethod::InOut}; diff --git a/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp b/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp new file mode 100644 index 0000000000..38c375afbb --- /dev/null +++ b/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp @@ -0,0 +1,258 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef AXOM_QUEST_GENERATE_QUADRATURE_MESH_HPP_ +#define AXOM_QUEST_GENERATE_QUADRATURE_MESH_HPP_ + +#include "axom/config.hpp" + +#if defined(AXOM_USE_CONDUIT) + + #include "axom/bump/utilities/blueprint_utilities.hpp" + #include "axom/bump/utilities/conduit_memory.hpp" + #include "axom/core.hpp" + #include "axom/core/numerics/quadrature.hpp" + #include "axom/primal.hpp" + #include "axom/sidre/core/ConduitMemory.hpp" + #include "axom/slic.hpp" + + #include + #include + #include + +namespace axom +{ +namespace quest +{ +namespace shaping +{ +namespace detail +{ + +template +AXOM_HOST_DEVICE primal::Point mapToPhysicalPoint( + const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v) +{ + using PointType = primal::Point; + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + + const double n0 = (1.0 - u) * (1.0 - v); + const double n1 = u * (1.0 - v); + const double n2 = u * v; + const double n3 = (1.0 - u) * v; + + PointType pt; + for(int d = 0; d < 2; ++d) + { + pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d]; + } + return pt; +} + +template +AXOM_HOST_DEVICE primal::Point mapToPhysicalPoint( + const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v, + double w) +{ + using PointType = primal::Point; + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + const auto p4 = coordsetView[zone.getId(4)]; + const auto p5 = coordsetView[zone.getId(5)]; + const auto p6 = coordsetView[zone.getId(6)]; + const auto p7 = coordsetView[zone.getId(7)]; + + const double a = 1.0 - u; + const double b = 1.0 - v; + const double c = 1.0 - w; + + const double n0 = a * b * c; + const double n1 = u * b * c; + const double n2 = u * v * c; + const double n3 = a * v * c; + const double n4 = a * b * w; + const double n5 = u * b * w; + const double n6 = u * v * w; + const double n7 = a * v * w; + + PointType pt; + for(int d = 0; d < 3; ++d) + { + pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d] + n4 * p4[d] + n5 * p5[d] + + n6 * p6[d] + n7 * p7[d]; + } + return pt; +} + +inline int quadraturePointCount(const numerics::QuadratureRule& ruleX, + const numerics::QuadratureRule& ruleY, + const numerics::QuadratureRule& ruleZ, + int dim) +{ + return dim == 2 ? ruleX.getNumPoints() * ruleY.getNumPoints() + : ruleX.getNumPoints() * ruleY.getNumPoints() * ruleZ.getNumPoints(); +} + +} // namespace detail + +template +class GenerateQuadratureMesh +{ +public: + using CoordsetType = typename CoordsetView::value_type; + using PointType = primal::Point; + + GenerateQuadratureMesh(const TopologyView& topologyView, const CoordsetView& coordsetView) + : m_topologyView(topologyView) + , m_coordsetView(coordsetView) + , m_allocator_id(axom::execution_space::allocatorID()) + { } + + void setAllocatorID(int allocator_id) + { + SLIC_ERROR_IF(!axom::isValidAllocatorID(allocator_id), "Invalid allocator id."); + SLIC_ERROR_IF(!axom::execution_space::usesAllocId(allocator_id), + "Allocator id is not compatible with execution space."); + m_allocator_id = allocator_id; + } + + int getAllocatorID() const { return m_allocator_id; } + + void execute(const conduit::Node& n_topology, + const conduit::Node& n_coordset, + const std::string& outputTopologyName, + const std::string& outputCoordsetName, + const std::string& originalElementsFieldName, + const numerics::QuadratureRule& ruleX, + const numerics::QuadratureRule& ruleY, + const numerics::QuadratureRule& ruleZ, + conduit::Node& n_output) const + { + namespace utils = axom::bump::utilities; + + const auto numZones = m_topologyView.numberOfZones(); + const int dim = CoordsetView::dimension(); + const int npts = detail::quadraturePointCount(ruleX, ruleY, ruleZ, dim); + const IndexType numPoints = numZones * static_cast(npts); + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(getAllocatorID()); + + const std::vector axes = utils::coordsetAxes(n_coordset); + SLIC_ASSERT(static_cast(axes.size()) == dim); + + conduit::Node& n_outputCoordset = n_output["coordsets/" + outputCoordsetName]; + n_outputCoordset.reset(); + n_outputCoordset["type"] = "explicit"; + + axom::StackArray, CoordsetView::dimension()> coordViews; + for(int d = 0; d < dim; ++d) + { + conduit::Node& comp = n_outputCoordset["values/" + axes[d]]; + comp.set_allocator(conduitAllocatorId); + comp.set(conduit::DataType(utils::cpp2conduit::id, numPoints)); + coordViews[d] = utils::make_array_view(comp); + } + + conduit::Node& n_outputTopo = n_output["topologies/" + outputTopologyName]; + n_outputTopo.reset(); + n_outputTopo["type"] = "unstructured"; + n_outputTopo["coordset"] = outputCoordsetName; + n_outputTopo["elements/shape"] = "point"; + + conduit::Node& n_connectivity = n_outputTopo["elements/connectivity"]; + n_connectivity.set_allocator(conduitAllocatorId); + n_connectivity.set(conduit::DataType::index_t(numPoints)); + auto connectivity = utils::make_array_view(n_connectivity); + + conduit::Node& n_sizes = n_outputTopo["elements/sizes"]; + n_sizes.set_allocator(conduitAllocatorId); + n_sizes.set(conduit::DataType::index_t(numPoints)); + auto sizes = utils::make_array_view(n_sizes); + + conduit::Node& n_offsets = n_outputTopo["elements/offsets"]; + n_offsets.set_allocator(conduitAllocatorId); + n_offsets.set(conduit::DataType::index_t(numPoints)); + auto offsets = utils::make_array_view(n_offsets); + + conduit::Node& n_originalElements = n_output["fields/" + originalElementsFieldName]; + n_originalElements.reset(); + n_originalElements["association"] = "element"; + n_originalElements["topology"] = outputTopologyName; + conduit::Node& n_originalValues = n_originalElements["values"]; + n_originalValues.set_allocator(conduitAllocatorId); + n_originalValues.set(conduit::DataType::index_t(numPoints)); + auto originalElements = utils::make_array_view(n_originalValues); + + const TopologyView deviceTopoView(m_topologyView); + const CoordsetView deviceCoordsetView(m_coordsetView); + + axom::for_all( + numZones, + AXOM_LAMBDA(IndexType zoneIndex) { + const auto zone = deviceTopoView.zone(zoneIndex); + IndexType pointIndex = zoneIndex * static_cast(npts); + + for(int kz = 0; kz < (dim == 3 ? ruleZ.getNumPoints() : 1); ++kz) + { + const double zeta = dim == 3 ? ruleZ.node(kz) : 0.0; + for(int jy = 0; jy < ruleY.getNumPoints(); ++jy) + { + const double eta = ruleY.node(jy); + for(int ix = 0; ix < ruleX.getNumPoints(); ++ix) + { + const double xi = ruleX.node(ix); + + PointType pt; + if constexpr(CoordsetView::dimension() == 2) + { + pt = detail::mapToPhysicalPoint(zone, deviceCoordsetView, xi, eta); + } + else + { + pt = detail::mapToPhysicalPoint(zone, deviceCoordsetView, xi, eta, zeta); + } + + for(int d = 0; d < dim; ++d) + { + coordViews[d][pointIndex] = pt[d]; + } + connectivity[pointIndex] = pointIndex; + sizes[pointIndex] = 1; + offsets[pointIndex] = pointIndex; + originalElements[pointIndex] = zoneIndex; + ++pointIndex; + } + } + } + }); + + AXOM_UNUSED_VAR(n_topology); + } + +private: + TopologyView m_topologyView; + CoordsetView m_coordsetView; + int m_allocator_id; +}; + +} // namespace shaping +} // namespace quest +} // namespace axom + +#endif + +#endif diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 72daa17eda..36064cc7d5 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -40,29 +40,17 @@ constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; -numerics::QuadratureRule getBlueprintQuadratureRule(int npts, int quadratureType, int allocatorID) +numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureType quadratureType, + int npts, + int allocatorID) { SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); + SLIC_ERROR_IF(!axom::numerics::is_supported_quadrature_type(quadratureType), + axom::fmt::format( + "Quadrature type {} is not yet supported for Blueprint quadrature meshes.", + static_cast(quadratureType))); -#if defined(AXOM_USE_MFEM) - switch(quadratureType) - { - case mfem::Quadrature1D::Invalid: - case mfem::Quadrature1D::GaussLegendre: - return numerics::get_gauss_legendre(npts, allocatorID); - case mfem::Quadrature1D::OpenUniform: - return numerics::get_open_uniform(npts, allocatorID); - case mfem::Quadrature1D::ClosedUniform: - return numerics::get_closed_uniform(npts, allocatorID); - default: - SLIC_ERROR(axom::fmt::format( - "Quadrature type {} is not supported for Blueprint quadrature meshes.", quadratureType)); - return numerics::get_gauss_legendre(npts, allocatorID); - } -#else - AXOM_UNUSED_VAR(quadratureType); - return numerics::get_gauss_legendre(npts, allocatorID); -#endif + return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); } template @@ -75,10 +63,10 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, const numerics::QuadratureRule& ruleZ, conduit::Node& meshNode) { - using namespace axom::bump::views; - constexpr int SupportedShapes = encode_shapes(Quad_ShapeID, Hex_ShapeID); + namespace views = axom::bump::views; + constexpr int SupportedShapes = views::select_shapes(views::Quad_ShapeID, views::Hex_ShapeID); - dispatch_unstructured_topology(topoNode, [&](const auto&, auto topoView) { + views::dispatch_unstructured_topology(topoNode, [&](const auto&, auto topoView) { GenerateQuadratureMesh generator(topoView, coordsetView); generator.setAllocatorID(allocatorID); @@ -116,9 +104,9 @@ class OwnedQuadratureSpace : public mfem::QuadratureSpace bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, const int sampleResolution[3], - int quadratureType) + axom::numerics::QuadratureType quadratureType) { - if(quadratureType == static_cast(mfem::Quadrature1D::Invalid)) + if(quadratureType == axom::numerics::QuadratureType::Invalid) { return false; } @@ -136,6 +124,30 @@ bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, } // namespace +int to_mfem_quadrature_type(axom::numerics::QuadratureType quadratureType) +{ + switch(quadratureType) + { + case axom::numerics::QuadratureType::Invalid: + return mfem::Quadrature1D::Invalid; + case axom::numerics::QuadratureType::GaussLegendre: + return mfem::Quadrature1D::GaussLegendre; + case axom::numerics::QuadratureType::GaussLobatto: + return mfem::Quadrature1D::GaussLobatto; + case axom::numerics::QuadratureType::OpenUniform: + return mfem::Quadrature1D::OpenUniform; + case axom::numerics::QuadratureType::ClosedUniform: + return mfem::Quadrature1D::ClosedUniform; + case axom::numerics::QuadratureType::OpenHalfUniform: + return mfem::Quadrature1D::OpenHalfUniform; + case axom::numerics::QuadratureType::ClosedGL: + return mfem::Quadrature1D::ClosedGL; + } + + SLIC_ERROR(axom::fmt::format("Invalid quadrature type {}.", static_cast(quadratureType))); + return mfem::Quadrature1D::Invalid; +} + // Utility function to either return a gf from the dc, or to allocate it through the dc mfem::GridFunction* getOrAllocateL2GridFunction(mfem::DataCollection* dc, const std::string& gf_name, @@ -264,7 +276,9 @@ mfem::QuadratureSpace* makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRe return new mfem::QuadratureSpace(mesh, sampleOrder); } -mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, int sampleRes[3], int quadratureType) +mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, + int sampleRes[3], + axom::numerics::QuadratureType quadratureType) { SLIC_ASSERT(mesh != nullptr); const int NE = mesh->GetNE(); @@ -284,26 +298,27 @@ mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, int sampleRes axom::fmt::format("Invalid sample value {} for dimension {}.", sampleRes[d], d)); switch(quadratureType) { - case mfem::Quadrature1D::GaussLegendre: + case axom::numerics::QuadratureType::GaussLegendre: mfem::QuadratureFunctions1D::GaussLegendre(sampleRes[d], &ird[d]); break; - case mfem::Quadrature1D::GaussLobatto: + case axom::numerics::QuadratureType::GaussLobatto: mfem::QuadratureFunctions1D::GaussLobatto(sampleRes[d], &ird[d]); break; - case mfem::Quadrature1D::OpenUniform: + case axom::numerics::QuadratureType::OpenUniform: mfem::QuadratureFunctions1D::OpenUniform(sampleRes[d], &ird[d]); break; - case mfem::Quadrature1D::ClosedUniform: + case axom::numerics::QuadratureType::ClosedUniform: mfem::QuadratureFunctions1D::ClosedUniform(sampleRes[d], &ird[d]); break; - case mfem::Quadrature1D::OpenHalfUniform: + case axom::numerics::QuadratureType::OpenHalfUniform: mfem::QuadratureFunctions1D::OpenHalfUniform(sampleRes[d], &ird[d]); break; - case mfem::Quadrature1D::ClosedGL: + case axom::numerics::QuadratureType::ClosedGL: mfem::QuadratureFunctions1D::ClosedGL(sampleRes[d], &ird[d]); break; + case axom::numerics::QuadratureType::Invalid: default: - SLIC_ERROR(axom::fmt::format("Invalid quadrature type {}.", quadratureType)); + SLIC_ERROR(axom::fmt::format("Invalid quadrature type {}.", static_cast(quadratureType))); break; } } @@ -328,7 +343,7 @@ mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, int sampleRes void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, int sampleResolution[3], - int quadratureType) + axom::numerics::QuadratureType quadratureType) { SLIC_ASSERT(mesh != nullptr); const int NE = mesh->GetNE(); @@ -342,7 +357,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, // Make a quadrature space to determine the point locations in each element. mfem::QuadratureSpace* sp = nullptr; - if(quadratureType == static_cast(mfem::Quadrature1D::Invalid)) + if(quadratureType == axom::numerics::QuadratureType::Invalid) { sp = makeDefaultQuadratureSpace(mesh, sampleResolution[0]); } @@ -409,7 +424,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, void generatePositionsQFunction(SamplingMFEMState& mfemState, int sampleResolution[3], - int quadratureType) + axom::numerics::QuadratureType quadratureType) { generatePositionsQFunction(mfemState.m_dc->GetMesh(), mfemState.m_inoutShapeQFuncs, @@ -418,17 +433,19 @@ void generatePositionsQFunction(SamplingMFEMState& mfemState, } #if defined(AXOM_USE_CONDUIT) -void generatePositionsQFunction(BlueprintState& bpState, - int sampleResolution[3], - int quadratureType) +void generateQuadraturePointMesh(conduit::Node& bpMeshNode, + const std::string& topologyName, + int allocatorID, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType) { - if(bpState.m_internal_node.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) + if(bpMeshNode.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) { return; } const conduit::Node& topoNode = - bpState.m_internal_node.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); + bpMeshNode.fetch_existing("topologies").fetch_existing(topologyName); const std::string topoType = topoNode.fetch_existing("type").as_string(); SLIC_ERROR_IF(topoType != "unstructured", axom::fmt::format("Unsupported Blueprint topology type '{}' for quadrature mesh generation.", @@ -440,83 +457,93 @@ void generatePositionsQFunction(BlueprintState& bpState, shape)); const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); - const conduit::Node& coordsetNode = - bpState.m_internal_node.fetch_existing("coordsets").fetch_existing(coordsetName); + const conduit::Node& coordsetNode = bpMeshNode.fetch_existing("coordsets").fetch_existing(coordsetName); const std::string coordsetType = coordsetNode.fetch_existing("type").as_string(); SLIC_ERROR_IF(coordsetType != "explicit", axom::fmt::format("Unsupported Blueprint coordset type '{}' for quadrature mesh generation.", coordsetType)); - int allocatorID = bpState.m_allocator_id; - if(!axom::execution_space::usesAllocId(allocatorID) && - !axom::execution_space::usesAllocId(allocatorID) + int selectedAllocatorID = allocatorID; + if(!axom::execution_space::usesAllocId(selectedAllocatorID) && + !axom::execution_space::usesAllocId(selectedAllocatorID) #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - && !axom::execution_space::usesAllocId(allocatorID) + && !axom::execution_space::usesAllocId(selectedAllocatorID) #endif #if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - && !axom::execution_space::usesAllocId(allocatorID) + && !axom::execution_space::usesAllocId(selectedAllocatorID) #endif ) { - allocatorID = axom::execution_space::allocatorID(); + selectedAllocatorID = axom::execution_space::allocatorID(); } - auto ruleX = getBlueprintQuadratureRule(sampleResolution[0], quadratureType, allocatorID); - auto ruleY = getBlueprintQuadratureRule(sampleResolution[1], quadratureType, allocatorID); - auto ruleZ = getBlueprintQuadratureRule(sampleResolution[2], quadratureType, allocatorID); + auto ruleX = getBlueprintQuadratureRule(quadratureType, sampleResolution[0], selectedAllocatorID); + auto ruleY = getBlueprintQuadratureRule(quadratureType, sampleResolution[1], selectedAllocatorID); + auto ruleZ = getBlueprintQuadratureRule(quadratureType, sampleResolution[2], selectedAllocatorID); axom::bump::views::dispatch_explicit_coordset(coordsetNode, [&](auto coordsetView) { #if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - if(axom::execution_space::usesAllocId(allocatorID)) + if(axom::execution_space::usesAllocId(selectedAllocatorID)) { buildBlueprintQuadratureMesh(topoNode, coordsetNode, coordsetView, - allocatorID, + selectedAllocatorID, ruleX, ruleY, ruleZ, - bpState.m_internal_node); + bpMeshNode); return; } #endif #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - if(axom::execution_space::usesAllocId(allocatorID)) + if(axom::execution_space::usesAllocId(selectedAllocatorID)) { buildBlueprintQuadratureMesh(topoNode, coordsetNode, coordsetView, - allocatorID, + selectedAllocatorID, ruleX, ruleY, ruleZ, - bpState.m_internal_node); + bpMeshNode); return; } #endif - if(axom::execution_space::usesAllocId(allocatorID)) + if(axom::execution_space::usesAllocId(selectedAllocatorID)) { buildBlueprintQuadratureMesh(topoNode, coordsetNode, coordsetView, - allocatorID, + selectedAllocatorID, ruleX, ruleY, ruleZ, - bpState.m_internal_node); + bpMeshNode); return; } buildBlueprintQuadratureMesh(topoNode, coordsetNode, coordsetView, - allocatorID, + selectedAllocatorID, ruleX, ruleY, ruleZ, - bpState.m_internal_node); + bpMeshNode); }); } + +void generatePositionsQFunction(BlueprintState& bpState, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType) +{ + generateQuadraturePointMesh(bpState.m_internal_node, + bpState.m_topology_name, + bpState.m_allocator_id, + sampleResolution, + quadratureType); +} #endif void FCT_correct(const double* M, // Mass matrix diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 72ed77e41d..40a12ba3e9 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -150,6 +150,16 @@ using PointProjector = #if defined(AXOM_USE_MFEM) +/*! + * \brief Converts an Axom quadrature family to the corresponding MFEM + * `Quadrature1D` value. + * + * \note All `axom::numerics::QuadratureType` enumerators currently map 1:1 to + * MFEM names, even when Axom core numerics does not yet implement the + * corresponding rule family. + */ +int to_mfem_quadrature_type(axom::numerics::QuadratureType quadratureType); + using QFunctionCollection = mfem::NamedFieldsMap; using DenseTensorCollection = mfem::NamedFieldsMap; using MFEMArrayCollection = mfem::NamedFieldsMap>; @@ -265,23 +275,40 @@ void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, int sampleResolution[3], - int quadratureType); + axom::numerics::QuadratureType quadratureType); /** * \brief Generates a "position" quadrature function for the supplied MFEM state. */ void generatePositionsQFunction(SamplingMFEMState& mfemState, int sampleResolution[3], - int quadratureType); + axom::numerics::QuadratureType quadratureType); #if defined(AXOM_USE_CONDUIT) +/** + * \brief Generates a derived Blueprint quadrature point mesh within the + * supplied Blueprint mesh node. + * + * \param bpMeshNode The Blueprint mesh node to augment. + * \param topologyName The source topology name to sample. + * \param allocatorID Allocator id used for generated storage. + * \param sampleResolution The sample resolution in each logical dimension. + * \param quadratureType An int corresponding to `mfem::Quadrature1D` when MFEM + * is enabled, or to `axom::numerics::QuadratureType` otherwise. + */ +void generateQuadraturePointMesh(conduit::Node& bpMeshNode, + const std::string& topologyName, + int allocatorID, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType); + /** * \brief Generates a derived Blueprint quadrature point mesh for the supplied * Blueprint state. */ void generatePositionsQFunction(BlueprintState& bpState, int sampleResolution[3], - int quadratureType); + axom::numerics::QuadratureType quadratureType); #endif /** diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 991dc59908..9034c747fb 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -108,7 +108,7 @@ struct Input RuntimePolicy policy {RuntimePolicy::seq}; std::vector samplingResolution {5, 5, 5}; // We set quadratureType to Invalid to select the default method. - int quadratureType {static_cast(mfem::Quadrature1D::Invalid)}; + axom::numerics::QuadratureType quadratureType {axom::numerics::QuadratureType::Invalid}; int outputOrder {2}; int samplesPerKnotSpan {25}; int refinementLevel {7}; @@ -328,14 +328,14 @@ struct Input ->capture_default_str() ->transform(axom::CLI::CheckedTransformer(vfsamplingMap, axom::CLI::ignore_case)); - std::map quadTypeMap { - {"default", mfem::Quadrature1D::Invalid}, - {"gausslegendre", mfem::Quadrature1D::GaussLegendre}, - {"gausslobatto", mfem::Quadrature1D::GaussLobatto}, - {"openuniform", mfem::Quadrature1D::OpenUniform}, - {"closeduniform", mfem::Quadrature1D::ClosedUniform}, - {"openhalfuniform", mfem::Quadrature1D::OpenHalfUniform}, - {"closedgl", mfem::Quadrature1D::ClosedGL}}; + std::map quadTypeMap { + {"default", axom::numerics::QuadratureType::Invalid}, + {"gausslegendre", axom::numerics::QuadratureType::GaussLegendre}, + {"gausslobatto", axom::numerics::QuadratureType::GaussLobatto}, + {"openuniform", axom::numerics::QuadratureType::OpenUniform}, + {"closeduniform", axom::numerics::QuadratureType::ClosedUniform}, + {"openhalfuniform", axom::numerics::QuadratureType::OpenHalfUniform}, + {"closedgl", axom::numerics::QuadratureType::ClosedGL}}; sampling_options->add_option("-q,--quadrature-type", quadratureType) ->description( "Quadrature type. \n" diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index d30e7f40ab..bef0d73806 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -87,6 +87,21 @@ if(CONDUIT_FOUND AND AXOM_DATA_DIR) endif() +if(CONDUIT_FOUND AND MFEM_FOUND AND AXOM_ENABLE_SIDRE) + axom_add_executable( + NAME quest_blueprint_quadrature_mesh_test + SOURCES quest_blueprint_quadrature_mesh.cpp + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + DEPENDS_ON ${quest_tests_depends} conduit::conduit mfem + FOLDER axom/quest/tests + ) + + axom_add_test( + NAME quest_blueprint_quadrature_mesh + COMMAND quest_blueprint_quadrature_mesh_test + ) +endif() + #------------------------------------------------------------------------------ # Tests that use MFEM when available #------------------------------------------------------------------------------ diff --git a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp new file mode 100644 index 0000000000..8cb377bf57 --- /dev/null +++ b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp @@ -0,0 +1,194 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/config.hpp" + +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_MFEM) + + #include "gtest/gtest.h" + + #include "axom/core.hpp" + #include "axom/quest/detail/shaping/shaping_helpers.hpp" + #include "axom/bump/utilities/conduit_memory.hpp" + #include "axom/bump/views/dispatch_coordset.hpp" + + #include "conduit.hpp" + #include "conduit_blueprint.hpp" + +namespace +{ + +template +bool compareArrayView(axom::ArrayView lhs, axom::ArrayView rhs) +{ + if(lhs.size() != rhs.size()) + { + return false; + } + + for(axom::IndexType i = 0; i < lhs.size(); ++i) + { + if(lhs[i] != rhs[i]) + { + return false; + } + } + return true; +} + +void setNodeValues(conduit::Node& node, axom::ArrayView values) +{ + node.set(conduit::DataType::float64(values.size())); + auto* data = node.as_float64_ptr(); + for(axom::IndexType i = 0; i < values.size(); ++i) + { + data[i] = values[i]; + } +} + +void setNodeValues(conduit::Node& node, axom::ArrayView values) +{ + node.set(conduit::DataType::index_t(values.size())); + auto* data = node.as_index_t_ptr(); + for(axom::IndexType i = 0; i < values.size(); ++i) + { + data[i] = values[i]; + } +} + +conduit::Node makeQuadMesh() +{ + conduit::Node mesh; + + mesh["coordsets/coords/type"] = "explicit"; + const axom::Array x {{0., 1., 0., 1.}}; + const axom::Array y {{0., 0., 1., 1.}}; + setNodeValues(mesh["coordsets/coords/values/x"], x.view()); + setNodeValues(mesh["coordsets/coords/values/y"], y.view()); + + mesh["topologies/mesh/type"] = "unstructured"; + mesh["topologies/mesh/coordset"] = "coords"; + mesh["topologies/mesh/elements/shape"] = "quad"; + const axom::Array connectivity {{0, 1, 3, 2}}; + setNodeValues(mesh["topologies/mesh/elements/connectivity"], connectivity.view()); + + return mesh; +} + +conduit::Node makeHexMesh() +{ + conduit::Node mesh; + + mesh["coordsets/coords/type"] = "explicit"; + const axom::Array x {{0., 1., 0., 1., 0., 1., 0., 1.}}; + const axom::Array y {{0., 0., 1., 1., 0., 0., 1., 1.}}; + const axom::Array z {{0., 0., 0., 0., 1., 1., 1., 1.}}; + setNodeValues(mesh["coordsets/coords/values/x"], x.view()); + setNodeValues(mesh["coordsets/coords/values/y"], y.view()); + setNodeValues(mesh["coordsets/coords/values/z"], z.view()); + + mesh["topologies/mesh/type"] = "unstructured"; + mesh["topologies/mesh/coordset"] = "coords"; + mesh["topologies/mesh/elements/shape"] = "hex"; + const axom::Array connectivity {{0, 1, 3, 2, 4, 5, 7, 6}}; + setNodeValues(mesh["topologies/mesh/elements/connectivity"], connectivity.view()); + + return mesh; +} + +} // namespace + +TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) +{ + conduit::Node mesh = makeQuadMesh(); + + int sampleResolution[3] = {2, 3, 1}; + axom::quest::shaping::generateQuadraturePointMesh(mesh, + "mesh", + axom::execution_space::allocatorID(), + sampleResolution, + axom::numerics::QuadratureType::ClosedUniform); + + conduit::Node info; + EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); + + const conduit::Node& quadTopo = mesh["topologies/quadrature_points"]; + EXPECT_EQ(quadTopo["type"].as_string(), "unstructured"); + EXPECT_EQ(quadTopo["coordset"].as_string(), "quadrature_points"); + EXPECT_EQ(quadTopo["elements/shape"].as_string(), "point"); + + namespace utils = axom::bump::utilities; + const auto connView = utils::make_array_view( + mesh["topologies/quadrature_points/elements/connectivity"]); + const auto sizesView = utils::make_array_view( + mesh["topologies/quadrature_points/elements/sizes"]); + const auto offsetsView = utils::make_array_view( + mesh["topologies/quadrature_points/elements/offsets"]); + const auto originalElementsView = + utils::make_array_view(mesh["fields/originalElements/values"]); + + const axom::Array expectedX {{0., 1., 0., 1., 0., 1.}}; + const axom::Array expectedY {{0., 0., 0.5, 0.5, 1., 1.}}; + const axom::Array expectedConn {{0, 1, 2, 3, 4, 5}}; + const axom::Array expectedSizes {{1, 1, 1, 1, 1, 1}}; + const axom::Array expectedOffsets {{0, 1, 2, 3, 4, 5}}; + const axom::Array expectedOriginalElements {{0, 0, 0, 0, 0, 0}}; + + axom::bump::views::dispatch_explicit_coordset( + mesh["coordsets/quadrature_points"], [&](auto coordsetView) { + for(axom::IndexType i = 0; i < expectedX.size(); ++i) + { + EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-12); + EXPECT_NEAR(coordsetView[i][1], expectedY[i], 1e-12); + } + }); + EXPECT_TRUE(compareArrayView(expectedConn.view(), connView)); + EXPECT_TRUE(compareArrayView(expectedSizes.view(), sizesView)); + EXPECT_TRUE(compareArrayView(expectedOffsets.view(), offsetsView)); + EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); +} + +TEST(quest_blueprint_quadrature_mesh, generate_open_uniform_hex_mesh) +{ + conduit::Node mesh = makeHexMesh(); + + int sampleResolution[3] = {2, 1, 2}; + axom::quest::shaping::generateQuadraturePointMesh(mesh, + "mesh", + axom::execution_space::allocatorID(), + sampleResolution, + axom::numerics::QuadratureType::OpenUniform); + + conduit::Node info; + EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); + + EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/type")) << mesh.to_yaml(); + EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/values/x")) << mesh.to_yaml(); + EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/values/y")) << mesh.to_yaml(); + EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/values/z")) << mesh.to_yaml(); + + namespace utils = axom::bump::utilities; + const auto originalElementsView = + utils::make_array_view(mesh["fields/originalElements/values"]); + + const axom::Array expectedX {{1. / 3., 2. / 3., 1. / 3., 2. / 3.}}; + const axom::Array expectedY {{0.5, 0.5, 0.5, 0.5}}; + const axom::Array expectedZ {{1. / 3., 1. / 3., 2. / 3., 2. / 3.}}; + const axom::Array expectedOriginalElements {{0, 0, 0, 0}}; + + axom::bump::views::dispatch_explicit_coordset( + mesh["coordsets/quadrature_points"], [&](auto coordsetView) { + for(axom::IndexType i = 0; i < expectedX.size(); ++i) + { + EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-6); + EXPECT_NEAR(coordsetView[i][1], expectedY[i], 1e-6); + EXPECT_NEAR(coordsetView[i][2], expectedZ[i], 1e-6); + } + }); + EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); +} + +#endif diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index eedeb143e3..723599a7ce 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -235,14 +235,14 @@ struct PlaneProjector23 } }; -const std::pair supported_quadrature_types[] = { - {"default", static_cast(mfem::Quadrature1D::Invalid)}, - {"gausslegendre", static_cast(mfem::Quadrature1D::GaussLegendre)}, - {"gausslobatto", static_cast(mfem::Quadrature1D::GaussLobatto)}, - {"openuniform", static_cast(mfem::Quadrature1D::OpenUniform)}, - {"closeduniform", static_cast(mfem::Quadrature1D::ClosedUniform)}, - {"openhalfuniform", static_cast(mfem::Quadrature1D::OpenHalfUniform)}, - {"closedgl", static_cast(mfem::Quadrature1D::ClosedGL)}}; +const std::pair supported_quadrature_types[] = { + {"default", axom::numerics::QuadratureType::Invalid}, + {"gausslegendre", axom::numerics::QuadratureType::GaussLegendre}, + {"gausslobatto", axom::numerics::QuadratureType::GaussLobatto}, + {"openuniform", axom::numerics::QuadratureType::OpenUniform}, + {"closeduniform", axom::numerics::QuadratureType::ClosedUniform}, + {"openhalfuniform", axom::numerics::QuadratureType::OpenHalfUniform}, + {"closedgl", axom::numerics::QuadratureType::ClosedGL}}; // Utility function to slice a tetrahedron along a plane primal::Polygon slice(const primal::Tetrahedron& tet, @@ -2388,7 +2388,7 @@ piece = line(end=start) int sampleRes[3] = {3, 5, 1}; this->m_shaper->setSamplingResolution(sampleRes); - this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedUniform)); + this->m_shaper->setQuadratureType(axom::numerics::QuadratureType::ClosedUniform); this->m_shaper->setVolumeFractionOrder(0); this->runShaping(); @@ -2484,9 +2484,9 @@ dimensions: 2 this->initializeShaping(shape_file.getPath()); slic::ScopedAbortToThrow abort_guard; - EXPECT_THROW(m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::Invalid) - 1), + EXPECT_THROW(m_shaper->setQuadratureType(static_cast(-2)), slic::SlicAbortException); - EXPECT_THROW(m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedGL) + 1), + EXPECT_THROW(m_shaper->setQuadratureType(static_cast(6)), slic::SlicAbortException); } @@ -2521,7 +2521,7 @@ dimensions: 3 int sampleRes[3] = {3, 5, 2}; this->m_shaper->setSamplingResolution(sampleRes); - this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::ClosedUniform)); + this->m_shaper->setQuadratureType(axom::numerics::QuadratureType::ClosedUniform); this->m_shaper->setVolumeFractionOrder(0); this->runShaping(); @@ -2569,7 +2569,7 @@ dimensions: 3 int sampleRes[3] = {3, 4, 5}; this->m_shaper->setSamplingResolution(sampleRes); - this->m_shaper->setQuadratureType(static_cast(mfem::Quadrature1D::OpenUniform)); + this->m_shaper->setQuadratureType(axom::numerics::QuadratureType::OpenUniform); this->m_shaper->setVolumeFractionOrder(4); this->runShaping(); @@ -2599,7 +2599,7 @@ TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_ quest::shaping::generatePositionsQFunction(&mesh, qfuncs, sampleRes, - static_cast(mfem::Quadrature1D::OpenUniform)); + axom::numerics::QuadratureType::OpenUniform); auto* positions = qfuncs.Get("positions"); ASSERT_NE(positions, nullptr); From 421010ea9c53082fd21079dccba1fd7192b8f6f2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 May 2026 11:37:26 -0700 Subject: [PATCH 06/72] Blueprint point sampling. --- src/axom/quest/SamplingShaper.hpp | 17 ++-- .../quest/detail/shaping/InOutSampler.hpp | 32 ++++++- .../quest/detail/shaping/PrimitiveSampler.hpp | 18 +++- .../detail/shaping/WindingNumberSampler.hpp | 37 +++++++- .../quest/detail/shaping/shaping_helpers.cpp | 23 +++-- .../quest/detail/shaping/shaping_helpers.hpp | 95 +++++++++++++++++-- .../tests/quest_blueprint_quadrature_mesh.cpp | 36 +++++++ .../quest/tests/quest_sampling_shaper.cpp | 24 +++++ 8 files changed, 245 insertions(+), 37 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 771204c411..20f4993983 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -643,7 +643,7 @@ class SamplingShaper : public Shaper auto& mfemState = samplingMFEMState(); auto* mesh = mfemState.m_dc->GetMesh(); - ensurePositionsQFunction(mfemState); + ensureSamplingPositions(mfemState); auto* positionsQSpace = mfemState.m_inoutShapeQFuncs.Get("positions")->GetSpace(); // Interpolate grid functions at quadrature points & register material quad functions @@ -765,18 +765,15 @@ class SamplingShaper : public Shaper } private: - void ensurePositionsQFunction(shaping::SamplingMFEMState& mfemState) + void ensureSamplingPositions(shaping::SamplingMFEMState& mfemState) { - if(!mfemState.m_inoutShapeQFuncs.Has("positions")) - { - shaping::generatePositionsQFunction(mfemState, m_sampleResolution, m_quadratureType); - } + shaping::generateSamplingPositions(mfemState, m_sampleResolution, m_quadratureType); } #if defined(AXOM_USE_CONDUIT) - void ensurePositionsQFunction(shaping::BlueprintState& bpState) + void ensureSamplingPositions(shaping::BlueprintState& bpState) { - shaping::generatePositionsQFunction(bpState, m_sampleResolution, m_quadratureType); + shaping::generateSamplingPositions(bpState, m_sampleResolution, m_quadratureType); } #endif @@ -802,7 +799,7 @@ class SamplingShaper : public Shaper // Sample the InOut field at the mesh quadrature points if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) { - ensurePositionsQFunction(meshState); + ensureSamplingPositions(meshState); } const int meshDim = meshDimension(meshState); @@ -931,7 +928,7 @@ class SamplingShaper : public Shaper const int meshDim = meshDimension(meshState); if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) { - ensurePositionsQFunction(meshState); + ensureSamplingPositions(meshState); } switch(m_vfSampling) diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index 534e1facf8..0b452757e7 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -183,11 +183,33 @@ class InOutSampler #if defined(AXOM_USE_CONDUIT) template - void sampleInOutField(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), - int AXOM_UNUSED_PARAM(sampleRes)[3], - int AXOM_UNUSED_PARAM(quadratureType), - PointProjector AXOM_UNUSED_PARAM(projector) = {}) - { } + std::enable_if_t sampleInOutField(shaping::BlueprintState& bpState, + int sampleRes[3], + int quadratureType, + PointProjector projector = {}) + { + using PointType = primal::Point; + + const InOutOctreeType* octree = m_octree; + auto checkInside = [=](const PointType& pt) -> bool { return octree->within(pt); }; + shaping::sampleInOutField(m_shapeName, + bpState, + sampleRes, + quadratureType, + checkInside, + projector); + } + + template + std::enable_if_t sampleInOutField(shaping::BlueprintState&, + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), + PointProjector) + { + static_assert(ToDim != DIM, + "Do not call this function -- it only exists to appease the compiler!" + "Projector's return dimension (ToDim), must match class dimension (DIM)"); + } template void computeVolumeFractionsBaseline(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index 679934bf28..45bf70c552 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -315,11 +315,19 @@ class PrimitiveSampler #if defined(AXOM_USE_CONDUIT) template - void sampleInOutField(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), - int AXOM_UNUSED_PARAM(sampleRes)[3], - int AXOM_UNUSED_PARAM(quadratureType), - PointProjector AXOM_UNUSED_PARAM(projector) = {}) - { } + void sampleInOutField(shaping::BlueprintState& bpState, + int sampleRes[3], + int quadratureType, + PointProjector projector = {}) + { + auto checkInside = [](const primal::Point&) -> bool { return false; }; + shaping::sampleInOutField(m_shapeName, + bpState, + sampleRes, + quadratureType, + checkInside, + projector); + } template void computeVolumeFractionsBaseline(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index 1809367ef5..38ae931e33 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -319,11 +319,38 @@ class WindingNumberSampler #if defined(AXOM_USE_CONDUIT) template - void sampleInOutField(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), - int AXOM_UNUSED_PARAM(sampleRes)[3], - int AXOM_UNUSED_PARAM(quadratureType), - PointProjector AXOM_UNUSED_PARAM(projector) = {}) - { } + std::enable_if_t sampleInOutField(shaping::BlueprintState& bpState, + int sampleRes[3], + int quadratureType, + PointProjector projector = {}) + { + const auto contourCaches = m_contourCaches; + auto checkInside = [=](const PointType& pt) -> bool { + bool inside = false; + for(axom::IndexType i = 0; i < contourCaches.size() && !inside; i++) + { + inside |= detail::checkInside(contourCaches[i], pt); + } + return inside; + }; + shaping::sampleInOutField(m_shapeName, + bpState, + sampleRes, + quadratureType, + checkInside, + projector); + } + + template + std::enable_if_t sampleInOutField(shaping::BlueprintState&, + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), + PointProjector) + { + static_assert(ToDim != DIM, + "Do not call this function -- it only exists to appease the compiler!" + "Projector's return dimension (ToDim), must match class dimension (DIM)"); + } template void computeVolumeFractionsBaseline(shaping::BlueprintState& AXOM_UNUSED_PARAM(bpState), diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 36064cc7d5..bb2c9e576e 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -422,10 +422,15 @@ void generatePositionsQFunction(mfem::Mesh* mesh, inoutQFuncs.Register("positions", pos_coef, true); } -void generatePositionsQFunction(SamplingMFEMState& mfemState, - int sampleResolution[3], - axom::numerics::QuadratureType quadratureType) +void generateSamplingPositions(SamplingMFEMState& mfemState, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType) { + if(mfemState.m_inoutShapeQFuncs.Has("positions")) + { + return; + } + generatePositionsQFunction(mfemState.m_dc->GetMesh(), mfemState.m_inoutShapeQFuncs, sampleResolution, @@ -534,10 +539,16 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, }); } -void generatePositionsQFunction(BlueprintState& bpState, - int sampleResolution[3], - axom::numerics::QuadratureType quadratureType) +void generateSamplingPositions(BlueprintState& bpState, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType) { + if(bpState.m_internal_node.has_path( + axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) + { + return; + } + generateQuadraturePointMesh(bpState.m_internal_node, bpState.m_topology_name, bpState.m_allocator_id, diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 40a12ba3e9..c858ae024e 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -24,6 +24,8 @@ #endif #if defined(AXOM_USE_CONDUIT) #include "conduit_node.hpp" + #include "axom/bump/utilities/conduit_memory.hpp" + #include "axom/bump/views/dispatch_coordset.hpp" #endif namespace axom @@ -280,9 +282,9 @@ void generatePositionsQFunction(mfem::Mesh* mesh, /** * \brief Generates a "position" quadrature function for the supplied MFEM state. */ -void generatePositionsQFunction(SamplingMFEMState& mfemState, - int sampleResolution[3], - axom::numerics::QuadratureType quadratureType); +void generateSamplingPositions(SamplingMFEMState& mfemState, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType); #if defined(AXOM_USE_CONDUIT) /** @@ -306,9 +308,9 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, * \brief Generates a derived Blueprint quadrature point mesh for the supplied * Blueprint state. */ -void generatePositionsQFunction(BlueprintState& bpState, - int sampleResolution[3], - axom::numerics::QuadratureType quadratureType); +void generateSamplingPositions(BlueprintState& bpState, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType); #endif /** @@ -438,6 +440,87 @@ void sampleInOutField(const std::string shapeName, static_cast(numQueryPoints / timer.elapsed()))); } +#if defined(AXOM_USE_CONDUIT) +template +void sampleInOutField(const std::string& shapeName, + shaping::BlueprintState& bpState, + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), + InsideFunc&& checkInside, + PointProjector projector = {}) +{ + using FromPoint = primal::Point; + using ToPoint = primal::Point; + AXOM_ANNOTATE_SCOPE("sampleInOutField"); + + SLIC_ERROR_IF(FromDim != ToDim && !projector, + "A projector callback function is required when FromDim != ToDim"); + + constexpr const char* quadratureCoordsetName = "quadrature_points"; + constexpr const char* quadratureTopologyName = "quadrature_points"; + const std::string inoutName = axom::fmt::format("inout_{}", shapeName); + + conduit::Node& bpMeshNode = bpState.m_internal_node; + SLIC_ERROR_IF(!bpMeshNode.has_path("coordsets/quadrature_points"), + "Missing Blueprint quadrature coordset. Generate sampling positions first."); + SLIC_ERROR_IF(!bpMeshNode.has_path("topologies/quadrature_points"), + "Missing Blueprint quadrature topology. Generate sampling positions first."); + + conduit::Node& inoutNode = bpMeshNode["fields/" + inoutName]; + inoutNode.reset(); + inoutNode["association"] = "element"; + inoutNode["topology"] = quadratureTopologyName; + + namespace utils = axom::bump::utilities; + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); + conduit::Node& valuesNode = inoutNode["values"]; + valuesNode.set_allocator(conduitAllocatorId); + + axom::utilities::Timer timer(true); + axom::bump::views::dispatch_explicit_coordset( + bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)], [&](auto coordsetView) { + using CoordsetView = typename std::decay::type; + + SLIC_ERROR_IF(CoordsetView::dimension() != FromDim, + axom::fmt::format("Expected {}D quadrature point coordset, got {}D.", + FromDim, + CoordsetView::dimension())); + + const auto numQueryPoints = coordsetView.size(); + valuesNode.set(conduit::DataType::float64(numQueryPoints)); + auto inoutValues = utils::make_array_view(valuesNode); + + for(axom::IndexType i = 0; i < numQueryPoints; ++i) + { + FromPoint fromPt; + const auto coordsetPoint = coordsetView[i]; + for(int d = 0; d < FromDim; ++d) + { + fromPt[d] = coordsetPoint[d]; + } + + const ToPoint queryPt = projector ? projector(fromPt) : ToPoint(fromPt.data()); + inoutValues[i] = checkInside(queryPt) ? 1. : 0.; + } + }); + timer.stop(); + + const auto numQueryPoints = bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)] + .fetch_existing("values") + .child(0) + .dtype() + .number_of_elements(); + + SLIC_INFO_ROOT(axom::fmt::format( + axom::utilities::locale(), + "\t Sampling inout field '{}' took {:.3Lf} seconds (@ {:L} queries per second)", + inoutName, + timer.elapsed(), + static_cast(numQueryPoints / timer.elapsed()))); +} +#endif + /*! * \brief Samples the inout field over the indexed geometry, possibly using a * callback function to project the input points (from the computational mesh) diff --git a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp index 8cb377bf57..3e87788a7b 100644 --- a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp +++ b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp @@ -191,4 +191,40 @@ TEST(quest_blueprint_quadrature_mesh, generate_open_uniform_hex_mesh) EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); } +TEST(quest_blueprint_quadrature_mesh, state_wrapper_generation_is_idempotent) +{ + conduit::Node mesh = makeQuadMesh(); + + axom::quest::shaping::BlueprintState bpState; + bpState.m_allocator_id = axom::execution_space::allocatorID(); + bpState.m_topology_name = "mesh"; + bpState.m_internal_node = mesh; + + int sampleResolution[3] = {2, 2, 1}; + axom::quest::shaping::generateSamplingPositions( + bpState, + sampleResolution, + axom::numerics::QuadratureType::ClosedUniform); + + ASSERT_TRUE(bpState.m_internal_node.has_path("fields/originalElements/values")); + conduit::Node savedOriginalElements; + savedOriginalElements.set_external( + bpState.m_internal_node["fields/originalElements/values"]); + + axom::quest::shaping::generateSamplingPositions( + bpState, + sampleResolution, + axom::numerics::QuadratureType::OpenUniform); + + EXPECT_TRUE(bpState.m_internal_node.has_path("topologies/quadrature_points")); + + namespace utils = axom::bump::utilities; + const auto originalElementsView = utils::make_array_view( + bpState.m_internal_node["fields/originalElements/values"]); + const auto savedOriginalElementsView = + utils::make_array_view(savedOriginalElements); + + EXPECT_TRUE(compareArrayView(savedOriginalElementsView, originalElementsView)); +} + #endif diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 723599a7ce..6f27c5f287 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2633,6 +2633,30 @@ TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_ qfuncs.DeleteData(true); } +TEST_F(CurvedSampleTester2D, generate_sampling_positions_is_idempotent) +{ + quest::shaping::SamplingMFEMState mfemState; + mfemState.m_dc = &this->getDC(); + + int sampleRes[3] = {3, 2, 1}; + quest::shaping::generateSamplingPositions(mfemState, + sampleRes, + axom::numerics::QuadratureType::OpenUniform); + + auto* positions = mfemState.m_inoutShapeQFuncs.Get("positions"); + ASSERT_NE(positions, nullptr); + auto* qspace = dynamic_cast(positions->GetSpace()); + ASSERT_NE(qspace, nullptr); + const int initialNumPoints = qspace->GetElementIntRule(0).GetNPoints(); + + quest::shaping::generateSamplingPositions(mfemState, + sampleRes, + axom::numerics::QuadratureType::ClosedUniform); + + EXPECT_EQ(mfemState.m_inoutShapeQFuncs.Get("positions"), positions); + EXPECT_EQ(qspace->GetElementIntRule(0).GetNPoints(), initialNumPoints); +} + //----------------------------------------------------------------------------- TEST_F(SamplingShaperTest2D, loadShape_missing_c2c_file_aborts) From 6c0ff813d927086c524862fada7e06d83f357ea5 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 May 2026 14:15:27 -0700 Subject: [PATCH 07/72] Added volume fraction creation functions. --- src/axom/quest/SamplingShaper.hpp | 430 +++++------------- .../detail/shaping/GenerateQuadratureMesh.hpp | 14 + .../quest/detail/shaping/shaping_helpers.cpp | 371 +++++++++++++++ .../quest/detail/shaping/shaping_helpers.hpp | 110 +++++ .../tests/quest_blueprint_quadrature_mesh.cpp | 104 +++++ 5 files changed, 725 insertions(+), 304 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 20f4993983..dc66435a4a 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -138,6 +138,24 @@ class SamplingShaper : public Shaper initializeSamplingMFEMState(); } +#if defined(AXOM_USE_CONDUIT) + SamplingShaper(RuntimePolicy execPolicy, + int allocatorId, + const klee::ShapeSet& shapeSet, + sidre::Group* bpMesh, + const std::string& topo = "") + : Shaper(execPolicy, allocatorId, shapeSet, bpMesh, topo) + { } + + SamplingShaper(RuntimePolicy execPolicy, + int allocatorId, + const klee::ShapeSet& shapeSet, + conduit::Node& bpNode, + const std::string& topo = "") + : Shaper(execPolicy, allocatorId, shapeSet, bpNode, topo) + { } +#endif + ~SamplingShaper() override = default; ///@{ @@ -513,100 +531,21 @@ class SamplingShaper : public Shaper internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug : slic::message::Warning); - - const auto& shapeName = shape.getName(); - const auto& thisMatName = shape.getMaterial(); - - SLIC_INFO_ROOT( - axom::fmt::format("{:-^80}", - axom::fmt::format("Applying replacement rules for shape '{}'", shapeName))); - - mfem::QuadratureFunction* shapeQFunc = nullptr; - - if(shape.getGeometry().hasGeometry()) - { - // Get inout qfunc for this shape - shapeQFunc = shapeQFuncs().Get(axom::fmt::format("inout_{}", shapeName)); - - SLIC_ERROR_IF(shapeQFunc == nullptr, - axom::fmt::format("Missing inout samples for shape '{}'. " - "This indicates the shape query did not produce a " - "quadrature field before replacement rules were applied.", - shapeName)); - } - else - { - // No input geometry for the shape, get inout qfunc for associated material - shapeQFunc = materialQFuncs().Get(axom::fmt::format("mat_inout_{}", thisMatName)); - - SLIC_ERROR_IF(shapeQFunc == nullptr, - axom::fmt::format("Missing inout samples for material '{}' while applying " - "replacement rules for shape '{}', which has no input " - "geometry. Initialize that material before shaping, e.g. " - "pass '--background-material {}' in the shaping driver or " - "import initial volume fractions for it.", - thisMatName, - shapeName, - thisMatName)); - } - - // Create a copy of the inout samples for this shape - // Replacements will be applied to this and then copied into our shape's material - auto* shapeQFuncCopy = new mfem::QuadratureFunction(*shapeQFunc); - - // apply replacement rules to all other materials - for(auto& otherMatName : m_knownMaterials) - { - // We'll handle the current shape's material at the end - if(otherMatName == thisMatName) - { - continue; - } - - const bool shouldReplace = shape.replaces(otherMatName); - SLIC_INFO_ROOT( - axom::fmt::format("Should we replace material '{}' with shape '{}' of material '{}'? {}", - otherMatName, - shapeName, - thisMatName, - shouldReplace ? "yes" : "no")); - - auto* otherMatQFunc = - materialQFuncs().Get(axom::fmt::format("mat_inout_{}", otherMatName)); - SLIC_ERROR_IF(otherMatQFunc == nullptr, - axom::fmt::format("Missing inout samples for material '{}' while applying " - "replacement rules for shape '{}'.", - otherMatName, - shapeName)); - - quest::shaping::replaceMaterial(shapeQFuncCopy, otherMatQFunc, shouldReplace); - } - - // Get inout qfunc for the current material - const std::string materialQFuncName = axom::fmt::format("mat_inout_{}", thisMatName); - if(!materialQFuncs().Has(materialQFuncName)) +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) { - // initialize material from shape inout, the QFunc registry takes ownership - materialQFuncs().Register(materialQFuncName, shapeQFuncCopy, true); + applyReplacementRulesImpl(samplingMFEMState(), shape); + return; } - else +#endif +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) { - // copy shape data into current material and delete the copy - auto* matQFunc = materialQFuncs().Get(materialQFuncName); - SLIC_ERROR_IF(matQFunc == nullptr, - axom::fmt::format("Missing inout samples for material '{}' while updating " - "the material field for shape '{}'.", - thisMatName, - shapeName)); - - const bool reuseExisting = shape.getGeometry().hasGeometry(); - quest::shaping::copyShapeIntoMaterial(shapeQFuncCopy, matQFunc, reuseExisting); - - delete shapeQFuncCopy; - shapeQFuncCopy = nullptr; + applyReplacementRulesImpl(*m_bp_state, shape); + return; } - - m_knownMaterials.insert(thisMatName); +#endif + SLIC_ERROR("No mesh state is available for SamplingShaper."); } void finalizeShapeQuery() override @@ -702,22 +641,23 @@ class SamplingShaper : public Shaper internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug : slic::message::Warning); - for(auto& mat : materialQFuncs()) - { - const std::string matName = mat.first; + auto computeVolumeFractions = [this](const std::string& matName) { SLIC_INFO_ROOT( axom::fmt::format("Generating volume fraction fields for '{}' material", matName)); - // Sample the InOut field at the mesh quadrature points switch(m_vfSampling) { case shaping::VolFracSampling::SAMPLE_AT_QPTS: this->computeVolumeFractionsForMaterial(matName); break; case shaping::VolFracSampling::SAMPLE_AT_DOFS: - /* no-op for now */ break; } + }; + + for(const auto& materialName : m_knownMaterials) + { + computeVolumeFractions(axom::fmt::format("mat_inout_{}", materialName)); } } @@ -980,240 +920,122 @@ class SamplingShaper : public Shaper SLIC_ERROR("No mesh state is available for SamplingShaper."); } - /** - * \brief Compute volume fractions for a given material using its associated quadrature function. - * - * The generated grid function will be registered in the data collection and prefixed by `vol_frac_` - * - * \param [in] matField The name of the material - */ - void computeVolumeFractionsForMaterial(const std::string& matField) + template + void applyReplacementRulesImpl(MeshState& meshState, const klee::Shape& shape) { - AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); - - // Retrieve the inout samples QFunc - SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); - mfem::QuadratureFunction* inout = materialQFuncs().Get(matField); - - const auto& sampleIR = inout->GetSpace()->GetIntRule(0); // assume all elements are the same - const int sampleOrder = sampleIR.GetOrder(); - const int sampleNQ = sampleIR.GetNPoints(); - const int sampleSZ = inout->GetSpace()->GetSize(); - - // extract some properties from computational mesh - mfem::Mesh* mesh = getDC()->GetMesh(); - const int dim = mesh->Dimension(); - const int NE = mesh->GetNE(); - const auto geom = mesh->GetTypicalElementGeometry(); + const auto& shapeName = shape.getName(); + const auto& thisMatName = shape.getMaterial(); - auto samples_per_dim = [=](int sampleRes[3], mfem::Geometry::Type geom) -> std::string { - switch(geom) - { - case mfem::Geometry::SQUARE: - return axom::fmt::format(" ({} * {})", sampleRes[0], sampleRes[1]); - case mfem::Geometry::CUBE: - return axom::fmt::format(" ({} * {} * {})", sampleRes[0], sampleRes[1], sampleRes[2]); - default: - return std::string(); - } - }; + SLIC_INFO_ROOT( + axom::fmt::format("{:-^80}", + axom::fmt::format("Applying replacement rules for shape '{}'", shapeName))); - // print info about sampling on rank 0 - // TODO: mpi reduce this for stats on all ranks - SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), - "In computeVolumeFractions(): num samples per element {}{} | " - "sample polynomial order {} | total samples {:L}", - sampleNQ, - samples_per_dim(m_sampleResolution, geom), - sampleOrder, - sampleSZ)); + auto* shapeFunc = shape.getGeometry().hasGeometry() + ? meshState.getShapeFunction(axom::fmt::format("inout_{}", shapeName)) + : meshState.getMaterialFunction(axom::fmt::format("mat_inout_{}", thisMatName)); - SLIC_INFO_ROOT( - axom::fmt::format(axom::utilities::locale(), "Mesh has dim {} and {:L} elements", dim, NE)); - - // Access or create a registered volume fraction grid function from the data collection - const auto vf_name = axom::fmt::format("vol_frac_{}", matField.substr(10)); - mfem::GridFunction* vf = shaping::getOrAllocateL2GridFunction(getDC(), - vf_name, - m_volfracOrder, - dim, - mfem::BasisType::Positive); - const mfem::FiniteElementSpace* fes = vf->FESpace(); - const int dofs = fes->GetTypicalFE()->GetDof(); - - // access or compute the mass matrix - mfem::DenseTensor* mass_mat {nullptr}; - const std::string mass_matrix_name = "shaping_mass_matrix"; - if(this->tensors().Has(mass_matrix_name)) + if(shape.getGeometry().hasGeometry()) { - mass_mat = tensors().Get(mass_matrix_name); + SLIC_ERROR_IF(shapeFunc == nullptr, + axom::fmt::format("Missing inout samples for shape '{}'. " + "This indicates the shape query did not produce a " + "quadrature field before replacement rules were applied.", + shapeName)); } else { - AXOM_ANNOTATE_SCOPE("mass integrator assemble"); - - mass_mat = new mfem::DenseTensor(dofs, dofs, NE); - mass_mat->HostWrite(); - (*mass_mat) = 0.; - mass_mat->ReadWrite(); + SLIC_ERROR_IF(shapeFunc == nullptr, + axom::fmt::format("Missing inout samples for material '{}' while applying " + "replacement rules for shape '{}', which has no input " + "geometry. Initialize that material before shaping, e.g. " + "pass '--background-material {}' in the shaping driver or " + "import initial volume fractions for it.", + thisMatName, + shapeName, + thisMatName)); + } - mfem::ConstantCoefficient one_coef(1.0); - mfem::MassIntegrator mass_integrator(one_coef, &sampleIR); + auto* shapeFuncCopy = quest::shaping::cloneInOutFunction(shapeFunc); - if(usesAnisotropicCustomTensorQuadrature(*fes->GetMesh())) - { - mfem::DenseMatrix elemMat; - mass_mat->HostWrite(); - for(int elem = 0; elem < NE; ++elem) - { - mass_integrator.AssembleElementMatrix(*fes->GetFE(elem), - *fes->GetElementTransformation(elem), - elemMat); - for(int j = 0; j < dofs; ++j) - { - for(int i = 0; i < dofs; ++i) - { - (*mass_mat)(i, j, elem) = elemMat(i, j); - } - } - } - } - else + for(auto& otherMatName : m_knownMaterials) + { + if(otherMatName == thisMatName) { - const int sz = mass_mat->TotalSize(); - - // wrap mass_mat data as vector for AssembleEA call - // note: AssembleEA expects the transpose, but it's ok since mass matrices are symmetric - mfem::Vector mass_vec; - mfem::Swap(mass_mat->GetMemory(), mass_vec.GetMemory()); - mass_vec.SetSize(sz); - mass_integrator.AssembleEA(*fes, mass_vec, false); - mfem::Swap(mass_mat->GetMemory(), mass_vec.GetMemory()); + continue; } - tensors().Register(mass_matrix_name, mass_mat, true); - } - SLIC_ASSERT(mass_mat->SizeI() == dofs); - SLIC_ASSERT(mass_mat->SizeJ() == dofs); - SLIC_ASSERT(mass_mat->SizeK() == NE); - - // access or compute LU factorization of the mass matrix - mfem::DenseTensor* mass_mat_inv {nullptr}; - mfem::Array* mass_mat_pivots {nullptr}; - const std::string minv_name = "shaping_mass_matrix_inv"; - const std::string pivots_name = "shaping_mass_matrix_pivots"; - if(this->tensors().Has(minv_name) && this->arrays().Has(pivots_name)) - { - mass_mat_inv = this->tensors().Get(minv_name); - mass_mat_pivots = this->arrays().Get(pivots_name); - } - else - { - AXOM_ANNOTATE_SCOPE("batch lu factor"); - - // Perform batched LU factorization on the mass tensor - mass_mat->ReadWrite(); - mass_mat_inv = new mfem::DenseTensor(*mass_mat); - mass_mat_pivots = new mfem::Array(dofs * NE); + const bool shouldReplace = shape.replaces(otherMatName); + SLIC_INFO_ROOT( + axom::fmt::format("Should we replace material '{}' with shape '{}' of material '{}'? {}", + otherMatName, + shapeName, + thisMatName, + shouldReplace ? "yes" : "no")); - mass_mat_inv->ReadWrite(); - mass_mat_pivots->Write(); - mfem::BatchLUFactor(*mass_mat_inv, *mass_mat_pivots); + auto* otherMatFunc = + meshState.getMaterialFunction(axom::fmt::format("mat_inout_{}", otherMatName)); + SLIC_ERROR_IF(otherMatFunc == nullptr, + axom::fmt::format("Missing inout samples for material '{}' while applying " + "replacement rules for shape '{}'.", + otherMatName, + shapeName)); - tensors().Register(minv_name, mass_mat_inv, true); - arrays().Register(pivots_name, mass_mat_pivots, true); + quest::shaping::replaceMaterial(shapeFuncCopy, otherMatFunc, shouldReplace); } - SLIC_ASSERT(mass_mat_inv->SizeJ() == dofs); - SLIC_ASSERT(mass_mat_inv->SizeI() == dofs); - SLIC_ASSERT(mass_mat_inv->SizeK() == NE); - SLIC_ASSERT(mass_mat_pivots->Size() == dofs * NE); - - mfem::DenseTensor* shaping_scratch_buffer {nullptr}; - const std::string scratch_buffer_name = "shaping_scratch_buffer"; - if(this->tensors().Has(scratch_buffer_name)) - { - shaping_scratch_buffer = this->tensors().Get(scratch_buffer_name); - } - else - { - shaping_scratch_buffer = new mfem::DenseTensor(dofs, dofs, NE); - // TODO -- we only need this buffer to be Write - // and only in the space that FCT_project is called - shaping_scratch_buffer->HostWrite(); - (*shaping_scratch_buffer) = 0.; - tensors().Register(scratch_buffer_name, shaping_scratch_buffer, true); - } - SLIC_ASSERT(shaping_scratch_buffer->SizeJ() == dofs); - SLIC_ASSERT(shaping_scratch_buffer->SizeI() == dofs); - SLIC_ASSERT(shaping_scratch_buffer->SizeK() == NE); + const std::string materialFunctionName = axom::fmt::format("mat_inout_{}", thisMatName); + auto* materialFunc = meshState.getMaterialFunction(materialFunctionName); + const bool hadExistingMaterial = (materialFunc != nullptr); - // Project QField onto volume fractions field using flux corrected transport (FCT) - // to keep the range of values between 0 and 1 - axom::utilities::Timer timer(true); + if(!hadExistingMaterial) { - // assemble the right hand side integral, incorporating the inout samples - mfem::Vector b(fes->GetVSize()); - SLIC_ASSERT(b.Size() == dofs * NE); - { - AXOM_ANNOTATE_SCOPE("domain lf integrator assemble"); + materialFunc = meshState.createMaterialFunction(materialFunctionName); + } - inout->ReadWrite(); + SLIC_ERROR_IF(materialFunc == nullptr, + axom::fmt::format("Missing inout samples for material '{}' while updating " + "the material field for shape '{}'.", + thisMatName, + shapeName)); - b.HostWrite(); - b = 0.; - b.ReadWrite(); + const bool reuseExisting = hadExistingMaterial && shape.getGeometry().hasGeometry(); + quest::shaping::copyShapeIntoMaterial(shapeFuncCopy, materialFunc, reuseExisting); - this->assembleVolumeFractionRHS(*fes, *inout, sampleIR, b); - } - inout->HostReadWrite(); + delete shapeFuncCopy; + shapeFuncCopy = nullptr; - { - AXOM_ANNOTATE_SCOPE("batch lu solve"); - - mass_mat_inv->Read(); - mass_mat_pivots->Read(); + m_knownMaterials.insert(thisMatName); + } - vf->HostReadWrite(); - (*vf) = b; - vf->ReadWrite(); - mfem::BatchLUSolve(*mass_mat_inv, *mass_mat_pivots, *vf); - } - mass_mat_inv->HostReadWrite(); - mass_mat_pivots->HostReadWrite(); - - constexpr double minY = 0.; - constexpr double maxY = 1.; - - // Reshape returns an indexable view of a multidimensional array - auto m_d = mfem::Reshape(mass_mat->HostReadWrite(), dofs, dofs, NE); - auto b_d = mfem::Reshape(b.HostReadWrite(), dofs, NE); - auto vf_d = mfem::Reshape(vf->HostReadWrite(), dofs, NE); - auto fct_mat_d = mfem::Reshape(shaping_scratch_buffer->HostReadWrite(), dofs, dofs, NE); - - AXOM_ANNOTATE_BEGIN("fct project"); - axom::for_all(0, NE, [=](int i) { - shaping::FCT_correct(&m_d(0, 0, i), - dofs, - &b_d(0, i), - minY, - maxY, - &vf_d(0, i), - &fct_mat_d(0, 0, i)); - }); - AXOM_ANNOTATE_END("fct project"); + /** + * \brief Compute volume fractions for a given material using its associated quadrature function. + * + * The generated grid function will be registered in the data collection and prefixed by `vol_frac_` + * + * \param [in] matField The name of the material + */ + void computeVolumeFractionsForMaterial(const std::string& matField) + { +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) + { + shaping::computeVolumeFractionsForMaterial( + samplingMFEMState(), + matField, + m_volfracOrder, + m_sampleResolution, + m_quadratureType); + return; } - timer.stop(); - - // print stats for root rank - SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), - "\t Generating volume fractions '{}' took {:.3f} seconds (@ " - "{:L} dofs processed per second)", - vf_name, - timer.elapsed(), - static_cast(fes->GetNDofs() / timer.elapsed()))); - - vf->HostReadWrite(); +#endif +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + shaping::computeVolumeFractionsForMaterial(*m_bp_state, matField); + return; + } +#endif + SLIC_ERROR("No mesh state is available for SamplingShaper."); } bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh) const diff --git a/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp b/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp index 38c375afbb..222546667c 100644 --- a/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp +++ b/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp @@ -137,6 +137,7 @@ class GenerateQuadratureMesh const std::string& outputTopologyName, const std::string& outputCoordsetName, const std::string& originalElementsFieldName, + const std::string& quadratureWeightsFieldName, const numerics::QuadratureRule& ruleX, const numerics::QuadratureRule& ruleY, const numerics::QuadratureRule& ruleZ, @@ -197,6 +198,15 @@ class GenerateQuadratureMesh n_originalValues.set(conduit::DataType::index_t(numPoints)); auto originalElements = utils::make_array_view(n_originalValues); + conduit::Node& n_quadratureWeights = n_output["fields/" + quadratureWeightsFieldName]; + n_quadratureWeights.reset(); + n_quadratureWeights["association"] = "element"; + n_quadratureWeights["topology"] = outputTopologyName; + conduit::Node& n_weightValues = n_quadratureWeights["values"]; + n_weightValues.set_allocator(conduitAllocatorId); + n_weightValues.set(conduit::DataType::float64(numPoints)); + auto quadratureWeights = utils::make_array_view(n_weightValues); + const TopologyView deviceTopoView(m_topologyView); const CoordsetView deviceCoordsetView(m_coordsetView); @@ -209,12 +219,15 @@ class GenerateQuadratureMesh for(int kz = 0; kz < (dim == 3 ? ruleZ.getNumPoints() : 1); ++kz) { const double zeta = dim == 3 ? ruleZ.node(kz) : 0.0; + const double wz = dim == 3 ? ruleZ.weight(kz) : 1.0; for(int jy = 0; jy < ruleY.getNumPoints(); ++jy) { const double eta = ruleY.node(jy); + const double wy = ruleY.weight(jy); for(int ix = 0; ix < ruleX.getNumPoints(); ++ix) { const double xi = ruleX.node(ix); + const double wx = ruleX.weight(ix); PointType pt; if constexpr(CoordsetView::dimension() == 2) @@ -234,6 +247,7 @@ class GenerateQuadratureMesh sizes[pointIndex] = 1; offsets[pointIndex] = pointIndex; originalElements[pointIndex] = zoneIndex; + quadratureWeights[pointIndex] = wx * wy * wz; ++pointIndex; } } diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index bb2c9e576e..82d14656d6 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -39,6 +39,7 @@ namespace constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; +constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureType quadratureType, int npts, @@ -75,6 +76,7 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, QUADRATURE_TOPOLOGY_NAME, QUADRATURE_COORDSET_NAME, ORIGINAL_ELEMENTS_FIELD_NAME, + QUADRATURE_WEIGHTS_FIELD_NAME, ruleX, ruleY, ruleZ, @@ -258,6 +260,12 @@ void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, } } +mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfunc) +{ + SLIC_ASSERT(qfunc != nullptr); + return new mfem::QuadratureFunction(*qfunc); +} + mfem::QuadratureSpace* makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRes) { SLIC_ASSERT(mesh != nullptr); @@ -339,6 +347,37 @@ mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, return new OwnedQuadratureSpace(*mesh, std::move(ir)); } +void assembleVolumeFractionRHS(const mfem::FiniteElementSpace& fes, + mfem::QuadratureFunction& inout, + const mfem::IntegrationRule& sampleIR, + bool useAnisotropicAssembly, + mfem::Vector& b) +{ + mfem::QuadratureFunctionCoefficient qfc(inout); + mfem::DomainLFIntegrator rhs(qfc, &sampleIR); + + if(useAnisotropicAssembly) + { + mfem::Vector elemVec; + mfem::Array elemVDofs; + + for(int elem = 0; elem < fes.GetNE(); ++elem) + { + rhs.AssembleRHSElementVect(*fes.GetFE(elem), *fes.GetElementTransformation(elem), elemVec); + fes.GetElementVDofs(elem, elemVDofs); + b.AddElementVector(elemVDofs, elemVec); + } + } + else + { + mfem::Array elem_marker(fes.GetNE()); + elem_marker.HostWrite(); + elem_marker = 1; + elem_marker.ReadWrite(); + rhs.AssembleDevice(fes, elem_marker, b); + } +} + /// Generates a quadrature function corresponding to the mesh "positions" field void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, @@ -437,6 +476,216 @@ void generateSamplingPositions(SamplingMFEMState& mfemState, quadratureType); } +void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, + const std::string& matField, + int volfracOrder, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType) +{ + AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); + + SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); + auto* inout = mfemState.getMaterialFunction(matField); + SLIC_ASSERT(inout != nullptr); + + auto* dc = mfemState.m_dc; + SLIC_ASSERT(dc != nullptr); + + const auto& sampleIR = inout->GetSpace()->GetIntRule(0); + const int sampleOrder = sampleIR.GetOrder(); + const int sampleNQ = sampleIR.GetNPoints(); + const int sampleSZ = inout->GetSpace()->GetSize(); + + mfem::Mesh* mesh = dc->GetMesh(); + const int dim = mesh->Dimension(); + const int NE = mesh->GetNE(); + const auto geom = mesh->GetTypicalElementGeometry(); + + auto samples_per_dim = [=](int sampleRes[3], mfem::Geometry::Type geomType) -> std::string { + switch(geomType) + { + case mfem::Geometry::SQUARE: + return axom::fmt::format(" ({} * {})", sampleRes[0], sampleRes[1]); + case mfem::Geometry::CUBE: + return axom::fmt::format(" ({} * {} * {})", sampleRes[0], sampleRes[1], sampleRes[2]); + default: + return std::string(); + } + }; + + SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), + "In computeVolumeFractions(): num samples per element {}{} | " + "sample polynomial order {} | total samples {:L}", + sampleNQ, + samples_per_dim(sampleResolution, geom), + sampleOrder, + sampleSZ)); + + SLIC_INFO_ROOT( + axom::fmt::format(axom::utilities::locale(), "Mesh has dim {} and {:L} elements", dim, NE)); + + const auto vf_name = axom::fmt::format("vol_frac_{}", matField.substr(10)); + mfem::GridFunction* vf = + getOrAllocateL2GridFunction(dc, vf_name, volfracOrder, dim, mfem::BasisType::Positive); + const mfem::FiniteElementSpace* fes = vf->FESpace(); + const int dofs = fes->GetTypicalFE()->GetDof(); + + mfem::DenseTensor* mass_mat {nullptr}; + const std::string mass_matrix_name = "shaping_mass_matrix"; + if(mfemState.m_inoutTensors.Has(mass_matrix_name)) + { + mass_mat = mfemState.m_inoutTensors.Get(mass_matrix_name); + } + else + { + AXOM_ANNOTATE_SCOPE("mass integrator assemble"); + + mass_mat = new mfem::DenseTensor(dofs, dofs, NE); + mass_mat->HostWrite(); + (*mass_mat) = 0.; + mass_mat->ReadWrite(); + + mfem::ConstantCoefficient one_coef(1.0); + mfem::MassIntegrator mass_integrator(one_coef, &sampleIR); + + if(usesAnisotropicCustomTensorQuadrature(*fes->GetMesh(), sampleResolution, quadratureType)) + { + mfem::DenseMatrix elemMat; + mass_mat->HostWrite(); + for(int elem = 0; elem < NE; ++elem) + { + mass_integrator.AssembleElementMatrix(*fes->GetFE(elem), + *fes->GetElementTransformation(elem), + elemMat); + for(int j = 0; j < dofs; ++j) + { + for(int i = 0; i < dofs; ++i) + { + (*mass_mat)(i, j, elem) = elemMat(i, j); + } + } + } + } + else + { + const int sz = mass_mat->TotalSize(); + mfem::Vector mass_vec; + mfem::Swap(mass_mat->GetMemory(), mass_vec.GetMemory()); + mass_vec.SetSize(sz); + mass_integrator.AssembleEA(*fes, mass_vec, false); + mfem::Swap(mass_mat->GetMemory(), mass_vec.GetMemory()); + } + + mfemState.m_inoutTensors.Register(mass_matrix_name, mass_mat, true); + } + + mfem::DenseTensor* mass_mat_inv {nullptr}; + mfem::Array* mass_mat_pivots {nullptr}; + const std::string minv_name = "shaping_mass_matrix_inv"; + const std::string pivots_name = "shaping_mass_matrix_pivots"; + if(mfemState.m_inoutTensors.Has(minv_name) && mfemState.m_inoutArrays.Has(pivots_name)) + { + mass_mat_inv = mfemState.m_inoutTensors.Get(minv_name); + mass_mat_pivots = mfemState.m_inoutArrays.Get(pivots_name); + } + else + { + AXOM_ANNOTATE_SCOPE("batch lu factor"); + + mass_mat->ReadWrite(); + mass_mat_inv = new mfem::DenseTensor(*mass_mat); + mass_mat_pivots = new mfem::Array(dofs * NE); + + mass_mat_inv->ReadWrite(); + mass_mat_pivots->Write(); + mfem::BatchLUFactor(*mass_mat_inv, *mass_mat_pivots); + + mfemState.m_inoutTensors.Register(minv_name, mass_mat_inv, true); + mfemState.m_inoutArrays.Register(pivots_name, mass_mat_pivots, true); + } + + mfem::DenseTensor* shaping_scratch_buffer {nullptr}; + const std::string scratch_buffer_name = "shaping_scratch_buffer"; + if(mfemState.m_inoutTensors.Has(scratch_buffer_name)) + { + shaping_scratch_buffer = mfemState.m_inoutTensors.Get(scratch_buffer_name); + } + else + { + shaping_scratch_buffer = new mfem::DenseTensor(dofs, dofs, NE); + shaping_scratch_buffer->HostWrite(); + (*shaping_scratch_buffer) = 0.; + mfemState.m_inoutTensors.Register(scratch_buffer_name, shaping_scratch_buffer, true); + } + + axom::utilities::Timer timer(true); + { + mfem::Vector b(fes->GetVSize()); + SLIC_ASSERT(b.Size() == dofs * NE); + { + AXOM_ANNOTATE_SCOPE("domain lf integrator assemble"); + + inout->ReadWrite(); + b.HostWrite(); + b = 0.; + b.ReadWrite(); + + assembleVolumeFractionRHS(*fes, + *inout, + sampleIR, + usesAnisotropicCustomTensorQuadrature(*fes->GetMesh(), + sampleResolution, + quadratureType), + b); + } + inout->HostReadWrite(); + + { + AXOM_ANNOTATE_SCOPE("batch lu solve"); + + mass_mat_inv->Read(); + mass_mat_pivots->Read(); + + vf->HostReadWrite(); + (*vf) = b; + vf->ReadWrite(); + mfem::BatchLUSolve(*mass_mat_inv, *mass_mat_pivots, *vf); + } + mass_mat_inv->HostReadWrite(); + mass_mat_pivots->HostReadWrite(); + + constexpr double minY = 0.; + constexpr double maxY = 1.; + + auto m_d = mfem::Reshape(mass_mat->HostReadWrite(), dofs, dofs, NE); + auto b_d = mfem::Reshape(b.HostReadWrite(), dofs, NE); + auto vf_d = mfem::Reshape(vf->HostReadWrite(), dofs, NE); + auto fct_mat_d = mfem::Reshape(shaping_scratch_buffer->HostReadWrite(), dofs, dofs, NE); + + AXOM_ANNOTATE_BEGIN("fct project"); + axom::for_all(0, NE, [=](int i) { + FCT_correct(&m_d(0, 0, i), + dofs, + &b_d(0, i), + minY, + maxY, + &vf_d(0, i), + &fct_mat_d(0, 0, i)); + }); + AXOM_ANNOTATE_END("fct project"); + } + timer.stop(); + + SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), + "\t Generating volume fractions '{}' took {:.3f} seconds (@ " + "{:L} dofs processed per second)", + vf_name, + timer.elapsed(), + static_cast(fes->GetNDofs() / timer.elapsed()))); + + vf->HostReadWrite(); +} + #if defined(AXOM_USE_CONDUIT) void generateQuadraturePointMesh(conduit::Node& bpMeshNode, const std::string& topologyName, @@ -555,6 +804,128 @@ void generateSamplingPositions(BlueprintState& bpState, sampleResolution, quadratureType); } + +void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField) +{ + AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); + + SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); + + conduit::Node* inout = bpState.getMaterialFunction(matField); + SLIC_ERROR_IF(inout == nullptr, + axom::fmt::format("Missing Blueprint material field '{}' for volume fraction projection.", + matField)); + + conduit::Node& bpMeshNode = bpState.m_internal_node; + SLIC_ERROR_IF(!bpMeshNode.has_path("fields/originalElements/values"), + "Missing Blueprint originalElements field for volume fraction projection."); + SLIC_ERROR_IF(!bpMeshNode.has_path("fields/quadratureWeights/values"), + "Missing Blueprint quadratureWeights field for volume fraction projection."); + + const conduit::Node& topoNode = + bpMeshNode.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); + + axom::IndexType numZones = 0; + axom::bump::views::dispatch_unstructured_topology( + topoNode, [&](const auto&, auto topoView) { numZones = topoView.numberOfZones(); }); + + namespace utils = axom::bump::utilities; + const auto originalElements = + utils::make_array_view(bpMeshNode["fields/originalElements/values"]); + const auto quadratureWeights = + utils::make_array_view(bpMeshNode["fields/quadratureWeights/values"]); + const auto inoutValues = utils::make_array_view(inout->fetch_existing("values")); + + SLIC_ASSERT(originalElements.size() == quadratureWeights.size()); + SLIC_ASSERT(originalElements.size() == inoutValues.size()); + + const std::string vfName = axom::fmt::format("vol_frac_{}", matField.substr(10)); + conduit::Node& vfNode = bpMeshNode["fields/" + vfName]; + vfNode.reset(); + vfNode["association"] = "element"; + vfNode["topology"] = bpState.m_topology_name; + + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); + conduit::Node& valuesNode = vfNode["values"]; + valuesNode.set_allocator(conduitAllocatorId); + valuesNode.set(conduit::DataType::float64(numZones)); + auto vfValues = utils::make_array_view(valuesNode); + + for(axom::IndexType zoneIdx = 0; zoneIdx < vfValues.size(); ++zoneIdx) + { + vfValues[zoneIdx] = 0.; + } + + for(axom::IndexType pointIdx = 0; pointIdx < inoutValues.size(); ++pointIdx) + { + const conduit::index_t zoneIdx = originalElements[pointIdx]; + SLIC_ASSERT(zoneIdx >= 0); + SLIC_ASSERT(zoneIdx < vfValues.size()); + vfValues[zoneIdx] += inoutValues[pointIdx] * quadratureWeights[pointIdx]; + } +} + +void replaceMaterial(conduit::Node* shapeNode, + conduit::Node* materialNode, + bool shapeReplacesMaterial) +{ + SLIC_ASSERT(shapeNode != nullptr); + SLIC_ASSERT(materialNode != nullptr); + + namespace utils = axom::bump::utilities; + auto shapeValues = utils::make_array_view(shapeNode->fetch_existing("values")); + auto materialValues = utils::make_array_view(materialNode->fetch_existing("values")); + + SLIC_ASSERT(shapeValues.size() == materialValues.size()); + + for(axom::IndexType i = 0; i < materialValues.size(); ++i) + { + if(shapeReplacesMaterial) + { + materialValues[i] = shapeValues[i] > 0. ? 0. : materialValues[i]; + } + else + { + shapeValues[i] = materialValues[i] > 0. ? 0. : shapeValues[i]; + } + } +} + +void copyShapeIntoMaterial(const conduit::Node* shapeNode, + conduit::Node* materialNode, + bool reuseExisting) +{ + SLIC_ASSERT(shapeNode != nullptr); + SLIC_ASSERT(materialNode != nullptr); + + namespace utils = axom::bump::utilities; + const auto shapeValues = utils::make_array_view(shapeNode->fetch_existing("values")); + auto materialValues = utils::make_array_view(materialNode->fetch_existing("values")); + + SLIC_ASSERT(shapeValues.size() == materialValues.size()); + + if(reuseExisting) + { + for(axom::IndexType i = 0; i < materialValues.size(); ++i) + { + materialValues[i] = shapeValues[i] > 0. ? 1. : materialValues[i]; + } + } + else + { + for(axom::IndexType i = 0; i < materialValues.size(); ++i) + { + materialValues[i] = shapeValues[i]; + } + } +} + +conduit::Node* cloneInOutFunction(const conduit::Node* node) +{ + SLIC_ASSERT(node != nullptr); + return new conduit::Node(*node); +} #endif void FCT_correct(const double* M, // Mass matrix diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index c858ae024e..f88b28c448 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -191,6 +191,40 @@ struct SamplingMFEMState : public MFEMState m_inoutArrays.clear(); } + mfem::QuadratureFunction* getShapeFunction(const std::string& name) + { + return m_inoutShapeQFuncs.Get(name); + } + + const mfem::QuadratureFunction* getShapeFunction(const std::string& name) const + { + return m_inoutShapeQFuncs.Get(name); + } + + mfem::QuadratureFunction* getMaterialFunction(const std::string& name) + { + return m_inoutMaterialQFuncs.Get(name); + } + + const mfem::QuadratureFunction* getMaterialFunction(const std::string& name) const + { + return m_inoutMaterialQFuncs.Get(name); + } + + mfem::QuadratureFunction* createMaterialFunction(const std::string& name) + { + auto* positions = m_inoutShapeQFuncs.Get("positions"); + SLIC_ERROR_IF(positions == nullptr, + std::string("Cannot create material function '") + name + + "' without positions."); + + auto* qfunc = new mfem::QuadratureFunction(positions->GetSpace(), 1); + qfunc->HostWrite(); + *qfunc = 0.; + m_inoutMaterialQFuncs.Register(name, qfunc, true); + return qfunc; + } + QFunctionCollection m_inoutShapeQFuncs; QFunctionCollection m_inoutMaterialQFuncs; DenseTensorCollection m_inoutTensors; @@ -211,6 +245,61 @@ struct BlueprintState conduit::Node* m_external_node_ptr {nullptr}; //! @brief Internal Node representation used for blueprint operations. conduit::Node m_internal_node; + + conduit::Node* getShapeFunction(const std::string& name) + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + const conduit::Node* getShapeFunction(const std::string& name) const + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + conduit::Node* getMaterialFunction(const std::string& name) + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + const conduit::Node* getMaterialFunction(const std::string& name) const + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + conduit::Node* createMaterialFunction(const std::string& name) + { + constexpr const char* quadratureTopologyName = "quadrature_points"; + SLIC_ERROR_IF(!m_internal_node.has_path("coordsets/quadrature_points/values"), + std::string("Cannot create material function '") + name + + "' without quadrature points."); + + conduit::Node& fieldNode = m_internal_node["fields/" + name]; + fieldNode.reset(); + fieldNode["association"] = "element"; + fieldNode["topology"] = quadratureTopologyName; + + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); + conduit::Node& valuesNode = fieldNode["values"]; + valuesNode.set_allocator(conduitAllocatorId); + + const conduit::Node& values = + m_internal_node["coordsets/quadrature_points"].fetch_existing("values"); + const auto numValues = values.child(0).dtype().number_of_elements(); + valuesNode.set(conduit::DataType::float64(numValues)); + + auto fieldValues = axom::bump::utilities::make_array_view(valuesNode); + for(axom::IndexType i = 0; i < fieldValues.size(); ++i) + { + fieldValues[i] = 0.; + } + + return &fieldNode; + } }; #endif @@ -261,6 +350,19 @@ void replaceMaterial(mfem::QuadratureFunction* shapeQFunc, void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, mfem::QuadratureFunction* materialQFunc, bool reuseExisting = true); + +mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfunc); + +#if defined(AXOM_USE_CONDUIT) +void replaceMaterial(conduit::Node* shapeNode, conduit::Node* materialNode, bool shouldReplace); + +void copyShapeIntoMaterial(const conduit::Node* shapeNode, + conduit::Node* materialNode, + bool reuseExisting = true); + +conduit::Node* cloneInOutFunction(const conduit::Node* node); +#endif + /** * \brief Generates a "position" quadrature function corresponding to the mesh positions and * store it in \a inoutQFuncs. @@ -286,6 +388,12 @@ void generateSamplingPositions(SamplingMFEMState& mfemState, int sampleResolution[3], axom::numerics::QuadratureType quadratureType); +void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, + const std::string& matField, + int volfracOrder, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType); + #if defined(AXOM_USE_CONDUIT) /** * \brief Generates a derived Blueprint quadrature point mesh within the @@ -311,6 +419,8 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, void generateSamplingPositions(BlueprintState& bpState, int sampleResolution[3], axom::numerics::QuadratureType quadratureType); + +void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField); #endif /** diff --git a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp index 3e87788a7b..be22872d6a 100644 --- a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp +++ b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp @@ -11,9 +11,11 @@ #include "gtest/gtest.h" #include "axom/core.hpp" + #include "axom/quest/SamplingShaper.hpp" #include "axom/quest/detail/shaping/shaping_helpers.hpp" #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/bump/views/dispatch_coordset.hpp" + #include "axom/sidre.hpp" #include "conduit.hpp" #include "conduit_blueprint.hpp" @@ -129,6 +131,8 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) mesh["topologies/quadrature_points/elements/offsets"]); const auto originalElementsView = utils::make_array_view(mesh["fields/originalElements/values"]); + const auto quadratureWeightsView = + utils::make_array_view(mesh["fields/quadratureWeights/values"]); const axom::Array expectedX {{0., 1., 0., 1., 0., 1.}}; const axom::Array expectedY {{0., 0., 0.5, 0.5, 1., 1.}}; @@ -136,6 +140,7 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) const axom::Array expectedSizes {{1, 1, 1, 1, 1, 1}}; const axom::Array expectedOffsets {{0, 1, 2, 3, 4, 5}}; const axom::Array expectedOriginalElements {{0, 0, 0, 0, 0, 0}}; + const axom::Array expectedWeights {{1. / 12., 1. / 12., 1. / 3., 1. / 3., 1. / 12., 1. / 12.}}; axom::bump::views::dispatch_explicit_coordset( mesh["coordsets/quadrature_points"], [&](auto coordsetView) { @@ -149,6 +154,10 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) EXPECT_TRUE(compareArrayView(expectedSizes.view(), sizesView)); EXPECT_TRUE(compareArrayView(expectedOffsets.view(), offsetsView)); EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); + for(axom::IndexType i = 0; i < expectedWeights.size(); ++i) + { + EXPECT_NEAR(expectedWeights[i], quadratureWeightsView[i], 1e-12); + } } TEST(quest_blueprint_quadrature_mesh, generate_open_uniform_hex_mesh) @@ -227,4 +236,99 @@ TEST(quest_blueprint_quadrature_mesh, state_wrapper_generation_is_idempotent) EXPECT_TRUE(compareArrayView(savedOriginalElementsView, originalElementsView)); } +TEST(quest_blueprint_quadrature_mesh, blueprint_state_field_helpers_support_replacement_ops) +{ + conduit::Node mesh = makeQuadMesh(); + + axom::quest::shaping::BlueprintState bpState; + bpState.m_allocator_id = axom::execution_space::allocatorID(); + bpState.m_topology_name = "mesh"; + bpState.m_internal_node = mesh; + + int sampleResolution[3] = {2, 2, 1}; + axom::quest::shaping::generateSamplingPositions( + bpState, + sampleResolution, + axom::numerics::QuadratureType::ClosedUniform); + + conduit::Node& shapeField = bpState.m_internal_node["fields/inout_shape"]; + shapeField["association"] = "element"; + shapeField["topology"] = "quadrature_points"; + const axom::Array shapeValues {{1., 0., 1., 0.}}; + setNodeValues(shapeField["values"], shapeValues.view()); + + conduit::Node* materialField = bpState.createMaterialFunction("mat_inout_void"); + ASSERT_NE(materialField, nullptr); + const axom::Array materialValues {{1., 1., 0., 0.}}; + setNodeValues((*materialField)["values"], materialValues.view()); + + conduit::Node* shapeCopy = + axom::quest::shaping::cloneInOutFunction(bpState.getShapeFunction("inout_shape")); + ASSERT_NE(shapeCopy, nullptr); + + axom::quest::shaping::replaceMaterial(shapeCopy, materialField, true); + + namespace utils = axom::bump::utilities; + const auto replacedView = utils::make_array_view((*materialField)["values"]); + const axom::Array expectedReplaced {{0., 1., 0., 0.}}; + EXPECT_TRUE(compareArrayView(expectedReplaced.view(), replacedView)); + + conduit::Node* createdField = bpState.createMaterialFunction("mat_inout_created"); + ASSERT_NE(createdField, nullptr); + axom::quest::shaping::copyShapeIntoMaterial(shapeCopy, createdField, false); + + const auto copiedView = utils::make_array_view((*createdField)["values"]); + EXPECT_TRUE(compareArrayView(shapeValues.view(), copiedView)); + + delete shapeCopy; +} + +TEST(quest_blueprint_quadrature_mesh, compute_volume_fractions_for_material_from_quadrature_weights) +{ + conduit::Node mesh = makeQuadMesh(); + + axom::quest::shaping::BlueprintState bpState; + bpState.m_allocator_id = axom::execution_space::allocatorID(); + bpState.m_topology_name = "mesh"; + bpState.m_internal_node = mesh; + + int sampleResolution[3] = {2, 2, 1}; + axom::quest::shaping::generateSamplingPositions( + bpState, + sampleResolution, + axom::numerics::QuadratureType::ClosedUniform); + + conduit::Node* materialField = bpState.createMaterialFunction("mat_inout_test"); + ASSERT_NE(materialField, nullptr); + + const axom::Array materialValues {{1., 0., 1., 0.}}; + setNodeValues((*materialField)["values"], materialValues.view()); + + axom::quest::shaping::computeVolumeFractionsForMaterial(bpState, "mat_inout_test"); + + ASSERT_TRUE(bpState.m_internal_node.has_path("fields/vol_frac_test/values")); + namespace utils = axom::bump::utilities; + const auto volFracValues = + utils::make_array_view(bpState.m_internal_node["fields/vol_frac_test/values"]); + + ASSERT_EQ(volFracValues.size(), 1); + EXPECT_NEAR(volFracValues[0], 0.5, 1e-12); +} + +TEST(quest_blueprint_quadrature_mesh, sampling_shaper_constructs_from_blueprint_node_and_group) +{ + conduit::Node mesh = makeQuadMesh(); + axom::klee::ShapeSet shapeSet; + const auto policy = axom::runtime_policy::Policy::seq; + const int allocatorId = axom::policyToDefaultAllocatorID(policy); + + axom::quest::SamplingShaper nodeShaper(policy, allocatorId, shapeSet, mesh, "mesh"); + + axom::sidre::DataStore ds; + auto* meshGroup = ds.getRoot()->createGroup("mesh"); + ASSERT_TRUE(meshGroup->importConduitTree(mesh)); + + axom::quest::SamplingShaper groupShaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); +} + #endif From 8c99b7e308ae5830569594dd58fc26aec7ab0914 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 May 2026 17:05:33 -0700 Subject: [PATCH 08/72] Handle structured topologies in SamplingShaper. --- src/axom/quest/IntersectionShaper.hpp | 41 +- src/axom/quest/SamplingShaper.hpp | 82 ++-- src/axom/quest/Shaper.cpp | 240 +++++++--- src/axom/quest/Shaper.hpp | 71 ++- .../detail/shaping/GenerateQuadratureMesh.hpp | 150 +++---- .../quest/detail/shaping/shaping_helpers.cpp | 223 +++++++++- .../quest/detail/shaping/shaping_helpers.hpp | 31 ++ .../quest/interface/internal/QuestHelpers.cpp | 105 ++++- .../tests/quest_blueprint_quadrature_mesh.cpp | 417 +++++++++++++++++- src/axom/quest/util/mesh_helpers.cpp | 11 +- 10 files changed, 1151 insertions(+), 220 deletions(-) diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 267c1c22a6..59e56449a9 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -381,6 +381,29 @@ class IntersectionShaper : public Shaper { } #endif +protected: + bool verifyInputMeshImpl(std::string& whyBad) const override + { + bool rval = true; + +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + rval = verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(whyBad); + } +#endif + +#if defined(AXOM_USE_MFEM) + if(getDC() != nullptr) + { + rval = verifyMFEMInputMesh(whyBad); + } +#endif + + return rval; + } + +public: //!@brief Set data that depends on mesh (but not on shapes). template void setMeshDependentData() @@ -1795,6 +1818,13 @@ class IntersectionShaper : public Shaper AXOM_ANNOTATE_SCOPE("runShapeQuery"); const std::string shapeFormat = shape.getGeometry().getFormat(); +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + ensureBlueprintMeshIsUnstructured(); + } +#endif + // C2C mesh is not discretized into tets, but all others are. if(surfaceMeshIsTet()) { @@ -2976,16 +3006,7 @@ class IntersectionShaper : public Shaper #if defined(AXOM_USE_CONDUIT) if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) { - std::string mesh_type = - m_bp_state->m_group_ptr->getView("topologies/mesh/elements/shape")->getString(); - if(mesh_type == "hex") - { - dim = 3; - } - else if(mesh_type == "quad") - { - dim = 2; - } + dim = getBlueprintMeshDimension(); } #endif diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index dc66435a4a..70fe6cb910 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -250,6 +250,29 @@ class SamplingShaper : public Shaper ///@} +protected: + bool verifyInputMeshImpl(std::string& whyBad) const override + { + bool rval = true; + +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + rval = verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(whyBad); + } +#endif + +#if defined(AXOM_USE_MFEM) + if(getDC() != nullptr) + { + rval = verifyMFEMInputMesh(whyBad); + } +#endif + + return rval; + } + +public: /// Returns a pointer to the quadrature function associated with shape \a name if it exists, else nullptr mfem::QuadratureFunction* getShapeQFunction(const std::string& name) const { @@ -665,43 +688,28 @@ class SamplingShaper : public Shaper /// This function is intended to help with debugging void printRegisteredFieldNames(const std::string& initialMessage) { - // helper lambda to extract the keys of a map as a vector of strings - auto extractKeys = [](const auto& map) { - std::vector keys; - for(const auto& kv : map) - { - keys.push_back(kv.first); - } - return keys; - }; - - axom::fmt::memory_buffer out; - - axom::fmt::format_to(std::back_inserter(out), - "List of registered fields in the SamplingShaper {}" - "\n\t* Data collection grid funcs: {}" - "\n\t* Data collection qfuncs: {}" - "\n\t* Known materials: {}", - initialMessage, - axom::fmt::join(extractKeys(getDC()->GetFieldMap()), ", "), - axom::fmt::join(extractKeys(getDC()->GetQFieldMap()), ", "), - axom::fmt::join(m_knownMaterials, ", ")); - - if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) { - axom::fmt::format_to(std::back_inserter(out), - "\n\t* Shape qfuncs: {}" - "\n\t* Mat qfuncs: {}", - axom::fmt::join(extractKeys(shapeQFuncs()), ", "), - axom::fmt::join(extractKeys(materialQFuncs()), ", ")); + shaping::printRegisteredFieldNames(samplingMFEMState(), + m_knownMaterials, + m_vfSampling, + initialMessage); + return; } - else if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_DOFS) +#endif +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) { - axom::fmt::format_to(std::back_inserter(out), - "\n\t* Shaping tensors: {}", - axom::fmt::join(extractKeys(tensors()), ", ")); + shaping::printRegisteredFieldNames(*m_bp_state, + m_knownMaterials, + m_vfSampling, + initialMessage); + return; } - SLIC_INFO_ROOT(axom::fmt::to_string(out)); +#endif + SLIC_INFO_ROOT(axom::fmt::format("SamplingShaper {} has no registered fields.", + initialMessage)); } private: @@ -723,12 +731,10 @@ class SamplingShaper : public Shaper } #if defined(AXOM_USE_CONDUIT) - static int meshDimension(const shaping::BlueprintState& bpState) + int meshDimension(const shaping::BlueprintState& bpState) const { - const conduit::Node& topoNode = - bpState.m_internal_node.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); - const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); - return bpState.m_internal_node["coordsets"][coordsetName]["values"].number_of_children(); + AXOM_UNUSED_VAR(bpState); + return getBlueprintMeshDimension(); } #endif diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 8f06231dd3..e95614cc03 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -22,6 +22,25 @@ namespace axom namespace quest { +#if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) +namespace +{ +bool mpiIsActive() +{ + int initialized = 0; + MPI_Initialized(&initialized); + if(!initialized) + { + return false; + } + + int finalized = 0; + MPI_Finalized(&finalized); + return finalized == 0; +} +} // namespace +#endif + // These were needed for linking - but why? They are constexpr. constexpr int Shaper::DEFAULT_SAMPLES_PER_KNOT_SPAN; constexpr double Shaper::MINIMUM_PERCENT_ERROR; @@ -75,20 +94,16 @@ Shaper::Shaper(RuntimePolicy execPolicy, #endif { m_bp_state = createBlueprintState(); - m_bp_state->m_group_ptr = bpGrp; + auto* internalGrp = m_dataStore.getRoot()->createGroup("internalGrp"); + internalGrp->setDefaultArrayAllocator(m_allocatorId); + m_bp_state->m_group_ptr = internalGrp->copyGroup(bpGrp); m_bp_state->m_allocator_id = m_allocatorId; - m_bp_state->m_topology_name = - topo.empty() ? bpGrp->getGroup("topologies")->getGroupName(0) : topo; + m_bp_state->m_topology_name = resolveBlueprintTopologyName(bpGrp, topo); m_bp_state->m_external_node_ptr = nullptr; - SLIC_ASSERT(m_bp_state->m_topology_name != sidre::InvalidName); + SLIC_ASSERT(m_bp_state->m_group_ptr != nullptr); - // This may take too long if there are repeated construction. - m_bp_state->m_group_ptr->createNativeLayout(m_bp_state->m_internal_node); - - m_cellCount = conduit::blueprint::mesh::topology::length( - m_bp_state->m_internal_node.fetch_existing("topologies") - .fetch_existing(m_bp_state->m_topology_name)); + refreshBlueprintMeshState(); setFilePath(shapeSet.getPath()); } @@ -118,48 +133,14 @@ Shaper::Shaper(RuntimePolicy execPolicy, m_bp_state = createBlueprintState(); m_bp_state->m_group_ptr = nullptr; m_bp_state->m_allocator_id = m_allocatorId; - m_bp_state->m_topology_name = - topo.empty() ? bpNode.fetch_existing("topologies").child(0).name() : topo; + m_bp_state->m_topology_name = resolveBlueprintTopologyName(bpNode, topo); m_bp_state->m_external_node_ptr = &bpNode; m_bp_state->m_group_ptr = m_dataStore.getRoot()->createGroup("internalGrp"); m_bp_state->m_group_ptr->setDefaultArrayAllocator(m_allocatorId); m_bp_state->m_group_ptr->importConduitTreeExternal(bpNode); - // We want unstructured topo but can accomodate structured. - const conduit::Node& n_topo = - bpNode.fetch_existing("topologies").fetch_existing(m_bp_state->m_topology_name); - const std::string topoType = n_topo.fetch_existing("type").as_string(); - - if(topoType == "structured") - { - AXOM_ANNOTATE_SCOPE("Shaper::convertStructured"); - const std::string shapeType = n_topo.fetch_existing("elements/shape").as_string(); - - if(shapeType == "hex") - { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d( - m_bp_state->m_group_ptr, - m_bp_state->m_topology_name, - m_execPolicy); - } - else if(shapeType == "quad") - { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d( - m_bp_state->m_group_ptr, - m_bp_state->m_topology_name, - m_execPolicy); - } - else - { - SLIC_ERROR("Axom Internal error: Unhandled shape type."); - } - } - - m_bp_state->m_group_ptr->createNativeLayout(m_bp_state->m_internal_node); - - m_cellCount = conduit::blueprint::mesh::topology::length( - bpNode.fetch_existing("topologies").fetch_existing(m_bp_state->m_topology_name)); + refreshBlueprintMeshState(); setFilePath(shapeSet.getPath()); } @@ -278,9 +259,89 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, double percentError, do bool Shaper::verifyInputMesh(std::string& whyBad) const { - bool rval = true; + return verifyInputMeshImpl(whyBad); +} #if defined(AXOM_USE_CONDUIT) +std::string Shaper::resolveBlueprintTopologyName(const sidre::Group* bpMesh, + const std::string& topo) const +{ + SLIC_ASSERT(bpMesh != nullptr); + auto* topologiesGrp = bpMesh->getGroup("topologies"); + SLIC_ERROR_IF(topologiesGrp == nullptr, "Blueprint mesh is missing a 'topologies' group."); + + const std::string topologyName = + topo.empty() ? topologiesGrp->getGroupName(0) : topo; + SLIC_ERROR_IF(topologyName == sidre::InvalidName, + "Blueprint mesh does not contain any topology groups."); + SLIC_ERROR_IF(!topologiesGrp->hasGroup(topologyName), + axom::fmt::format("Blueprint mesh does not contain topology '{}'.", topologyName)); + + return topologyName; +} + +std::string Shaper::resolveBlueprintTopologyName(const conduit::Node& bpMesh, + const std::string& topo) const +{ + SLIC_ERROR_IF(!bpMesh.has_path("topologies"), "Blueprint mesh is missing a 'topologies' node."); + + const conduit::Node& topologies = bpMesh.fetch_existing("topologies"); + SLIC_ERROR_IF(topologies.number_of_children() == 0, + "Blueprint mesh does not contain any topology nodes."); + + const std::string topologyName = topo.empty() ? topologies.child(0).name() : topo; + SLIC_ERROR_IF(!topologies.has_child(topologyName), + axom::fmt::format("Blueprint mesh does not contain topology '{}'.", topologyName)); + + return topologyName; +} + +void Shaper::refreshBlueprintMeshState() +{ + SLIC_ASSERT(m_bp_state != nullptr); + SLIC_ASSERT(m_bp_state->m_group_ptr != nullptr); + m_bp_state->m_group_ptr->createNativeLayout(m_bp_state->m_internal_node); + m_cellCount = conduit::blueprint::mesh::topology::length(getBlueprintTopologyNode()); +} + +const conduit::Node& Shaper::getBlueprintTopologyNode() const +{ + SLIC_ASSERT(m_bp_state != nullptr); + return m_bp_state->m_internal_node.fetch_existing("topologies") + .fetch_existing(m_bp_state->m_topology_name); +} + +const conduit::Node& Shaper::getBlueprintCoordsetNode() const +{ + const std::string coordsetName = getBlueprintTopologyNode().fetch_existing("coordset").as_string(); + return m_bp_state->m_internal_node.fetch_existing("coordsets").fetch_existing(coordsetName); +} + +std::string Shaper::getBlueprintCellShape() const +{ + return shaping::getBlueprintCellShape(getBlueprintTopologyNode()); +} + +int Shaper::getBlueprintMeshDimension() const +{ + const std::string shapeType = getBlueprintCellShape(); + if(shapeType == "quad") + { + return 2; + } + if(shapeType == "hex") + { + return 3; + } + + SLIC_ERROR(axom::fmt::format("Unsupported Blueprint cell shape '{}'.", shapeType)); + return -1; +} + +bool Shaper::verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(std::string& whyBad) const +{ + bool rval = true; + if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) { conduit::Node info; @@ -290,40 +351,91 @@ bool Shaper::verifyInputMesh(std::string& whyBad) const rval = conduit::blueprint::mesh::verify(m_bp_state->m_internal_node, info); if(rval) { - std::string topoType = - m_bp_state->m_internal_node.fetch("topologies")[m_bp_state->m_topology_name]["type"] - .as_string(); - rval = topoType == "unstructured"; - info[0].set_string("Topology is not unstructured."); + const std::string topoType = getBlueprintTopologyNode().fetch_existing("type").as_string(); + rval = topoType == "unstructured" || topoType == "structured"; + info[0].set_string("Topology is not structured or unstructured."); } if(rval) { - std::string elemShape = - m_bp_state->m_internal_node.fetch("topologies")[m_bp_state->m_topology_name]["elements"] - ["shape"] - .as_string(); + const std::string elemShape = getBlueprintCellShape(); rval = (elemShape == "hex") || (elemShape == "quad"); info[0].set_string("Topology elements are not hex or quad."); } + if(rval) + { + const std::string coordsetType = getBlueprintCoordsetNode().fetch_existing("type").as_string(); + rval = coordsetType == "explicit"; + info[0].set_string("Coordset is not explicit."); + } whyBad = info.to_summary_string(); } + + return rval; +} + +void Shaper::ensureBlueprintMeshIsUnstructured() +{ + if(m_bp_state == nullptr || m_bp_state->m_group_ptr == nullptr) + { + return; + } + + const conduit::Node& topoNode = getBlueprintTopologyNode(); + const std::string topoType = topoNode.fetch_existing("type").as_string(); + if(topoType != "structured") + { + return; + } + + AXOM_ANNOTATE_SCOPE("Shaper::convertStructured"); + + const std::string shapeType = getBlueprintCellShape(); + if(shapeType == "hex") + { + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d( + m_bp_state->m_group_ptr, + m_bp_state->m_topology_name, + m_execPolicy); + } + else if(shapeType == "quad") + { + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d( + m_bp_state->m_group_ptr, + m_bp_state->m_topology_name, + m_execPolicy); + } + else + { + SLIC_ERROR("Axom Internal error: Unhandled shape type."); + } + + refreshBlueprintMeshState(); +} #endif #if defined(AXOM_USE_MFEM) +bool Shaper::verifyMFEMInputMesh(std::string& whyBad) const +{ + AXOM_UNUSED_VAR(whyBad); + if(getDC() != nullptr) { // No specific requirements for MFEM mesh. } -#endif - return rval; + return true; } +#endif // ---------------------------------------------------------------------------- int Shaper::getRank() const { #if defined(AXOM_USE_MPI) + if(!mpiIsActive()) + { + return 0; + } int rank = -1; MPI_Comm_rank(m_comm, &rank); return rank; @@ -334,6 +446,10 @@ int Shaper::getRank() const double Shaper::allReduceSum(double val) const { #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) + if(!mpiIsActive()) + { + return val; + } double global; MPI_Allreduce(&val, &global, 1, MPI_DOUBLE, MPI_SUM, m_comm); return global; @@ -345,6 +461,10 @@ double Shaper::allReduceSum(double val) const double Shaper::allReduceMin(double val) const { #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) + if(!mpiIsActive()) + { + return val; + } double global; MPI_Allreduce(&val, &global, 1, MPI_DOUBLE, MPI_MIN, m_comm); return global; @@ -356,6 +476,10 @@ double Shaper::allReduceMin(double val) const double Shaper::allReduceMax(double val) const { #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) + if(!mpiIsActive()) + { + return val; + } double global; MPI_Allreduce(&val, &global, 1, MPI_DOUBLE, MPI_MAX, m_comm); return global; diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index 6cd3b12e55..04a4fb520e 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -99,7 +99,7 @@ class Shaper /// Refinement type. using RefinementType = DiscreteShape::RefinementType; - //! @brief Verify the input mesh is okay for this class to work with. + //! @brief Verify the input mesh is okay for this backend to work with. bool verifyInputMesh(std::string& whyBad) const; ///@{ @@ -240,6 +240,75 @@ class Shaper */ int getRank() const; + /*! + * \brief Backend-specific input-mesh validation hook. + * + * Derived shapers may support different Blueprint mesh representations, so + * the validation policy lives with the concrete backend. + */ + virtual bool verifyInputMeshImpl(std::string& whyBad) const = 0; + +#if defined(AXOM_USE_CONDUIT) + /*! + * \brief Selects the Blueprint topology name to use and verifies it exists. + */ + std::string resolveBlueprintTopologyName(const sidre::Group* bpMesh, + const std::string& topo) const; + + /*! + * \brief Selects the Blueprint topology name to use and verifies it exists. + */ + std::string resolveBlueprintTopologyName(const conduit::Node& bpMesh, + const std::string& topo) const; + + /*! + * \brief Rebuilds the internal Conduit view and cached cell count from the + * current Sidre-owned Blueprint mesh. + */ + void refreshBlueprintMeshState(); + + /*! + * \brief Returns the active Blueprint topology node. + */ + const conduit::Node& getBlueprintTopologyNode() const; + + /*! + * \brief Returns the active Blueprint coordset node. + */ + const conduit::Node& getBlueprintCoordsetNode() const; + + /*! + * \brief Returns the active Blueprint cell shape name. + */ + std::string getBlueprintCellShape() const; + + /*! + * \brief Returns the active Blueprint mesh dimension for supported quad/hex + * meshes. + */ + int getBlueprintMeshDimension() const; + + /*! + * \brief Helper for Blueprint meshes supported directly by sampling or by + * lazy conversion in the intersection backend. + * + * This helper verifies the internal Blueprint mesh uses a structured or + * unstructured quad/hex topology over an explicit coordset. + */ + bool verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(std::string& whyBad) const; + + /*! + * \brief Converts a structured explicit Blueprint quad/hex mesh to an + * unstructured working representation if needed. + */ + void ensureBlueprintMeshIsUnstructured(); +#endif + +#if defined(AXOM_USE_MFEM) + //! \brief MFEM meshes currently have no additional validation here. + bool verifyMFEMInputMesh(std::string& whyBad) const; +#endif + #if defined(AXOM_USE_MFEM) virtual std::unique_ptr createMFEMState() { diff --git a/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp b/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp index 222546667c..a7b0af264e 100644 --- a/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp +++ b/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp @@ -11,6 +11,7 @@ #if defined(AXOM_USE_CONDUIT) + #include "MappedZoneUtilities.hpp" #include "axom/bump/utilities/blueprint_utilities.hpp" #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/core.hpp" @@ -23,92 +24,31 @@ #include #include +/*! + * \file GenerateQuadratureMesh.hpp + * + * \brief Builds a derived Blueprint point mesh from tensor-product quadrature + * samples over low-order quad/hex zones. + */ + namespace axom { namespace quest { namespace shaping { -namespace detail -{ - -template -AXOM_HOST_DEVICE primal::Point mapToPhysicalPoint( - const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v) -{ - using PointType = primal::Point; - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - - const double n0 = (1.0 - u) * (1.0 - v); - const double n1 = u * (1.0 - v); - const double n2 = u * v; - const double n3 = (1.0 - u) * v; - - PointType pt; - for(int d = 0; d < 2; ++d) - { - pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d]; - } - return pt; -} - -template -AXOM_HOST_DEVICE primal::Point mapToPhysicalPoint( - const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v, - double w) -{ - using PointType = primal::Point; - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - const auto p4 = coordsetView[zone.getId(4)]; - const auto p5 = coordsetView[zone.getId(5)]; - const auto p6 = coordsetView[zone.getId(6)]; - const auto p7 = coordsetView[zone.getId(7)]; - - const double a = 1.0 - u; - const double b = 1.0 - v; - const double c = 1.0 - w; - - const double n0 = a * b * c; - const double n1 = u * b * c; - const double n2 = u * v * c; - const double n3 = a * v * c; - const double n4 = a * b * w; - const double n5 = u * b * w; - const double n6 = u * v * w; - const double n7 = a * v * w; - - PointType pt; - for(int d = 0; d < 3; ++d) - { - pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d] + n4 * p4[d] + n5 * p5[d] + - n6 * p6[d] + n7 * p7[d]; - } - return pt; -} - -inline int quadraturePointCount(const numerics::QuadratureRule& ruleX, - const numerics::QuadratureRule& ruleY, - const numerics::QuadratureRule& ruleZ, - int dim) -{ - return dim == 2 ? ruleX.getNumPoints() * ruleY.getNumPoints() - : ruleX.getNumPoints() * ruleY.getNumPoints() * ruleZ.getNumPoints(); -} - -} // namespace detail +/*! + * \brief Generates a Blueprint point mesh of quadrature samples over an input + * topology view. + * + * The generated mesh stores one point element per sampled quadrature point and + * publishes fields that map those points back to their source zones. + * + * \tparam ExecSpace The execution space used to populate the generated data. + * \tparam TopologyView The bump topology view type. + * \tparam CoordsetView The bump coordset view type. + */ template class GenerateQuadratureMesh { @@ -116,12 +56,23 @@ class GenerateQuadratureMesh using CoordsetType = typename CoordsetView::value_type; using PointType = primal::Point; + /*! + * \brief Constructs the generator from a topology and coordset view. + * + * \param [in] topologyView The source topology view. + * \param [in] coordsetView The source coordset view. + */ GenerateQuadratureMesh(const TopologyView& topologyView, const CoordsetView& coordsetView) : m_topologyView(topologyView) , m_coordsetView(coordsetView) , m_allocator_id(axom::execution_space::allocatorID()) { } + /*! + * \brief Sets the allocator used for generated Conduit-backed storage. + * + * \param [in] allocator_id The allocator to use for generated arrays. + */ void setAllocatorID(int allocator_id) { SLIC_ERROR_IF(!axom::isValidAllocatorID(allocator_id), "Invalid allocator id."); @@ -132,12 +83,30 @@ class GenerateQuadratureMesh int getAllocatorID() const { return m_allocator_id; } + /*! + * \brief Executes the quadrature-point mesh generation. + * + * \param [in] n_topology The source topology node. + * \param [in] n_coordset The source coordset node. + * \param [in] outputTopologyName The generated point-topology name. + * \param [in] outputCoordsetName The generated coordset name. + * \param [in] originalElementsFieldName The generated provenance field name. + * \param [in] quadratureWeightsFieldName The generated reference-weight field + * name. + * \param [in] quadraturePhysicalWeightsFieldName The generated physical-weight + * field name. + * \param [in] ruleX The quadrature rule in the first logical direction. + * \param [in] ruleY The quadrature rule in the second logical direction. + * \param [in] ruleZ The quadrature rule in the third logical direction. + * \param [in,out] n_output The Blueprint mesh tree to augment. + */ void execute(const conduit::Node& n_topology, const conduit::Node& n_coordset, const std::string& outputTopologyName, const std::string& outputCoordsetName, const std::string& originalElementsFieldName, const std::string& quadratureWeightsFieldName, + const std::string& quadraturePhysicalWeightsFieldName, const numerics::QuadratureRule& ruleX, const numerics::QuadratureRule& ruleY, const numerics::QuadratureRule& ruleZ, @@ -159,6 +128,7 @@ class GenerateQuadratureMesh n_outputCoordset.reset(); n_outputCoordset["type"] = "explicit"; + // Store the sampled coordinates as plain explicit coordset components. axom::StackArray, CoordsetView::dimension()> coordViews; for(int d = 0; d < dim; ++d) { @@ -174,6 +144,7 @@ class GenerateQuadratureMesh n_outputTopo["coordset"] = outputCoordsetName; n_outputTopo["elements/shape"] = "point"; + // The derived topology is a point mesh, so connectivity is the identity. conduit::Node& n_connectivity = n_outputTopo["elements/connectivity"]; n_connectivity.set_allocator(conduitAllocatorId); n_connectivity.set(conduit::DataType::index_t(numPoints)); @@ -207,6 +178,16 @@ class GenerateQuadratureMesh n_weightValues.set(conduit::DataType::float64(numPoints)); auto quadratureWeights = utils::make_array_view(n_weightValues); + conduit::Node& n_physicalQuadratureWeights = + n_output["fields/" + quadraturePhysicalWeightsFieldName]; + n_physicalQuadratureWeights.reset(); + n_physicalQuadratureWeights["association"] = "element"; + n_physicalQuadratureWeights["topology"] = outputTopologyName; + conduit::Node& n_physicalWeightValues = n_physicalQuadratureWeights["values"]; + n_physicalWeightValues.set_allocator(conduitAllocatorId); + n_physicalWeightValues.set(conduit::DataType::float64(numPoints)); + auto physicalQuadratureWeights = utils::make_array_view(n_physicalWeightValues); + const TopologyView deviceTopoView(m_topologyView); const CoordsetView deviceCoordsetView(m_coordsetView); @@ -230,15 +211,23 @@ class GenerateQuadratureMesh const double wx = ruleX.weight(ix); PointType pt; + double physicalMeasure = 0.; if constexpr(CoordsetView::dimension() == 2) { pt = detail::mapToPhysicalPoint(zone, deviceCoordsetView, xi, eta); + physicalMeasure = + detail::computePhysicalMeasureFactor(zone, deviceCoordsetView, xi, eta); } else { pt = detail::mapToPhysicalPoint(zone, deviceCoordsetView, xi, eta, zeta); + physicalMeasure = + detail::computePhysicalMeasureFactor(zone, deviceCoordsetView, xi, eta, zeta); } + // Retain both the reference-space tensor-product weights and the + // Jacobian-weighted physical weights for downstream consumers. + const double referenceWeight = wx * wy * wz; for(int d = 0; d < dim; ++d) { coordViews[d][pointIndex] = pt[d]; @@ -247,7 +236,8 @@ class GenerateQuadratureMesh sizes[pointIndex] = 1; offsets[pointIndex] = pointIndex; originalElements[pointIndex] = zoneIndex; - quadratureWeights[pointIndex] = wx * wy * wz; + quadratureWeights[pointIndex] = referenceWeight; + physicalQuadratureWeights[pointIndex] = referenceWeight * physicalMeasure; ++pointIndex; } } diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 82d14656d6..dfbf491910 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -16,10 +16,13 @@ #include "axom/fmt.hpp" #include +#include #if defined(AXOM_USE_CONDUIT) #include "axom/bump/views/dispatch_coordset.hpp" + #include "axom/bump/views/dispatch_topology.hpp" #include "axom/bump/views/dispatch_unstructured_topology.hpp" + #include "conduit_blueprint_mesh.hpp" #endif #if defined(AXOM_USE_MFEM) @@ -40,6 +43,7 @@ constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; +constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureType quadratureType, int npts, @@ -54,6 +58,38 @@ numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureTy return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); } +std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) +{ + const std::string topoType = topoNode.fetch_existing("type").as_string(); + if(topoNode.has_path("elements/shape")) + { + return topoNode.fetch_existing("elements/shape").as_string(); + } + + if(topoType == "structured") + { + const conduit::Node& dimsNode = topoNode.fetch_existing("elements/dims"); + if(dimsNode.has_child("k")) + { + return "hex"; + } + if(dimsNode.has_child("j")) + { + return "quad"; + } + if(dimsNode.has_child("i")) + { + return "line"; + } + + SLIC_ERROR("Structured Blueprint topology is missing recognizable element dims."); + } + + SLIC_ERROR( + axom::fmt::format("Blueprint topology type '{}' is missing 'elements/shape'.", topoType)); + return ""; +} + template void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, const conduit::Node& coordsetNode, @@ -67,7 +103,9 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, namespace views = axom::bump::views; constexpr int SupportedShapes = views::select_shapes(views::Quad_ShapeID, views::Hex_ShapeID); - views::dispatch_unstructured_topology(topoNode, [&](const auto&, auto topoView) { + views::dispatch_topology( + topoNode, + [&](const auto&, auto topoView) { GenerateQuadratureMesh generator(topoView, coordsetView); generator.setAllocatorID(allocatorID); @@ -77,14 +115,20 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, QUADRATURE_COORDSET_NAME, ORIGINAL_ELEMENTS_FIELD_NAME, QUADRATURE_WEIGHTS_FIELD_NAME, + QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME, ruleX, ruleY, ruleZ, meshNode); - }); + }); } } // namespace + +std::string getBlueprintCellShape(const conduit::Node& topoNode) +{ + return getBlueprintCellShapeImpl(topoNode); +} #endif #if defined(AXOM_USE_MFEM) @@ -266,6 +310,51 @@ mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfu return new mfem::QuadratureFunction(*qfunc); } +void printRegisteredFieldNames(const SamplingMFEMState& mfemState, + const std::set& knownMaterials, + VolFracSampling vfSampling, + const std::string& initialMessage) +{ + SLIC_ASSERT(mfemState.m_dc != nullptr); + + auto extractKeys = [](const auto& map) { + std::vector keys; + for(const auto& kv : map) + { + keys.push_back(kv.first); + } + return keys; + }; + + axom::fmt::memory_buffer out; + axom::fmt::format_to(std::back_inserter(out), + "List of registered fields in the SamplingShaper {}" + "\n\t* Data collection grid funcs: {}" + "\n\t* Data collection qfuncs: {}" + "\n\t* Known materials: {}", + initialMessage, + axom::fmt::join(extractKeys(mfemState.m_dc->GetFieldMap()), ", "), + axom::fmt::join(extractKeys(mfemState.m_dc->GetQFieldMap()), ", "), + axom::fmt::join(knownMaterials, ", ")); + + if(vfSampling == VolFracSampling::SAMPLE_AT_QPTS) + { + axom::fmt::format_to(std::back_inserter(out), + "\n\t* Shape qfuncs: {}" + "\n\t* Mat qfuncs: {}", + axom::fmt::join(extractKeys(mfemState.m_inoutShapeQFuncs), ", "), + axom::fmt::join(extractKeys(mfemState.m_inoutMaterialQFuncs), ", ")); + } + else if(vfSampling == VolFracSampling::SAMPLE_AT_DOFS) + { + axom::fmt::format_to(std::back_inserter(out), + "\n\t* Shaping tensors: {}", + axom::fmt::join(extractKeys(mfemState.m_inoutTensors), ", ")); + } + + SLIC_INFO_ROOT(axom::fmt::to_string(out)); +} + mfem::QuadratureSpace* makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRes) { SLIC_ASSERT(mesh != nullptr); @@ -687,6 +776,97 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, } #if defined(AXOM_USE_CONDUIT) +void printRegisteredFieldNames(const BlueprintState& bpState, + const std::set& knownMaterials, + VolFracSampling AXOM_UNUSED_PARAM(vfSampling), + const std::string& initialMessage) +{ + auto extractChildren = [](const conduit::Node& node) { + std::vector names; + if(node.dtype().is_object()) + { + names.reserve(node.number_of_children()); + for(conduit::index_t i = 0; i < node.number_of_children(); ++i) + { + names.push_back(node.child(i).name()); + } + } + return names; + }; + + auto extractMatchingFields = [&](const std::string& prefix) { + std::vector names; + if(bpState.m_internal_node.has_path("fields")) + { + const conduit::Node& fieldsNode = bpState.m_internal_node.fetch_existing("fields"); + for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) + { + const std::string name = fieldsNode.child(i).name(); + if(axom::utilities::string::startsWith(name, prefix)) + { + names.push_back(name); + } + } + } + return names; + }; + + auto extractOtherFields = [&]() { + std::vector names; + if(bpState.m_internal_node.has_path("fields")) + { + const conduit::Node& fieldsNode = bpState.m_internal_node.fetch_existing("fields"); + for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) + { + const std::string name = fieldsNode.child(i).name(); + if(!axom::utilities::string::startsWith(name, "inout_") && + !axom::utilities::string::startsWith(name, "mat_inout_") && + !axom::utilities::string::startsWith(name, "vol_frac_")) + { + names.push_back(name); + } + } + } + return names; + }; + + const std::vector topologyNames = + bpState.m_internal_node.has_path("topologies") + ? extractChildren(bpState.m_internal_node.fetch_existing("topologies")) + : std::vector {}; + const std::vector coordsetNames = + bpState.m_internal_node.has_path("coordsets") + ? extractChildren(bpState.m_internal_node.fetch_existing("coordsets")) + : std::vector {}; + const std::vector fieldNames = + bpState.m_internal_node.has_path("fields") + ? extractChildren(bpState.m_internal_node.fetch_existing("fields")) + : std::vector {}; + + axom::fmt::memory_buffer out; + axom::fmt::format_to(std::back_inserter(out), + "List of registered fields in the SamplingShaper {}" + "\n\t* Blueprint topologies: {}" + "\n\t* Blueprint coordsets: {}" + "\n\t* Blueprint fields: {}" + "\n\t* Known materials: {}" + "\n\t* Shape inout fields: {}" + "\n\t* Mat inout fields: {}" + "\n\t* Volume fraction fields: {}" + "\n\t* Other Blueprint fields: {}", + initialMessage, + axom::fmt::join(topologyNames, ", "), + axom::fmt::join(coordsetNames, ", "), + axom::fmt::join(fieldNames, ", "), + axom::fmt::join(knownMaterials, ", "), + axom::fmt::join(extractMatchingFields("inout_"), ", "), + axom::fmt::join(extractMatchingFields("mat_inout_"), ", "), + axom::fmt::join(extractMatchingFields("vol_frac_"), ", "), + axom::fmt::join(extractOtherFields(), ", ")); + + SLIC_INFO_ROOT(axom::fmt::to_string(out)); +} + void generateQuadraturePointMesh(conduit::Node& bpMeshNode, const std::string& topologyName, int allocatorID, @@ -701,11 +881,12 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, const conduit::Node& topoNode = bpMeshNode.fetch_existing("topologies").fetch_existing(topologyName); const std::string topoType = topoNode.fetch_existing("type").as_string(); - SLIC_ERROR_IF(topoType != "unstructured", - axom::fmt::format("Unsupported Blueprint topology type '{}' for quadrature mesh generation.", - topoType)); + SLIC_ERROR_IF(topoType != "unstructured" && topoType != "structured", + axom::fmt::format( + "Unsupported Blueprint topology type '{}' for quadrature mesh generation.", + topoType)); - const std::string shape = topoNode.fetch_existing("elements/shape").as_string(); + const std::string shape = shaping::getBlueprintCellShape(topoNode); SLIC_ERROR_IF(shape != "quad" && shape != "hex", axom::fmt::format("Unsupported Blueprint element shape '{}' for quadrature mesh generation.", shape)); @@ -819,21 +1000,24 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin conduit::Node& bpMeshNode = bpState.m_internal_node; SLIC_ERROR_IF(!bpMeshNode.has_path("fields/originalElements/values"), "Missing Blueprint originalElements field for volume fraction projection."); - SLIC_ERROR_IF(!bpMeshNode.has_path("fields/quadratureWeights/values"), - "Missing Blueprint quadratureWeights field for volume fraction projection."); + SLIC_ERROR_IF( + !bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") && + !bpMeshNode.has_path("fields/quadratureWeights/values"), + "Missing Blueprint quadrature weight field for volume fraction projection."); const conduit::Node& topoNode = bpMeshNode.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); - axom::IndexType numZones = 0; - axom::bump::views::dispatch_unstructured_topology( - topoNode, [&](const auto&, auto topoView) { numZones = topoView.numberOfZones(); }); + const axom::IndexType numZones = conduit::blueprint::mesh::topology::length(topoNode); namespace utils = axom::bump::utilities; const auto originalElements = utils::make_array_view(bpMeshNode["fields/originalElements/values"]); - const auto quadratureWeights = - utils::make_array_view(bpMeshNode["fields/quadratureWeights/values"]); + const conduit::Node& quadratureWeightsNode = + bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") + ? bpMeshNode["fields/quadraturePhysicalWeights/values"] + : bpMeshNode["fields/quadratureWeights/values"]; + const auto quadratureWeights = utils::make_array_view(quadratureWeightsNode); const auto inoutValues = utils::make_array_view(inout->fetch_existing("values")); SLIC_ASSERT(originalElements.size() == quadratureWeights.size()); @@ -851,10 +1035,13 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin valuesNode.set_allocator(conduitAllocatorId); valuesNode.set(conduit::DataType::float64(numZones)); auto vfValues = utils::make_array_view(valuesNode); + axom::Array totalWeights(numZones, numZones, bpState.m_allocator_id); + auto totalWeightsView = totalWeights.view(); for(axom::IndexType zoneIdx = 0; zoneIdx < vfValues.size(); ++zoneIdx) { vfValues[zoneIdx] = 0.; + totalWeightsView[zoneIdx] = 0.; } for(axom::IndexType pointIdx = 0; pointIdx < inoutValues.size(); ++pointIdx) @@ -863,6 +1050,16 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin SLIC_ASSERT(zoneIdx >= 0); SLIC_ASSERT(zoneIdx < vfValues.size()); vfValues[zoneIdx] += inoutValues[pointIdx] * quadratureWeights[pointIdx]; + totalWeightsView[zoneIdx] += quadratureWeights[pointIdx]; + } + + for(axom::IndexType zoneIdx = 0; zoneIdx < vfValues.size(); ++zoneIdx) + { + SLIC_ERROR_IF(axom::utilities::isNearlyEqual(totalWeightsView[zoneIdx], 0.0), + axom::fmt::format( + "Blueprint quadrature weights sum to zero in zone {} during volume fraction projection.", + zoneIdx)); + vfValues[zoneIdx] /= totalWeightsView[zoneIdx]; } } diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index f88b28c448..55bd5313db 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -28,6 +28,9 @@ #include "axom/bump/views/dispatch_coordset.hpp" #endif +#include +#include + namespace axom { @@ -311,6 +314,26 @@ enum class VolFracSampling : int SAMPLE_AT_QPTS }; +/** + * \brief Prints the registered sampling-related field names for an MFEM-backed + * sampling state. + */ +void printRegisteredFieldNames(const SamplingMFEMState& mfemState, + const std::set& knownMaterials, + VolFracSampling vfSampling, + const std::string& initialMessage); + +#if defined(AXOM_USE_CONDUIT) +/** + * \brief Prints the registered sampling-related field names for a Blueprint-backed + * sampling state. + */ +void printRegisteredFieldNames(const BlueprintState& bpState, + const std::set& knownMaterials, + VolFracSampling vfSampling, + const std::string& initialMessage); +#endif + /** * \brief Utility function to either return a grid function from the DataCollection \a dc, * or to allocate the grud function through the dc, ensuring the memory doesn't leak @@ -395,6 +418,14 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, axom::numerics::QuadratureType quadratureType); #if defined(AXOM_USE_CONDUIT) +/** + * \brief Returns the element shape for a supported Blueprint topology node. + * + * Structured topologies may omit `elements/shape`, in which case the shape is + * inferred from `elements/dims`. + */ +std::string getBlueprintCellShape(const conduit::Node& topoNode); + /** * \brief Generates a derived Blueprint quadrature point mesh within the * supplied Blueprint mesh node. diff --git a/src/axom/quest/interface/internal/QuestHelpers.cpp b/src/axom/quest/interface/internal/QuestHelpers.cpp index ea73192ae2..02267a63ce 100644 --- a/src/axom/quest/interface/internal/QuestHelpers.cpp +++ b/src/axom/quest/interface/internal/QuestHelpers.cpp @@ -18,10 +18,9 @@ #endif #if defined(AXOM_USE_C2C) + #include "axom/quest/io/C2CReader.hpp" #if defined(AXOM_USE_MPI) #include "axom/quest/io/PC2CReader.hpp" - #else - #include "axom/quest/io/C2CReader.hpp" #endif #endif @@ -42,6 +41,30 @@ namespace internal { /// Mesh I/O methods +#ifdef AXOM_USE_MPI +namespace +{ +bool mpiIsActive() +{ + int initialized = 0; + MPI_Initialized(&initialized); + if(!initialized) + { + return false; + } + + int finalized = 0; + MPI_Finalized(&finalized); + return finalized == 0; +} + +bool useParallelReader(MPI_Comm comm) +{ + return mpiIsActive() && comm != MPI_COMM_NULL && comm != MPI_COMM_SELF; +} +} // namespace +#endif + #if defined(AXOM_USE_UMPIRE_SHARED_MEMORY) /*! * \brief Deallocates the specified MPI communicator object. @@ -320,11 +343,28 @@ int read_stl_mesh(const std::string& file, mint::Mesh*& m, MPI_Comm comm) m = new TriangleMesh(DIMENSION, mint::TRIANGLE); // STEP 2: construct STL reader + quest::STLReader reader; #ifdef AXOM_USE_MPI - quest::PSTLReader reader(comm); + if(useParallelReader(comm)) + { + quest::PSTLReader preader(comm); + preader.setFileName(file); + int rc = preader.read(); + if(rc == READ_SUCCESS) + { + preader.getMesh(static_cast(m)); + } + else + { + SLIC_WARNING("reading STL file failed, setting mesh to NULL"); + delete m; + m = nullptr; + } + + return rc; + } #else AXOM_UNUSED_VAR(comm); - quest::STLReader reader; #endif // STEP 3: read the mesh from the STL file @@ -371,11 +411,43 @@ int read_c2c_mesh(const std::string& file, } // STEP 2: construct C2C reader + quest::C2CReader reader; #if defined(AXOM_USE_MPI) && defined(AXOM_USE_C2C) - quest::PC2CReader reader(comm); + if(useParallelReader(comm)) + { + quest::PC2CReader preader(comm); + preader.setFileName(file); + int rc = preader.read(); + if(rc == READ_SUCCESS) + { + m = new SegmentMesh(DIMENSION, mint::SEGMENT); + + LinearizeCurves lin; + lin.setVertexWeldingThreshold(vertexWeldThreshold); + if(uniform) + { + lin.getLinearMeshUniform(preader.getCurvesView(), + static_cast(m), + segmentsPerPiece); + } + else + { + lin.getLinearMeshNonUniform(preader.getCurvesView(), + static_cast(m), + percentError); + } + revolvedVolume = lin.getRevolvedVolume(preader.getCurvesView(), transform); + } + else + { + SLIC_WARNING("reading C2C file failed, setting mesh to NULL"); + m = nullptr; + } + + return rc; + } #else AXOM_UNUSED_VAR(comm); - quest::C2CReader reader; #endif // STEP 3: read the mesh from the input file @@ -428,11 +500,28 @@ int read_pro_e_mesh(const std::string& file, mint::Mesh*& m, MPI_Comm comm) m = new TetMesh(DIMENSION, mint::TET); // STEP 2: construct Pro/E reader + quest::ProEReader reader; #ifdef AXOM_USE_MPI - quest::PProEReader reader(comm); + if(useParallelReader(comm)) + { + quest::PProEReader preader(comm); + preader.setFileName(file); + int rc = preader.read(); + if(rc == READ_SUCCESS) + { + preader.getMesh(static_cast(m)); + } + else + { + SLIC_WARNING("reading Pro/E file failed, setting mesh to NULL"); + delete m; + m = nullptr; + } + + return rc; + } #else AXOM_UNUSED_VAR(comm); - quest::ProEReader reader; #endif // STEP 3: read the mesh from the Pro/E file diff --git a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp index be22872d6a..cf860ef022 100644 --- a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp +++ b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp @@ -11,10 +11,14 @@ #include "gtest/gtest.h" #include "axom/core.hpp" + #include "axom/quest/IntersectionShaper.hpp" #include "axom/quest/SamplingShaper.hpp" + #include "axom/quest/detail/shaping/MappedZoneUtilities.hpp" #include "axom/quest/detail/shaping/shaping_helpers.hpp" + #include "axom/quest/util/mesh_helpers.hpp" #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/bump/views/dispatch_coordset.hpp" + #include "axom/bump/views/dispatch_unstructured_topology.hpp" #include "axom/sidre.hpp" #include "conduit.hpp" @@ -23,6 +27,34 @@ namespace { +class BlueprintIntersectionShaperForTest : public axom::quest::IntersectionShaper +{ +public: + using axom::quest::IntersectionShaper::IntersectionShaper; + + void ensureInternalMeshIsUnstructured() { ensureBlueprintMeshIsUnstructured(); } + int blueprintMeshDimension() { return getBlueprintMeshDimension(); } + + std::string internalTopologyType() const + { + return m_bp_state->m_internal_node.fetch_existing("topologies") + .fetch_existing(m_bp_state->m_topology_name) + .fetch_existing("type") + .as_string(); + } +}; + +class BlueprintSamplingShaperForTest : public axom::quest::SamplingShaper +{ +public: + using axom::quest::SamplingShaper::SamplingShaper; + + const conduit::Node& internalMesh() const { return m_bp_state->m_internal_node; } +}; + +const std::string unit_circle_contour = + "piece = circle(origin=(0cm, 0cm), radius=1cm, start=0deg, end=360deg)"; + template bool compareArrayView(axom::ArrayView lhs, axom::ArrayView rhs) { @@ -61,7 +93,45 @@ void setNodeValues(conduit::Node& node, axom::ArrayView } } -conduit::Node makeQuadMesh() +void runSamplingShaper(BlueprintSamplingShaperForTest& shaper, const axom::klee::ShapeSet& shapeSet) +{ + auto getShapeDim = [](const auto& shape) { + static std::map formatDim {{"c2c", axom::klee::Dimensions::Two}, + {"stl", axom::klee::Dimensions::Three}}; + + const auto& shapeDim = shape.getGeometry().getInputDimensions(); + const auto& formatStr = shape.getGeometry().getFormat(); + return formatDim.find(formatStr) != formatDim.end() ? formatDim[formatStr] : shapeDim; + }; + + for(const auto& shape : shapeSet.getShapes()) + { + const auto shapeDim = getShapeDim(shape); + shaper.loadShape(shape); + shaper.prepareShapeQuery(shapeDim, shape); + shaper.runShapeQuery(shape); + shaper.applyReplacementRules(shape); + shaper.finalizeShapeQuery(); + } + + shaper.adjustVolumeFractions(); +} + +double computeStructuredMaterialMeasure(const conduit::Node& mesh, + const std::string& vfFieldName, + double cellMeasure) +{ + namespace utils = axom::bump::utilities; + const auto values = utils::make_array_view(mesh.fetch_existing("fields").fetch_existing(vfFieldName).fetch_existing("values")); + double total = 0.; + for(axom::IndexType i = 0; i < values.size(); ++i) + { + total += values[i] * cellMeasure; + } + return total; +} + +conduit::Node makeQuadMesh(const std::string& topoName = "mesh") { conduit::Node mesh; @@ -71,16 +141,16 @@ conduit::Node makeQuadMesh() setNodeValues(mesh["coordsets/coords/values/x"], x.view()); setNodeValues(mesh["coordsets/coords/values/y"], y.view()); - mesh["topologies/mesh/type"] = "unstructured"; - mesh["topologies/mesh/coordset"] = "coords"; - mesh["topologies/mesh/elements/shape"] = "quad"; + mesh["topologies"][topoName]["type"] = "unstructured"; + mesh["topologies"][topoName]["coordset"] = "coords"; + mesh["topologies"][topoName]["elements/shape"] = "quad"; const axom::Array connectivity {{0, 1, 3, 2}}; - setNodeValues(mesh["topologies/mesh/elements/connectivity"], connectivity.view()); + setNodeValues(mesh["topologies"][topoName]["elements/connectivity"], connectivity.view()); return mesh; } -conduit::Node makeHexMesh() +conduit::Node makeHexMesh(const std::string& topoName = "mesh") { conduit::Node mesh; @@ -92,11 +162,49 @@ conduit::Node makeHexMesh() setNodeValues(mesh["coordsets/coords/values/y"], y.view()); setNodeValues(mesh["coordsets/coords/values/z"], z.view()); - mesh["topologies/mesh/type"] = "unstructured"; - mesh["topologies/mesh/coordset"] = "coords"; - mesh["topologies/mesh/elements/shape"] = "hex"; + mesh["topologies"][topoName]["type"] = "unstructured"; + mesh["topologies"][topoName]["coordset"] = "coords"; + mesh["topologies"][topoName]["elements/shape"] = "hex"; const axom::Array connectivity {{0, 1, 3, 2, 4, 5, 7, 6}}; - setNodeValues(mesh["topologies/mesh/elements/connectivity"], connectivity.view()); + setNodeValues(mesh["topologies"][topoName]["elements/connectivity"], connectivity.view()); + + return mesh; +} + +conduit::Node makeDistortedQuadMesh(const std::string& topoName = "mesh") +{ + conduit::Node mesh; + + mesh["coordsets/coords/type"] = "explicit"; + const axom::Array x {{0., 2., 0., 1.}}; + const axom::Array y {{0., 0., 1., 1.}}; + setNodeValues(mesh["coordsets/coords/values/x"], x.view()); + setNodeValues(mesh["coordsets/coords/values/y"], y.view()); + + mesh["topologies"][topoName]["type"] = "unstructured"; + mesh["topologies"][topoName]["coordset"] = "coords"; + mesh["topologies"][topoName]["elements/shape"] = "quad"; + const axom::Array connectivity {{0, 1, 3, 2}}; + setNodeValues(mesh["topologies"][topoName]["elements/connectivity"], connectivity.view()); + + return mesh; +} + +conduit::Node makeStructuredQuadMesh(const std::string& topoName = "mesh") +{ + conduit::Node mesh; + + mesh["coordsets/coords/type"] = "explicit"; + const axom::Array x {{0., 1., 0., 1.}}; + const axom::Array y {{0., 0., 1., 1.}}; + setNodeValues(mesh["coordsets/coords/values/x"], x.view()); + setNodeValues(mesh["coordsets/coords/values/y"], y.view()); + + mesh["topologies"][topoName]["type"] = "structured"; + mesh["topologies"][topoName]["coordset"] = "coords"; + mesh["topologies"][topoName]["elements/shape"] = "quad"; + mesh["topologies"][topoName]["elements/dims/i"] = 1; + mesh["topologies"][topoName]["elements/dims/j"] = 1; return mesh; } @@ -133,6 +241,8 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) utils::make_array_view(mesh["fields/originalElements/values"]); const auto quadratureWeightsView = utils::make_array_view(mesh["fields/quadratureWeights/values"]); + const auto physicalQuadratureWeightsView = + utils::make_array_view(mesh["fields/quadraturePhysicalWeights/values"]); const axom::Array expectedX {{0., 1., 0., 1., 0., 1.}}; const axom::Array expectedY {{0., 0., 0.5, 0.5, 1., 1.}}; @@ -157,6 +267,7 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) for(axom::IndexType i = 0; i < expectedWeights.size(); ++i) { EXPECT_NEAR(expectedWeights[i], quadratureWeightsView[i], 1e-12); + EXPECT_NEAR(expectedWeights[i], physicalQuadratureWeightsView[i], 1e-12); } } @@ -182,11 +293,16 @@ TEST(quest_blueprint_quadrature_mesh, generate_open_uniform_hex_mesh) namespace utils = axom::bump::utilities; const auto originalElementsView = utils::make_array_view(mesh["fields/originalElements/values"]); + const auto quadratureWeightsView = + utils::make_array_view(mesh["fields/quadratureWeights/values"]); + const auto physicalQuadratureWeightsView = + utils::make_array_view(mesh["fields/quadraturePhysicalWeights/values"]); const axom::Array expectedX {{1. / 3., 2. / 3., 1. / 3., 2. / 3.}}; const axom::Array expectedY {{0.5, 0.5, 0.5, 0.5}}; const axom::Array expectedZ {{1. / 3., 1. / 3., 2. / 3., 2. / 3.}}; const axom::Array expectedOriginalElements {{0, 0, 0, 0}}; + const axom::Array expectedWeights {{0.25, 0.25, 0.25, 0.25}}; axom::bump::views::dispatch_explicit_coordset( mesh["coordsets/quadrature_points"], [&](auto coordsetView) { @@ -198,6 +314,75 @@ TEST(quest_blueprint_quadrature_mesh, generate_open_uniform_hex_mesh) } }); EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); + for(axom::IndexType i = 0; i < expectedWeights.size(); ++i) + { + EXPECT_NEAR(expectedWeights[i], quadratureWeightsView[i], 1e-12); + EXPECT_NEAR(expectedWeights[i], physicalQuadratureWeightsView[i], 1e-12); + } +} + +TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_structured_quad_mesh) +{ + conduit::Node mesh = makeStructuredQuadMesh(); + + int sampleResolution[3] = {2, 2, 1}; + axom::quest::shaping::generateQuadraturePointMesh(mesh, + "mesh", + axom::execution_space::allocatorID(), + sampleResolution, + axom::numerics::QuadratureType::ClosedUniform); + + conduit::Node info; + EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); + + ASSERT_TRUE(mesh.has_path("coordsets/quadrature_points/values/x")); + ASSERT_TRUE(mesh.has_path("fields/originalElements/values")); + + namespace utils = axom::bump::utilities; + const auto originalElementsView = + utils::make_array_view(mesh["fields/originalElements/values"]); + + const axom::Array expectedX {{0., 1., 0., 1.}}; + const axom::Array expectedY {{0., 0., 1., 1.}}; + const axom::Array expectedOriginalElements {{0, 0, 0, 0}}; + + axom::bump::views::dispatch_explicit_coordset( + mesh["coordsets/quadrature_points"], [&](auto coordsetView) { + for(axom::IndexType i = 0; i < expectedX.size(); ++i) + { + EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-12); + EXPECT_NEAR(coordsetView[i][1], expectedY[i], 1e-12); + } + }); + EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); +} + +TEST(quest_blueprint_quadrature_mesh, mapped_zone_helper_computes_distorted_quad_measure_factor) +{ + conduit::Node mesh = makeDistortedQuadMesh(); + double lowerFactor = -1.; + double upperFactor = -1.; + + axom::bump::views::dispatch_explicit_coordset( + mesh["coordsets/coords"], [&](auto coordsetView) { + axom::bump::views::dispatch_unstructured_topology( + mesh["topologies/mesh"], [&](const auto&, auto topoView) { + const auto zone = topoView.zone(0); + lowerFactor = + axom::quest::shaping::detail::computePhysicalMeasureFactor(zone, + coordsetView, + 1. / 3., + 1. / 3.); + upperFactor = + axom::quest::shaping::detail::computePhysicalMeasureFactor(zone, + coordsetView, + 1. / 3., + 2. / 3.); + }); + }); + + EXPECT_NEAR(lowerFactor, 5. / 3., 1e-12); + EXPECT_NEAR(upperFactor, 4. / 3., 1e-12); } TEST(quest_blueprint_quadrature_mesh, state_wrapper_generation_is_idempotent) @@ -315,6 +500,39 @@ TEST(quest_blueprint_quadrature_mesh, compute_volume_fractions_for_material_from EXPECT_NEAR(volFracValues[0], 0.5, 1e-12); } +TEST(quest_blueprint_quadrature_mesh, + compute_volume_fractions_for_material_uses_physical_quadrature_weights) +{ + conduit::Node mesh = makeDistortedQuadMesh(); + + axom::quest::shaping::BlueprintState bpState; + bpState.m_allocator_id = axom::execution_space::allocatorID(); + bpState.m_topology_name = "mesh"; + bpState.m_internal_node = mesh; + + int sampleResolution[3] = {2, 2, 1}; + axom::quest::shaping::generateSamplingPositions( + bpState, + sampleResolution, + axom::numerics::QuadratureType::OpenUniform); + + conduit::Node* materialField = bpState.createMaterialFunction("mat_inout_test"); + ASSERT_NE(materialField, nullptr); + + const axom::Array materialValues {{1., 1., 0., 0.}}; + setNodeValues((*materialField)["values"], materialValues.view()); + + axom::quest::shaping::computeVolumeFractionsForMaterial(bpState, "mat_inout_test"); + + ASSERT_TRUE(bpState.m_internal_node.has_path("fields/vol_frac_test/values")); + namespace utils = axom::bump::utilities; + const auto volFracValues = + utils::make_array_view(bpState.m_internal_node["fields/vol_frac_test/values"]); + + ASSERT_EQ(volFracValues.size(), 1); + EXPECT_NEAR(volFracValues[0], 5. / 9., 1e-12); +} + TEST(quest_blueprint_quadrature_mesh, sampling_shaper_constructs_from_blueprint_node_and_group) { conduit::Node mesh = makeQuadMesh(); @@ -331,4 +549,183 @@ TEST(quest_blueprint_quadrature_mesh, sampling_shaper_constructs_from_blueprint_ axom::quest::SamplingShaper groupShaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); } +TEST(quest_blueprint_quadrature_mesh, sampling_shaper_verify_accepts_structured_quad_mesh) +{ + conduit::Node mesh = makeStructuredQuadMesh(); + axom::klee::ShapeSet shapeSet; + const auto policy = axom::runtime_policy::Policy::seq; + const int allocatorId = axom::policyToDefaultAllocatorID(policy); + + axom::quest::SamplingShaper nodeShaper(policy, allocatorId, shapeSet, mesh, "mesh"); + std::string whyBad; + EXPECT_TRUE(nodeShaper.verifyInputMesh(whyBad)) << whyBad; + + axom::sidre::DataStore ds; + auto* meshGroup = ds.getRoot()->createGroup("mesh"); + ASSERT_TRUE(meshGroup->importConduitTree(mesh)); + + axom::quest::SamplingShaper groupShaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); + whyBad.clear(); + EXPECT_TRUE(groupShaper.verifyInputMesh(whyBad)) << whyBad; +} + +TEST(quest_blueprint_quadrature_mesh, intersection_shaper_verify_accepts_structured_quad_mesh) +{ + conduit::Node mesh = makeStructuredQuadMesh(); + axom::klee::ShapeSet shapeSet; + const auto policy = axom::runtime_policy::Policy::seq; + const int allocatorId = axom::policyToDefaultAllocatorID(policy); + + BlueprintIntersectionShaperForTest nodeShaper(policy, allocatorId, shapeSet, mesh, "mesh"); + std::string whyBad; + EXPECT_TRUE(nodeShaper.verifyInputMesh(whyBad)) << whyBad; + + axom::sidre::DataStore ds; + auto* meshGroup = ds.getRoot()->createGroup("mesh"); + ASSERT_TRUE(meshGroup->importConduitTree(mesh)); + + BlueprintIntersectionShaperForTest groupShaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); + whyBad.clear(); + EXPECT_TRUE(groupShaper.verifyInputMesh(whyBad)) << whyBad; +} + +TEST(quest_blueprint_quadrature_mesh, + intersection_shaper_lazy_conversion_keeps_original_sidre_group_structured) +{ + conduit::Node mesh = makeStructuredQuadMesh(); + axom::klee::ShapeSet shapeSet; + const auto policy = axom::runtime_policy::Policy::seq; + const int allocatorId = axom::policyToDefaultAllocatorID(policy); + + axom::sidre::DataStore ds; + auto* meshGroup = ds.getRoot()->createGroup("mesh"); + ASSERT_TRUE(meshGroup->importConduitTree(mesh)); + + BlueprintIntersectionShaperForTest shaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); + EXPECT_EQ(shaper.internalTopologyType(), "structured"); + + shaper.ensureInternalMeshIsUnstructured(); + EXPECT_EQ(shaper.internalTopologyType(), "unstructured"); + + conduit::Node originalMeshNode; + ASSERT_TRUE(meshGroup->createNativeLayout(originalMeshNode)); + EXPECT_EQ(originalMeshNode["topologies/mesh/type"].as_string(), "structured"); +} + +TEST(quest_blueprint_quadrature_mesh, blueprint_shapers_support_nondefault_topology_names) +{ + conduit::Node mesh = makeStructuredQuadMesh("cells"); + axom::klee::ShapeSet shapeSet; + const auto policy = axom::runtime_policy::Policy::seq; + const int allocatorId = axom::policyToDefaultAllocatorID(policy); + + axom::quest::SamplingShaper samplingShaper(policy, allocatorId, shapeSet, mesh, "cells"); + std::string whyBad; + EXPECT_TRUE(samplingShaper.verifyInputMesh(whyBad)) << whyBad; + + BlueprintIntersectionShaperForTest intersectionShaper(policy, allocatorId, shapeSet, mesh, "cells"); + whyBad.clear(); + EXPECT_TRUE(intersectionShaper.verifyInputMesh(whyBad)) << whyBad; + EXPECT_EQ(intersectionShaper.blueprintMeshDimension(), 2); +} + +TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_quad_blueprint_mesh) +{ + const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + const auto policy = axom::runtime_policy::Policy::seq; + const int allocatorId = axom::policyToDefaultAllocatorID(policy); + + axom::sidre::DataStore ds; + auto* meshGroup = ds.getRoot()->createGroup("mesh"); + meshGroup->setDefaultArrayAllocator(allocatorId); + + const axom::primal::BoundingBox bbox {{-2., -2.}, {2., 2.}}; + const axom::NumericArray resolution {64, 64}; + axom::quest::util::make_structured_blueprint_box_mesh_2d(meshGroup, bbox, resolution, "mesh", "coords", policy); + + axom::utilities::filesystem::TempFile contourFile(testname, ".contour"); + contourFile.write(unit_circle_contour); + + const std::string shapeYaml = axom::fmt::format(R"( +dimensions: 2 + +shapes: +- name: circle_shape + material: circleMat + geometry: + format: c2c + path: {} +)", + contourFile.getPath()); + + axom::utilities::filesystem::TempFile shapeFile(testname, ".yaml"); + shapeFile.write(shapeYaml); + + const axom::klee::ShapeSet shapeSet = axom::klee::readShapeSet(shapeFile.getPath()); + + BlueprintSamplingShaperForTest shaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); + std::string whyBad; + ASSERT_TRUE(shaper.verifyInputMesh(whyBad)) << whyBad; + + runSamplingShaper(shaper, shapeSet); + + conduit::Node info; + EXPECT_TRUE(conduit::blueprint::mesh::verify(shaper.internalMesh(), info)) << info.to_yaml(); + ASSERT_TRUE(shaper.internalMesh().has_path("fields/vol_frac_circleMat/values")); + + const double cellArea = bbox.range()[0] * bbox.range()[1] / (resolution[0] * resolution[1]); + const double totalArea = + computeStructuredMaterialMeasure(shaper.internalMesh(), "vol_frac_circleMat", cellArea); + EXPECT_NEAR(totalArea, 3.14159265358979323846, 5e-2); +} + +TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_hex_blueprint_mesh) +{ + const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + const auto policy = axom::runtime_policy::Policy::seq; + const int allocatorId = axom::policyToDefaultAllocatorID(policy); + + axom::sidre::DataStore ds; + auto* meshGroup = ds.getRoot()->createGroup("mesh"); + meshGroup->setDefaultArrayAllocator(allocatorId); + + const axom::primal::BoundingBox bbox {{-2., -2., -2.}, {2., 2., 2.}}; + const axom::NumericArray resolution {8, 8, 8}; + axom::quest::util::make_structured_blueprint_box_mesh_3d(meshGroup, bbox, resolution, "mesh", "coords", policy); + + const std::string tetPath = axom::fmt::format("{}/quest/tetrahedron.stl", AXOM_DATA_DIR); + const std::string shapeYaml = axom::fmt::format(R"( +dimensions: 3 + +shapes: +- name: tet_shape + material: steel + geometry: + format: stl + path: {} +)", + tetPath); + + axom::utilities::filesystem::TempFile shapeFile(testname, ".yaml"); + shapeFile.write(shapeYaml); + + const axom::klee::ShapeSet shapeSet = axom::klee::readShapeSet(shapeFile.getPath()); + + BlueprintSamplingShaperForTest shaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); + std::string whyBad; + ASSERT_TRUE(shaper.verifyInputMesh(whyBad)) << whyBad; + + runSamplingShaper(shaper, shapeSet); + + conduit::Node info; + EXPECT_TRUE(conduit::blueprint::mesh::verify(shaper.internalMesh(), info)) << info.to_yaml(); + ASSERT_TRUE(shaper.internalMesh().has_path("fields/vol_frac_steel/values")); + + const double cellVolume = bbox.range()[0] * bbox.range()[1] * bbox.range()[2] / + (resolution[0] * resolution[1] * resolution[2]); + const double totalVolume = + computeStructuredMaterialMeasure(shaper.internalMesh(), "vol_frac_steel", cellVolume); + EXPECT_NEAR(totalVolume, 8. / 3., 5e-2); +} + #endif diff --git a/src/axom/quest/util/mesh_helpers.cpp b/src/axom/quest/util/mesh_helpers.cpp index 95443b1b24..6f0d73b390 100644 --- a/src/axom/quest/util/mesh_helpers.cpp +++ b/src/axom/quest/util/mesh_helpers.cpp @@ -343,7 +343,10 @@ void convert_blueprint_structured_explicit_to_unstructured_3d_impl(axom::sidre:: axom::sidre::View* ugTopoTypeView = ugTopoGrp == topoGrp ? ugTopoGrp->getView("type") : ugTopoGrp->createView("type"); ugTopoTypeView->setString("unstructured"); - axom::sidre::View* shapeView = ugTopoGrp->createView("elements/shape"); + axom::sidre::View* shapeView = + ugTopoGrp->hasView("elements/shape") ? ugTopoGrp->getView("elements/shape") + : ugTopoGrp->createView("elements/shape"); + SLIC_ASSERT(shapeView != nullptr); shapeView->setString("hex"); axom::sidre::Group* topoElemGrp = topoGrp->getGroup("elements"); @@ -466,7 +469,11 @@ void convert_blueprint_structured_explicit_to_unstructured_2d_impl(axom::sidre:: axom::sidre::View* topoTypeView = topoGrp->getView("type"); SLIC_ASSERT(std::string(topoTypeView->getString()) == "structured"); topoTypeView->setString("unstructured"); - topoGrp->createView("elements/shape")->setString("quad"); + axom::sidre::View* shapeView = + topoGrp->hasView("elements/shape") ? topoGrp->getView("elements/shape") + : topoGrp->createView("elements/shape"); + SLIC_ASSERT(shapeView != nullptr); + shapeView->setString("quad"); axom::sidre::Group* topoElemGrp = topoGrp->getGroup("elements"); axom::sidre::Group* topoDimsGrp = topoElemGrp->getGroup("dims"); From 68901203780c2ec68180480643cd0a9359d87f46 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 May 2026 17:27:56 -0700 Subject: [PATCH 09/72] Refactoring and support for inline Blueprint meshes in shaping driver. --- src/axom/quest/SamplingShaper.hpp | 9 +- src/axom/quest/Shaper.hpp | 7 + .../detail/shaping/MappedZoneUtilities.hpp | 283 ++++++++++++++ src/axom/quest/examples/CMakeLists.txt | 14 +- src/axom/quest/examples/shaping_driver.cpp | 349 +++++++++++++++--- 5 files changed, 596 insertions(+), 66 deletions(-) create mode 100644 src/axom/quest/detail/shaping/MappedZoneUtilities.hpp diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 70fe6cb910..75af40a03a 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -664,7 +664,9 @@ class SamplingShaper : public Shaper internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug : slic::message::Warning); - auto computeVolumeFractions = [this](const std::string& matName) { + for(const auto& materialName : m_knownMaterials) + { + const auto matName = axom::fmt::format("mat_inout_{}", materialName); SLIC_INFO_ROOT( axom::fmt::format("Generating volume fraction fields for '{}' material", matName)); @@ -676,11 +678,6 @@ class SamplingShaper : public Shaper case shaping::VolFracSampling::SAMPLE_AT_DOFS: break; } - }; - - for(const auto& materialName : m_knownMaterials) - { - computeVolumeFractions(axom::fmt::format("mat_inout_{}", materialName)); } } diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index 04a4fb520e..6610ba43d1 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -136,6 +136,13 @@ class Shaper } #endif +#if defined(AXOM_USE_CONDUIT) + const conduit::Node* getBlueprintMeshNode() const + { + return m_bp_state != nullptr ? &m_bp_state->m_internal_node : nullptr; + } +#endif + /*! * \brief Predicate to determine if the specified format is valid * diff --git a/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp b/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp new file mode 100644 index 0000000000..358628e987 --- /dev/null +++ b/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp @@ -0,0 +1,283 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef AXOM_QUEST_MAPPED_ZONE_UTILITIES_HPP_ +#define AXOM_QUEST_MAPPED_ZONE_UTILITIES_HPP_ + +#include "axom/config.hpp" + +#include "axom/core.hpp" +#include "axom/core/numerics/Determinants.hpp" +#include "axom/primal.hpp" + +/*! + * \file MappedZoneUtilities.hpp + * + * \brief Header-only utilities for evaluating low-order mapped quad/hex zones. + */ + +namespace axom +{ +namespace quest +{ +namespace shaping +{ +namespace detail +{ + +/*! + * \brief Maps a point in the unit square to a physical quad using bilinear + * shape functions. + * + * \tparam ShapeType A zone type exposing `getId(i)` for its node ids. + * \tparam CoordsetView A coordset view whose entries are point-like. + * + * \param [in] zone The source zone. + * \param [in] coordsetView The coordinate storage for the source mesh. + * \param [in] u The first reference-space coordinate in `[0,1]`. + * \param [in] v The second reference-space coordinate in `[0,1]`. + * + * \return The mapped physical-space point. + */ +template +AXOM_HOST_DEVICE primal::Point mapToPhysicalPoint( + const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v) +{ + using PointType = primal::Point; + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + + const double n0 = (1.0 - u) * (1.0 - v); + const double n1 = u * (1.0 - v); + const double n2 = u * v; + const double n3 = (1.0 - u) * v; + + PointType pt; + for(int d = 0; d < 2; ++d) + { + pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d]; + } + return pt; +} + +/*! + * \brief Maps a point in the unit cube to a physical hex using trilinear + * shape functions. + * + * \tparam ShapeType A zone type exposing `getId(i)` for its node ids. + * \tparam CoordsetView A coordset view whose entries are point-like. + * + * \param [in] zone The source zone. + * \param [in] coordsetView The coordinate storage for the source mesh. + * \param [in] u The first reference-space coordinate in `[0,1]`. + * \param [in] v The second reference-space coordinate in `[0,1]`. + * \param [in] w The third reference-space coordinate in `[0,1]`. + * + * \return The mapped physical-space point. + */ +template +AXOM_HOST_DEVICE primal::Point mapToPhysicalPoint( + const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v, + double w) +{ + using PointType = primal::Point; + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + const auto p4 = coordsetView[zone.getId(4)]; + const auto p5 = coordsetView[zone.getId(5)]; + const auto p6 = coordsetView[zone.getId(6)]; + const auto p7 = coordsetView[zone.getId(7)]; + + const double a = 1.0 - u; + const double b = 1.0 - v; + const double c = 1.0 - w; + + const double n0 = a * b * c; + const double n1 = u * b * c; + const double n2 = u * v * c; + const double n3 = a * v * c; + const double n4 = a * b * w; + const double n5 = u * b * w; + const double n6 = u * v * w; + const double n7 = a * v * w; + + PointType pt; + for(int d = 0; d < 3; ++d) + { + pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d] + n4 * p4[d] + n5 * p5[d] + + n6 * p6[d] + n7 * p7[d]; + } + return pt; +} + +/*! + * \brief Evaluates the local physical area scale for a mapped quad. + * + * Computes the determinant of the 2x2 Jacobian for the bilinear map from the + * unit square to the physical zone, returning its absolute value. + * + * \tparam ShapeType A zone type exposing `getId(i)` for its node ids. + * \tparam CoordsetView A coordset view whose entries are point-like. + * + * \param [in] zone The source zone. + * \param [in] coordsetView The coordinate storage for the source mesh. + * \param [in] u The first reference-space coordinate in `[0,1]`. + * \param [in] v The second reference-space coordinate in `[0,1]`. + * + * \return The local Jacobian area scale `|det(dx/du, dx/dv)|`. + */ +template +AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v) +{ + using VectorType = primal::Vector; + + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + + const double du0 = -(1.0 - v); + const double du1 = (1.0 - v); + const double du2 = v; + const double du3 = -v; + + const double dv0 = -(1.0 - u); + const double dv1 = -u; + const double dv2 = u; + const double dv3 = 1.0 - u; + + VectorType dxdu; + VectorType dxdv; + for(int d = 0; d < 2; ++d) + { + dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d]; + dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d]; + } + + return axom::utilities::abs( + axom::numerics::determinant(dxdu[0], dxdv[0], dxdu[1], dxdv[1])); +} + +/*! + * \brief Evaluates the local physical volume scale for a mapped hex. + * + * Computes the determinant of the 3x3 Jacobian for the trilinear map from the + * unit cube to the physical zone, returning its absolute value. + * + * \tparam ShapeType A zone type exposing `getId(i)` for its node ids. + * \tparam CoordsetView A coordset view whose entries are point-like. + * + * \param [in] zone The source zone. + * \param [in] coordsetView The coordinate storage for the source mesh. + * \param [in] u The first reference-space coordinate in `[0,1]`. + * \param [in] v The second reference-space coordinate in `[0,1]`. + * \param [in] w The third reference-space coordinate in `[0,1]`. + * + * \return The local Jacobian volume scale `|det(dx/du, dx/dv, dx/dw)|`. + */ +template +AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v, + double w) +{ + using VectorType = primal::Vector; + + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + const auto p4 = coordsetView[zone.getId(4)]; + const auto p5 = coordsetView[zone.getId(5)]; + const auto p6 = coordsetView[zone.getId(6)]; + const auto p7 = coordsetView[zone.getId(7)]; + + const double a = 1.0 - u; + const double b = 1.0 - v; + const double c = 1.0 - w; + + const double du0 = -b * c; + const double du1 = b * c; + const double du2 = v * c; + const double du3 = -v * c; + const double du4 = -b * w; + const double du5 = b * w; + const double du6 = v * w; + const double du7 = -v * w; + + const double dv0 = -a * c; + const double dv1 = -u * c; + const double dv2 = u * c; + const double dv3 = a * c; + const double dv4 = -a * w; + const double dv5 = -u * w; + const double dv6 = u * w; + const double dv7 = a * w; + + const double dw0 = -a * b; + const double dw1 = -u * b; + const double dw2 = -u * v; + const double dw3 = -a * v; + const double dw4 = a * b; + const double dw5 = u * b; + const double dw6 = u * v; + const double dw7 = a * v; + + VectorType dxdu; + VectorType dxdv; + VectorType dxdw; + for(int d = 0; d < 3; ++d) + { + dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d] + du4 * p4[d] + + du5 * p5[d] + du6 * p6[d] + du7 * p7[d]; + dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d] + dv4 * p4[d] + + dv5 * p5[d] + dv6 * p6[d] + dv7 * p7[d]; + dxdw[d] = dw0 * p0[d] + dw1 * p1[d] + dw2 * p2[d] + dw3 * p3[d] + dw4 * p4[d] + + dw5 * p5[d] + dw6 * p6[d] + dw7 * p7[d]; + } + + return axom::utilities::abs(VectorType::scalar_triple_product(dxdu, dxdv, dxdw)); +} + +/*! + * \brief Returns the tensor-product quadrature point count per zone. + * + * \param [in] ruleX The rule in the first logical direction. + * \param [in] ruleY The rule in the second logical direction. + * \param [in] ruleZ The rule in the third logical direction. + * \param [in] dim The logical dimension of the source zones. + * + * \return The number of quadrature points generated for one zone. + */ +inline int quadraturePointCount(const numerics::QuadratureRule& ruleX, + const numerics::QuadratureRule& ruleY, + const numerics::QuadratureRule& ruleZ, + int dim) +{ + return dim == 2 ? ruleX.getNumPoints() * ruleY.getNumPoints() + : ruleX.getNumPoints() * ruleY.getNumPoints() * ruleZ.getNumPoints(); +} + +} // namespace detail +} // namespace shaping +} // namespace quest +} // namespace axom + +#endif diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index b6431e547e..d6194e5d16 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -183,6 +183,19 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI set_tests_properties(${_testname} PROPERTIES PASS_REGULAR_EXPRESSION "Volume of material 'steel' is 65.") + if(CONDUIT_FOUND) + set(_testname quest_shaping_driver_ex_sampling_circles_blueprint) + axom_add_test( + NAME ${_testname} + COMMAND quest_shaping_driver_ex + -i ${shaping_data_dir}/circles.yaml + --method sampling + inline_mesh_blueprint --min -6 -6 --max 6 6 --res 25 25 -d 2 + NUM_MPI_TASKS ${_nranks}) + set_tests_properties(${_testname} PROPERTIES + PASS_REGULAR_EXPRESSION "Saved shaped Blueprint mesh to 'shaping_blueprint'.") + endif() + set(_testname quest_shaping_driver_ex_sampling_balls_and_jacks) axom_add_test( NAME ${_testname} @@ -789,4 +802,3 @@ if(OPENCASCADE_FOUND) endif() endif() - diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 9034c747fb..091e4e6c68 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -60,6 +60,19 @@ namespace using Point2D = primal::Point; using Point3D = primal::Point; +enum class InlineMeshKind : int +{ + None, + MFEM, + Blueprint +}; + +enum class BlueprintTopologyType : int +{ + Structured, + Unstructured +}; + struct AxisymmetricProjector32 { AXOM_HOST_DEVICE Point2D operator()(Point3D pt) const @@ -99,6 +112,8 @@ struct Input std::vector boxMaxs; std::vector boxResolution; int boxDim {-1}; + InlineMeshKind inlineMeshKind {InlineMeshKind::None}; + BlueprintTopologyType blueprintTopologyType {BlueprintTopologyType::Structured}; std::string shapeFile; klee::ShapeSet shapeSet; @@ -125,6 +140,8 @@ struct Input public: bool isVerbose() const { return m_verboseOutput; } + bool usesInlineMFEMMesh() const { return inlineMeshKind == InlineMeshKind::MFEM; } + bool usesInlineBlueprintMesh() const { return inlineMeshKind == InlineMeshKind::Blueprint; } /// Generate an mfem Cartesian mesh, scaled to the bounding box range mfem::Mesh* createBoxMesh() @@ -179,11 +196,83 @@ struct Input return mesh; } +#if defined(AXOM_USE_CONDUIT) + std::unique_ptr createBlueprintBoxMesh() + { + auto ds = std::make_unique(); + auto* meshGrp = ds->getRoot()->createGroup("mesh"); + meshGrp->setDefaultArrayAllocator(axom::policyToDefaultAllocatorID(policy)); + + switch(boxDim) + { + case 2: + { + using BBox2D = primal::BoundingBox; + using Pt2D = primal::Point; + auto res = axom::NumericArray(boxResolution.data()); + auto bbox = BBox2D(Pt2D(boxMins.data()), Pt2D(boxMaxs.data())); + + SLIC_INFO_ROOT(axom::fmt::format("Creating inline Blueprint box mesh of resolution {} and " + "bounding box {}", + res, + bbox)); + + if(blueprintTopologyType == BlueprintTopologyType::Structured) + { + quest::util::make_structured_blueprint_box_mesh_2d(meshGrp, bbox, res, "mesh", "coords", policy); + } + else + { + quest::util::make_unstructured_blueprint_box_mesh_2d(meshGrp, + bbox, + res, + "mesh", + "coords", + policy); + } + } + break; + case 3: + { + using BBox3D = primal::BoundingBox; + using Pt3D = primal::Point; + auto res = axom::NumericArray(boxResolution.data()); + auto bbox = BBox3D(Pt3D(boxMins.data()), Pt3D(boxMaxs.data())); + + SLIC_INFO_ROOT(axom::fmt::format("Creating inline Blueprint box mesh of resolution {} and " + "bounding box {}", + res, + bbox)); + + if(blueprintTopologyType == BlueprintTopologyType::Structured) + { + quest::util::make_structured_blueprint_box_mesh_3d(meshGrp, bbox, res, "mesh", "coords", policy); + } + else + { + quest::util::make_unstructured_blueprint_box_mesh_3d(meshGrp, + bbox, + res, + "mesh", + "coords", + policy); + } + } + break; + default: + SLIC_ERROR_ROOT("Only 2D and 3D meshes are currently supported."); + break; + } + + return ds; + } +#endif + 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 = usesInlineMFEMMesh() ? createBoxMesh() : nullptr; + std::string name = usesInlineMFEMMesh() ? "mesh" : getDCMeshName(); auto dc = std::unique_ptr( new sidre::MFEMSidreDataCollection(name, mesh, dc_owns_data)); @@ -267,12 +356,13 @@ struct Input auto* mesh_file = app.add_option("-m,--mesh-file", meshFile) ->description( "Path to computational mesh. \n" - "Alternatively, use the `inline_mesh` subcommand.") + "Alternatively, use the `inline_mesh` or `inline_mesh_blueprint` subcommands.") ->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->callback([this]() { inlineMeshKind = InlineMeshKind::MFEM; }); inline_mesh_subcommand->add_option("--min", boxMins) ->description("Min bounds for box mesh (x,y[,z])") @@ -293,9 +383,49 @@ struct Input ->check(axom::CLI::PositiveNumber) ->required(); +#if defined(AXOM_USE_CONDUIT) + std::map blueprintTopoMap { + {"structured", BlueprintTopologyType::Structured}, + {"unstructured", BlueprintTopologyType::Unstructured}}; + + auto* inline_mesh_blueprint_subcommand = + app.add_subcommand("inline_mesh_blueprint") + ->description("Options for setting up a simple inline Blueprint mesh") + ->fallthrough(); + inline_mesh_blueprint_subcommand->callback([this]() { inlineMeshKind = InlineMeshKind::Blueprint; }); + + inline_mesh_blueprint_subcommand->add_option("--min", boxMins) + ->description("Min bounds for box mesh (x,y[,z])") + ->expected(2, 3) + ->required(); + inline_mesh_blueprint_subcommand->add_option("--max", boxMaxs) + ->description("Max bounds for box mesh (x,y[,z])") + ->expected(2, 3) + ->required(); + inline_mesh_blueprint_subcommand->add_option("--res", boxResolution) + ->description("Resolution of the box mesh (i,j[,k])") + ->expected(2, 3) + ->required(); + auto* inline_mesh_blueprint_dim = + inline_mesh_blueprint_subcommand->add_option("-d,--dimension", boxDim) + ->description("Dimension of the box mesh") + ->check(axom::CLI::PositiveNumber) + ->required(); + inline_mesh_blueprint_subcommand->add_option("--topology", blueprintTopologyType) + ->description("Blueprint topology type for the inline mesh") + ->capture_default_str() + ->transform(axom::CLI::CheckedTransformer(blueprintTopoMap, axom::CLI::ignore_case)); +#endif + // we want either the mesh_file or an inline mesh mesh_file->excludes(inline_mesh_dim); inline_mesh_dim->excludes(mesh_file); +#if defined(AXOM_USE_CONDUIT) + mesh_file->excludes(inline_mesh_blueprint_dim); + inline_mesh_blueprint_dim->excludes(mesh_file); + inline_mesh_blueprint_subcommand->excludes(mesh_file); + inline_mesh_blueprint_subcommand->excludes(inline_mesh_subcommand); +#endif } app.add_option("--background-material", backgroundMaterial) @@ -522,6 +652,18 @@ void save_quadrature_points(mfem::QuadratureFunction* positions) #endif } +#if defined(AXOM_USE_CONDUIT) && defined(CONDUIT_RELAY_IO_HDF5_ENABLED) +void save_blueprint_mesh(const conduit::Node& n_mesh) +{ + #ifdef CONDUIT_RELAY_MPI_ENABLED + conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, "shaping_blueprint", "hdf5", MPI_COMM_WORLD); + #else + conduit::relay::io::blueprint::save_mesh(n_mesh, "shaping_blueprint", "hdf5"); + #endif + SLIC_INFO_ROOT("Saved shaped Blueprint mesh to 'shaping_blueprint'."); +} +#endif + //------------------------------------------------------------------------------ int main(int argc, char** argv) { @@ -592,24 +734,46 @@ int main(int argc, char** argv) //--------------------------------------------------------------------------- // Load the computational mesh //--------------------------------------------------------------------------- - auto originalMeshDC = params.loadComputationalMesh(); + std::unique_ptr originalMeshDC; +#if defined(AXOM_USE_CONDUIT) + std::unique_ptr originalBlueprintMeshDS; + sidre::Group* originalBlueprintMeshGroup = nullptr; +#endif //--------------------------------------------------------------------------- // Set up DataCollection for shaping //--------------------------------------------------------------------------- +#if defined(AXOM_USE_MFEM) mfem::Mesh* shapingMesh = nullptr; constexpr bool dc_owns_data = true; sidre::MFEMSidreDataCollection shapingDC("shaping", shapingMesh, dc_owns_data); +#endif + if(params.usesInlineBlueprintMesh()) + { +#if defined(AXOM_USE_CONDUIT) + originalBlueprintMeshDS = params.createBlueprintBoxMesh(); + originalBlueprintMeshGroup = originalBlueprintMeshDS->getRoot()->getGroup("mesh"); +#else + SLIC_ERROR_ROOT("inline_mesh_blueprint requires Axom to be configured with Conduit."); +#endif + } + else { + originalMeshDC = params.loadComputationalMesh(); +#if defined(AXOM_USE_MFEM) shapingDC.SetMeshNodesName("positions"); auto* pmesh = dynamic_cast(originalMeshDC->GetMesh()); shapingMesh = (pmesh != nullptr) ? new mfem::ParMesh(*pmesh) : new mfem::Mesh(*originalMeshDC->GetMesh()); shapingDC.SetMesh(shapingMesh); +#endif } AXOM_ANNOTATE_END("load mesh"); - printMeshInfo(shapingDC.GetMesh(), "After loading"); + if(!params.usesInlineBlueprintMesh()) + { + printMeshInfo(shapingDC.GetMesh(), "After loading"); + } //--------------------------------------------------------------------------- // Initialize the shaping query object @@ -619,16 +783,46 @@ int main(int argc, char** argv) switch(params.shapingMethod) { case ShapingMethod::Sampling: - shaper = new quest::SamplingShaper(params.policy, - axom::policyToDefaultAllocatorID(params.policy), - params.shapeSet, - &shapingDC); + if(params.usesInlineBlueprintMesh()) + { +#if defined(AXOM_USE_CONDUIT) + shaper = new quest::SamplingShaper(params.policy, + axom::policyToDefaultAllocatorID(params.policy), + params.shapeSet, + originalBlueprintMeshGroup, + "mesh"); +#else + SLIC_ERROR_ROOT("inline_mesh_blueprint requires Axom to be configured with Conduit."); +#endif + } + else + { + shaper = new quest::SamplingShaper(params.policy, + axom::policyToDefaultAllocatorID(params.policy), + params.shapeSet, + &shapingDC); + } break; case ShapingMethod::Intersection: - shaper = new quest::IntersectionShaper(params.policy, - axom::policyToDefaultAllocatorID(params.policy), - params.shapeSet, - &shapingDC); + if(params.usesInlineBlueprintMesh()) + { +#if defined(AXOM_USE_CONDUIT) + shaper = new quest::IntersectionShaper(params.policy, + axom::policyToDefaultAllocatorID(params.policy), + params.shapeSet, + originalBlueprintMeshGroup, + "mesh"); +#else + SLIC_ERROR_ROOT("inline_mesh_blueprint requires Axom to be configured with Conduit."); +#endif + } + else + { + shaper = new quest::IntersectionShaper(params.policy, + axom::policyToDefaultAllocatorID(params.policy), + params.shapeSet, + &shapingDC); + } break; } SLIC_ASSERT_MSG(shaper != nullptr, "Invalid shaping method selected!"); @@ -645,7 +839,10 @@ int main(int argc, char** argv) // 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"); + if(shaper->getDC() != nullptr) + { + shaper->getDC()->AssociateMaterialSet("vol_frac", "material"); + } // Set specific parameters for a SamplingShaper, if appropriate if(auto* samplingShaper = dynamic_cast(shaper)) @@ -663,11 +860,21 @@ int main(int argc, char** argv) samplingShaper->setSamplingMethod(params.samplingMethod); // register point projectors - if(shapingDC.GetMesh()->Dimension() == 3) + int meshDim = -1; + if(shaper->getDC() != nullptr) + { + meshDim = shapingDC.GetMesh()->Dimension(); + } + else if(params.usesInlineBlueprintMesh()) + { + meshDim = params.boxDim; + } + + if(meshDim == 3) { samplingShaper->setPointProjector32(AxisymmetricProjector32 {}); } - else if(shapingDC.GetMesh()->Dimension() == 2) + else if(meshDim == 2) { samplingShaper->setPointProjector23(Projector23 {}); } @@ -690,35 +897,43 @@ int main(int argc, char** argv) 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()) + if(params.usesInlineBlueprintMesh()) { - auto material = params.backgroundMaterial; - auto name = axom::fmt::format("vol_frac_{}", material); + SLIC_ERROR_IF(!params.backgroundMaterial.empty(), + "Background material import is not yet supported for inline Blueprint sampling meshes."); + } + else + { + std::map initial_grid_functions; - const int order = params.outputOrder; - const int dim = shapingMesh->Dimension(); - const auto basis = mfem::BasisType::Positive; + // 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); - auto* coll = new mfem::L2_FECollection(order, dim, basis); - auto* fes = new mfem::FiniteElementSpace(shapingDC.GetMesh(), coll); - const int sz = fes->GetVSize(); + const int order = params.outputOrder; + const int dim = shapingMesh->Dimension(); + const auto basis = mfem::BasisType::Positive; - auto* view = shapingDC.AllocNamedBuffer(name, sz); - auto* volFrac = new mfem::GridFunction(fes, view->getArray()); - volFrac->MakeOwner(coll); + auto* coll = new mfem::L2_FECollection(order, dim, basis); + auto* fes = new mfem::FiniteElementSpace(shapingDC.GetMesh(), coll); + const int sz = fes->GetVSize(); - (*volFrac) = 1.; + auto* view = shapingDC.AllocNamedBuffer(name, sz); + auto* volFrac = new mfem::GridFunction(fes, view->getArray()); + volFrac->MakeOwner(coll); - shapingDC.RegisterField(name, volFrac); + (*volFrac) = 1.; - initial_grid_functions[material] = shapingDC.GetField(name); - } + shapingDC.RegisterField(name, volFrac); - // Project provided volume fraction grid functions as quadrature point data - samplingShaper->importInitialVolumeFractions(initial_grid_functions); + 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"); @@ -781,26 +996,33 @@ 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()) + if(shaper->getDC() != nullptr) { - if(startsWith(kv.first, "vol_frac_")) + for(auto& kv : shaper->getDC()->GetFieldMap()) { - const auto mat_name = kv.first.substr(9); - auto* gf = kv.second; + 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(); + 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); + const double volume = shaper->allReduceSum(*gf * vol_form); - SLIC_INFO(axom::fmt::format(axom::utilities::locale(), - "Volume of material '{}' is {:.6Lf}", - mat_name, - volume)); + SLIC_INFO(axom::fmt::format(axom::utilities::locale(), + "Volume of material '{}' is {:.6Lf}", + mat_name, + volume)); + } } } + else + { + SLIC_INFO("Volume summaries are not yet implemented for Blueprint-backed shaping in this driver."); + } AXOM_ANNOTATE_END("adjust"); //--------------------------------------------------------------------------- @@ -815,22 +1037,31 @@ int main(int argc, char** argv) } } -#ifdef MFEM_USE_MPI { AXOM_ANNOTATE_SCOPE("save shaping results"); - shaper->getDC()->Save(); - - // Save quadrature sample point positions as a Blueprint mesh in verbose mode. - if(auto* samplingShaper = dynamic_cast(shaper)) + if(shaper->getDC() != nullptr) { - mfem::QuadratureFunction* positions = samplingShaper->getShapeQFunction("positions"); - if(positions && params.isVerbose()) +#ifdef MFEM_USE_MPI + shaper->getDC()->Save(); + + // Save quadrature sample point positions as a Blueprint mesh in verbose mode. + if(auto* samplingShaper = dynamic_cast(shaper)) { - save_quadrature_points(positions); + mfem::QuadratureFunction* positions = samplingShaper->getShapeQFunction("positions"); + if(positions && params.isVerbose()) + { + save_quadrature_points(positions); + } } +#endif + } +#if defined(AXOM_USE_CONDUIT) && defined(CONDUIT_RELAY_IO_HDF5_ENABLED) + else if(const conduit::Node* bpMesh = shaper->getBlueprintMeshNode()) + { + save_blueprint_mesh(*bpMesh); } - } #endif + } delete shaper; From 285eea3677d112fffbda0b0984800712aaf90cf2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 May 2026 17:40:25 -0700 Subject: [PATCH 10/72] Change how we write quadrature mesh. --- src/axom/quest/SamplingShaper.hpp | 109 +++++++++++++++++++++ src/axom/quest/examples/CMakeLists.txt | 3 +- src/axom/quest/examples/shaping_driver.cpp | 67 ++----------- 3 files changed, 117 insertions(+), 62 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 75af40a03a..446807428b 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -36,9 +36,18 @@ #include "mfem.hpp" #include "mfem/linalg/dtensor.hpp" +#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED + #ifdef CONDUIT_RELAY_MPI_ENABLED + #include "conduit_relay_mpi_io_blueprint.hpp" + #else + #include "conduit_relay_io_blueprint.hpp" + #endif +#endif + #include "axom/fmt.hpp" #include +#include namespace axom { @@ -284,6 +293,106 @@ class SamplingShaper : public Shaper return materialQFuncs().Get(name); } + /*! + * \brief Saves the sampling quadrature points as a Blueprint point mesh. + * + * For MFEM-backed sampling, this converts the `"positions"` quadrature + * function to a temporary Blueprint point mesh before saving. For + * Blueprint-backed sampling, this saves the generated quadrature-point + * topology and any fields associated with it. + */ + void saveQuadraturePoints(const std::string& filename) const + { +#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED + conduit::Node n_mesh; + +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) + { + auto* positions = getShapeQFunction("positions"); + if(positions == nullptr) + { + SLIC_WARNING("No MFEM quadrature positions are available to save."); + return; + } + + const int dim = positions->GetSpace()->GetMesh()->Dimension(); + mfem::real_t* X = const_cast(positions->GetData()); + const int npts = positions->Size() / positions->GetVDim(); + const conduit::index_t stride = dim * sizeof(mfem::real_t); + n_mesh["coordsets/coords/type"] = "explicit"; + n_mesh["coordsets/coords/values/x"].set_external(X, npts, 0, stride); + n_mesh["coordsets/coords/values/y"].set_external(X, npts, sizeof(mfem::real_t), stride); + if(dim > 2) + { + n_mesh["coordsets/coords/values/z"].set_external(X, npts, 2 * sizeof(mfem::real_t), stride); + } + n_mesh["topologies/points/type"] = "unstructured"; + n_mesh["topologies/points/coordset"] = "coords"; + n_mesh["topologies/points/elements/shape"] = "point"; + std::vector tmp(npts); + std::iota(tmp.begin(), tmp.end(), 0); + n_mesh["topologies/points/elements/connectivity"].set(tmp); + n_mesh["topologies/points/elements/offsets"].set(tmp); + std::fill(tmp.begin(), tmp.end(), 1); + n_mesh["topologies/points/elements/sizes"].set(tmp); + + #ifdef CONDUIT_RELAY_MPI_ENABLED + conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, "hdf5", MPI_COMM_WORLD); + #else + conduit::relay::io::blueprint::save_mesh(n_mesh, filename, "hdf5"); + #endif + SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); + return; + } +#endif + +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + constexpr const char* quadName = "quadrature_points"; + const conduit::Node& bpMesh = m_bp_state->m_internal_node; + + if(!bpMesh.has_path(axom::fmt::format("coordsets/{}", quadName)) || + !bpMesh.has_path(axom::fmt::format("topologies/{}", quadName))) + { + SLIC_WARNING("No Blueprint quadrature point mesh is available to save."); + return; + } + + n_mesh["coordsets"][quadName].update(bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", quadName))); + n_mesh["topologies"][quadName].update(bpMesh.fetch_existing(axom::fmt::format("topologies/{}", quadName))); + + if(bpMesh.has_path("fields")) + { + const conduit::Node& fields = bpMesh.fetch_existing("fields"); + for(conduit::index_t i = 0; i < fields.number_of_children(); ++i) + { + const conduit::Node& field = fields.child(i); + if(field.has_path("topology") && field.fetch_existing("topology").as_string() == quadName) + { + n_mesh["fields"][field.name()].update(field); + } + } + } + + #ifdef CONDUIT_RELAY_MPI_ENABLED + conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, "hdf5", MPI_COMM_WORLD); + #else + conduit::relay::io::blueprint::save_mesh(n_mesh, filename, "hdf5"); + #endif + SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); + return; + } +#endif + + SLIC_WARNING("No mesh state is available for quadrature-point export."); +#else + AXOM_UNUSED_VAR(filename); + SLIC_WARNING("Quadrature-point export requires Conduit Relay HDF5 support."); +#endif + } + private: std::unique_ptr createMFEMState() override { diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index d6194e5d16..31468fdee3 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -190,10 +190,11 @@ if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI COMMAND quest_shaping_driver_ex -i ${shaping_data_dir}/circles.yaml --method sampling + --verbose inline_mesh_blueprint --min -6 -6 --max 6 6 --res 25 25 -d 2 NUM_MPI_TASKS ${_nranks}) set_tests_properties(${_testname} PROPERTIES - PASS_REGULAR_EXPRESSION "Saved shaped Blueprint mesh to 'shaping_blueprint'.") + PASS_REGULAR_EXPRESSION "Saved quadrature point mesh to 'shaping_quadrature'.") endif() set(_testname quest_shaping_driver_ex_sampling_balls_and_jacks) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 091e4e6c68..c227f15ac2 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -617,53 +617,6 @@ void finalizeLogger() } //------------------------------------------------------------------------------ -/// Write the quadrature points as a Blueprint mesh. -void save_quadrature_points(mfem::QuadratureFunction* positions) -{ -#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED - const int dim = positions->GetSpace()->GetMesh()->Dimension(); - - conduit::Node n_mesh; - mfem::real_t* X = const_cast(positions->GetData()); - const int npts = positions->Size() / positions->GetVDim(); - const conduit::index_t stride = dim * sizeof(mfem::real_t); - n_mesh["coordsets/coords/type"] = "explicit"; - n_mesh["coordsets/coords/values/x"].set_external(X, npts, 0, stride); - n_mesh["coordsets/coords/values/y"].set_external(X, npts, sizeof(mfem::real_t), stride); - if(dim > 2) - { - n_mesh["coordsets/coords/values/z"].set_external(X, npts, 2 * sizeof(mfem::real_t), stride); - } - n_mesh["topologies/points/type"] = "unstructured"; - n_mesh["topologies/points/coordset"] = "coords"; - n_mesh["topologies/points/elements/shape"] = "point"; - std::vector tmp(npts); - std::iota(tmp.begin(), tmp.end(), 0); - n_mesh["topologies/points/elements/connectivity"].set(tmp); - n_mesh["topologies/points/elements/offset"].set(tmp); - std::fill(tmp.begin(), tmp.end(), 1); - n_mesh["topologies/points/elements/sizes"].set(tmp); - - #ifdef CONDUIT_RELAY_MPI_ENABLED - conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, "shaping_quadrature", "hdf5", MPI_COMM_WORLD); - #else - conduit::relay::io::blueprint::save_mesh(n_mesh, "shaping_quadrature", "hdf5"); - #endif -#endif -} - -#if defined(AXOM_USE_CONDUIT) && defined(CONDUIT_RELAY_IO_HDF5_ENABLED) -void save_blueprint_mesh(const conduit::Node& n_mesh) -{ - #ifdef CONDUIT_RELAY_MPI_ENABLED - conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, "shaping_blueprint", "hdf5", MPI_COMM_WORLD); - #else - conduit::relay::io::blueprint::save_mesh(n_mesh, "shaping_blueprint", "hdf5"); - #endif - SLIC_INFO_ROOT("Saved shaped Blueprint mesh to 'shaping_blueprint'."); -} -#endif - //------------------------------------------------------------------------------ int main(int argc, char** argv) { @@ -1043,24 +996,16 @@ int main(int argc, char** argv) { #ifdef MFEM_USE_MPI shaper->getDC()->Save(); - - // Save quadrature sample point positions as a Blueprint mesh in verbose mode. - if(auto* samplingShaper = dynamic_cast(shaper)) - { - mfem::QuadratureFunction* positions = samplingShaper->getShapeQFunction("positions"); - if(positions && params.isVerbose()) - { - save_quadrature_points(positions); - } - } #endif } -#if defined(AXOM_USE_CONDUIT) && defined(CONDUIT_RELAY_IO_HDF5_ENABLED) - else if(const conduit::Node* bpMesh = shaper->getBlueprintMeshNode()) + + if(auto* samplingShaper = dynamic_cast(shaper)) { - save_blueprint_mesh(*bpMesh); + if(params.isVerbose()) + { + samplingShaper->saveQuadraturePoints("shaping_quadrature"); + } } -#endif } delete shaper; From 56463ca2658f9db86041b5642ef521ce2ae2dc3d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 May 2026 19:30:15 -0700 Subject: [PATCH 11/72] Save blueprint mesh with sampled fields to disk. --- src/axom/quest/SamplingShaper.hpp | 31 +++++++++++--- src/axom/quest/Shaper.cpp | 40 +++++++++++++++++++ src/axom/quest/Shaper.hpp | 14 +++++++ .../quest/detail/shaping/PrimitiveSampler.hpp | 4 +- .../quest/detail/shaping/shaping_helpers.hpp | 17 ++++++++ src/axom/quest/examples/shaping_driver.cpp | 15 +------ 6 files changed, 98 insertions(+), 23 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 446807428b..e18cc71e0b 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -36,11 +36,12 @@ #include "mfem.hpp" #include "mfem/linalg/dtensor.hpp" +#include "conduit/conduit_relay_io.hpp" #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED #ifdef CONDUIT_RELAY_MPI_ENABLED - #include "conduit_relay_mpi_io_blueprint.hpp" + #include "conduit/conduit_relay_mpi_io_blueprint.hpp" #else - #include "conduit_relay_io_blueprint.hpp" + #include "conduit/conduit_relay_io_blueprint.hpp" #endif #endif @@ -338,9 +339,9 @@ class SamplingShaper : public Shaper n_mesh["topologies/points/elements/sizes"].set(tmp); #ifdef CONDUIT_RELAY_MPI_ENABLED - conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, "hdf5", MPI_COMM_WORLD); + conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, outputProtocol(), m_comm); #else - conduit::relay::io::blueprint::save_mesh(n_mesh, filename, "hdf5"); + conduit::relay::io::blueprint::save_mesh(n_mesh, filename, outputProtocol()); #endif SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); return; @@ -377,9 +378,9 @@ class SamplingShaper : public Shaper } #ifdef CONDUIT_RELAY_MPI_ENABLED - conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, "hdf5", MPI_COMM_WORLD); + conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, outputProtocol(), m_comm); #else - conduit::relay::io::blueprint::save_mesh(n_mesh, filename, "hdf5"); + conduit::relay::io::blueprint::save_mesh(n_mesh, filename, outputProtocol()); #endif SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); return; @@ -818,6 +819,20 @@ class SamplingShaper : public Shaper initialMessage)); } + /*! + * \brief Save the shaping results to disk. + * + * \param extra Save extra data when available. + */ + virtual void saveResults(bool extra) override + { + Shaper::saveResults(extra); + if(extra) + { + saveQuadraturePoints("shaping_quadrature"); + } + } + private: void ensureSamplingPositions(shaping::SamplingMFEMState& mfemState) { @@ -1112,6 +1127,10 @@ class SamplingShaper : public Shaper const bool reuseExisting = hadExistingMaterial && shape.getGeometry().hasGeometry(); quest::shaping::copyShapeIntoMaterial(shapeFuncCopy, materialFunc, reuseExisting); + if(shape.getGeometry().hasGeometry()) + { + meshState.deleteShapeFunction(axom::fmt::format("inout_{}", shapeName)); + } delete shapeFuncCopy; shapeFuncCopy = nullptr; diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index e95614cc03..047d63bc04 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -17,6 +17,15 @@ #include "axom/fmt.hpp" +#include "conduit/conduit_relay_io.hpp" +#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED + #ifdef CONDUIT_RELAY_MPI_ENABLED + #include "conduit/conduit_relay_mpi_io_blueprint.hpp" + #else + #include "conduit/conduit_relay_io_blueprint.hpp" + #endif +#endif + namespace axom { namespace quest @@ -413,6 +422,15 @@ void Shaper::ensureBlueprintMeshIsUnstructured() } #endif +std::string Shaper::outputProtocol() const +{ +#if defined(CONDUIT_RELAY_IO_HDF5_ENABLED) + return "hdf5"; +#else + return "yaml"; +#endif +} + #if defined(AXOM_USE_MFEM) bool Shaper::verifyMFEMInputMesh(std::string& whyBad) const { @@ -427,6 +445,28 @@ bool Shaper::verifyMFEMInputMesh(std::string& whyBad) const } #endif +void Shaper::saveResults(bool AXOM_UNUSED_PARAM(extra)) +{ +#ifdef MFEM_USE_MPI + // If the target mesh was MFEM, save it. + if(getDC() != nullptr) + { + getDC()->Save(); + } +#endif +#if defined(AXOM_USE_CONDUIT) + // If the target mesh was Blueprint, save it. + if(m_bp_state != nullptr) + { + const std::string filename("shaping"); + #if defined(CONDUIT_RELAY_MPI_ENABLED) + conduit::relay::mpi::io::blueprint::save_mesh(m_bp_state->m_internal_node, filename, outputProtocol(), m_comm); + #else + conduit::relay::io::blueprint::save_mesh(m_bp_state->m_internal_node, filename, outputProtocol()); + #endif + } +#endif +} // ---------------------------------------------------------------------------- int Shaper::getRank() const diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index 6610ba43d1..8530b4f79d 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -184,6 +184,13 @@ class Shaper ///@} + /*! + * \brief Save the shaping results to disk. + * + * \param extra Save extra data when available. + */ + virtual void saveResults(bool extra); + /*! * \brief Helper to apply a parallel sum reduction to a quantity * @@ -311,6 +318,13 @@ class Shaper void ensureBlueprintMeshIsUnstructured(); #endif + /*! + * \brief Get the protocol to use for Blueprint output. + * + * \return "hdf5" when possible, otherwise "yaml". + */ + std::string outputProtocol() const; + #if defined(AXOM_USE_MFEM) //! \brief MFEM meshes currently have no additional validation here. bool verifyMFEMInputMesh(std::string& whyBad) const; diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index 45bf70c552..c2c1c31df6 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -190,9 +190,7 @@ class PrimitiveSampler "A projector callback function is required when FromDim != ToDim"); auto* mesh = mfemState.m_dc->GetMesh(); - SLIC_ASSERT(mesh != nullptr); - //const int NE = mesh->GetNE(); - //const int dim = mesh->Dimension(); + SLIC_ERROR_IF(mesh != nullptr, "No input mesh"); AXOM_UNUSED_VAR(sampleRes); AXOM_UNUSED_VAR(quadratureType); diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 55bd5313db..041aeb0c01 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -204,6 +204,11 @@ struct SamplingMFEMState : public MFEMState return m_inoutShapeQFuncs.Get(name); } + void deleteShapeFunction(const std::string& AXOM_UNUSED_PARAM(name)) + { + // TODO: remove the function from m_inoutShapeQFuncs if it exists. + } + mfem::QuadratureFunction* getMaterialFunction(const std::string& name) { return m_inoutMaterialQFuncs.Get(name); @@ -261,6 +266,18 @@ struct BlueprintState : nullptr; } + void deleteShapeFunction(const std::string& name) + { + if(m_internal_node.has_path("fields")) + { + conduit::Node &n_fields = m_internal_node["fields"]; + if(n_fields.has_path(name)) + { + n_fields.remove(name); + } + } + } + conduit::Node* getMaterialFunction(const std::string& name) { return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index c227f15ac2..a190df6c17 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -992,20 +992,7 @@ int main(int argc, char** argv) { AXOM_ANNOTATE_SCOPE("save shaping results"); - if(shaper->getDC() != nullptr) - { -#ifdef MFEM_USE_MPI - shaper->getDC()->Save(); -#endif - } - - if(auto* samplingShaper = dynamic_cast(shaper)) - { - if(params.isVerbose()) - { - samplingShaper->saveQuadraturePoints("shaping_quadrature"); - } - } + shaper->saveResults(params.isVerbose()); } delete shaper; From d3668f4b7034ee29795ee23de45511f46ec62cbe Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 20 May 2026 16:13:55 -0700 Subject: [PATCH 12/72] Moved some methods to a cpp file --- src/axom/quest/CMakeLists.txt | 1 + src/axom/quest/SamplingShaper.cpp | 411 +++++++++++++ src/axom/quest/SamplingShaper.hpp | 482 +++------------- .../quest/detail/shaping/shaping_helpers.hpp | 546 ++++++++++-------- src/axom/quest/examples/shaping_driver.cpp | 24 +- 5 files changed, 791 insertions(+), 673 deletions(-) create mode 100644 src/axom/quest/SamplingShaper.cpp diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index 35e0451286..3f65b6746d 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -199,6 +199,7 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE) endif() if(MFEM_FOUND) + list(APPEND quest_sources SamplingShaper.cpp) list(APPEND quest_headers SamplingShaper.hpp detail/shaping/InOutSampler.hpp detail/shaping/PrimitiveSampler.hpp diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp new file mode 100644 index 0000000000..8f30b451f5 --- /dev/null +++ b/src/axom/quest/SamplingShaper.cpp @@ -0,0 +1,411 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#include "axom/quest/SamplingShaper.hpp" +#include "axom/quest/detail/shaping/shaping_helpers.hpp" + +namespace axom +{ +namespace quest +{ + bool SamplingShaper::verifyInputMeshImpl(std::string& whyBad) const + { + bool rval = true; + +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + rval = verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(whyBad); + } +#endif + +#if defined(AXOM_USE_MFEM) + if(getDC() != nullptr) + { + rval = verifyMFEMInputMesh(whyBad); + } +#endif + + return rval; + } + +#if defined(AXOM_USE_CONDUIT) + void SamplingShaper::saveBlueprintFile(const conduit::Node &n_mesh, const std::string &filename) const + { + #ifdef CONDUIT_RELAY_MPI_ENABLED + conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, outputProtocol(), m_comm); + #else + conduit::relay::io::blueprint::save_mesh(n_mesh, filename, outputProtocol()); + #endif + } +#endif + + void SamplingShaper::saveQuadraturePoints(const std::string& filename) const + { +#if defined(AXOM_USE_CONDUIT) + conduit::Node n_mesh; + + // Save the quadrature points from MFEM as a Blueprint file. +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) + { + auto* positions = shapeQFuncs().Get("positions"); + if(positions == nullptr) + { + SLIC_WARNING("No MFEM quadrature positions are available to save."); + return; + } + + const int dim = positions->GetSpace()->GetMesh()->Dimension(); + mfem::real_t* X = const_cast(positions->GetData()); + const int npts = positions->Size() / positions->GetVDim(); + const conduit::index_t stride = dim * sizeof(mfem::real_t); + n_mesh["coordsets/coords/type"] = "explicit"; + n_mesh["coordsets/coords/values/x"].set_external(X, npts, 0, stride); + n_mesh["coordsets/coords/values/y"].set_external(X, npts, sizeof(mfem::real_t), stride); + if(dim > 2) + { + n_mesh["coordsets/coords/values/z"].set_external(X, npts, 2 * sizeof(mfem::real_t), stride); + } + n_mesh["topologies/points/type"] = "unstructured"; + n_mesh["topologies/points/coordset"] = "coords"; + n_mesh["topologies/points/elements/shape"] = "point"; + std::vector tmp(npts); + std::iota(tmp.begin(), tmp.end(), 0); + n_mesh["topologies/points/elements/connectivity"].set(tmp); + n_mesh["topologies/points/elements/offsets"].set(tmp); + std::fill(tmp.begin(), tmp.end(), 1); + n_mesh["topologies/points/elements/sizes"].set(tmp); + + saveBlueprintFile(n_mesh, filename); + SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); + return; + } +#endif + + // Save the Blueprint quadrature point mesh as a Blueprint file. + if(m_bp_state != nullptr) + { + constexpr const char* quadName = "quadrature_points"; + const conduit::Node& bpMesh = m_bp_state->m_internal_node; + + if(!bpMesh.has_path(axom::fmt::format("coordsets/{}", quadName)) || + !bpMesh.has_path(axom::fmt::format("topologies/{}", quadName))) + { + SLIC_WARNING("No Blueprint quadrature point mesh is available to save."); + return; + } + + n_mesh["coordsets"][quadName].update(bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", quadName))); + n_mesh["topologies"][quadName].update(bpMesh.fetch_existing(axom::fmt::format("topologies/{}", quadName))); + + if(bpMesh.has_path("fields")) + { + const conduit::Node& fields = bpMesh.fetch_existing("fields"); + for(conduit::index_t i = 0; i < fields.number_of_children(); ++i) + { + const conduit::Node& field = fields.child(i); + if(field.has_path("topology") && field.fetch_existing("topology").as_string() == quadName) + { + n_mesh["fields"][field.name()].update(field); + } + } + } + + saveBlueprintFile(n_mesh, filename); + SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); + return; + } + SLIC_WARNING("No mesh state is available for quadrature-point export."); +#else + AXOM_UNUSED_VAR(filename); + SLIC_WARNING("Quadrature-point export requires Conduit Relay HDF5 support."); +#endif + } + + void SamplingShaper::loadShape(const klee::Shape& shape) + { + if(useWindingNumberSampler(shape)) + { + const std::string shapePath = + axom::utilities::filesystem::prefixRelativePath(shape.getGeometry().getPath(), m_prefixPath); + SLIC_INFO_ROOT("Reading file: " << shapePath << "..."); + // Read the MFEM file as curved polygon contours for winding number intersection. + quest::MFEMReader reader; + reader.setFileName(shapePath); + const int rc = reader.read(m_contours); + + SLIC_ERROR_IF(rc != quest::MFEMReader::READ_SUCCESS, + axom::fmt::format("Failed to read MFEM shape '{}' from file '{}'.", + shape.getName(), + shapePath)); + } + else + { + Shaper::loadShape(shape); + } + } + +void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const klee::Shape& shape) + { + AXOM_ANNOTATE_SCOPE("prepareShapeQuery"); + + internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug + : slic::message::Warning); + + if(!shape.getGeometry().hasGeometry()) + { + return; + } + + SLIC_INFO_ROOT(axom::fmt::format("{:-^80}", " Generating the spatial index ")); + + const auto& shapeName = shape.getName(); + + // Initialize the sampler based on shape format + // note: ignoring the global shapeDimension for now since it's causing problems + // reading c2c when the dimension is Three + AXOM_UNUSED_VAR(shapeDimension); + const auto format = this->shapeFormat(shape); + if(useWindingNumberSampler(shape)) + { + m_sampler = std::make_unique(shapeName, m_contours.view()); + } + else if(format == "c2c" || format == "mfem") + { + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + } + else if(format == "stl") + { + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + } + else if(format == "proe") + { + using Policy = runtime_policy::Policy; + switch(this->getExecutionPolicy()) + { + case Policy::seq: + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + break; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case Policy::omp: + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case Policy::cuda: + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case Policy::hip: + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + break; +#endif + default: + SLIC_ERROR("Unsupported execution policy for PrimitiveSampler3D"); + break; + } + } + + SLIC_ASSERT(hasValidSampler()); + + // Use visitor to initialize the sampler + std::visit( + [this](auto& sampler) { + using T = std::decay_t; + if constexpr(std::is_same_v) + { + // no op -- monostate + } + else if constexpr(is_wnsampler_v) + { + sampler->computeBounds(); + sampler->initSpatialIndex(this->m_vertexWeldThreshold); + } + else if constexpr(is_inoutsampler_v) + { + sampler->computeBounds(); + sampler->initSpatialIndex(this->m_vertexWeldThreshold); + } + else if constexpr(is_primitivesampler_v) + { + sampler->computeBounds(); + sampler->initSpatialIndex(); + } + }, + m_sampler); + + // Output some logging info and dump the mesh + if(this->isVerbose() && this->getRank() == 0) + { + if(m_surfaceMesh != nullptr) + { + const int nVerts = m_surfaceMesh->getNumberOfNodes(); + const int nCells = m_surfaceMesh->getNumberOfCells(); + SLIC_INFO(axom::fmt::format("After welding, surface mesh has {} vertices and {} elements.", + nVerts, + nCells)); + mint::write_vtk(m_surfaceMesh.get(), + axom::fmt::format("melded_shape_mesh_{}.vtk", shapeName)); + } + else if(!m_contours.empty()) + { + SLIC_INFO(axom::fmt::format("Contours contain {} curved polygons.", m_contours.size())); + } + } + } + +#if defined(AXOM_USE_MFEM) + /// Determines whether we are using an anisotropic quadrature that we need to work around in MFEM. + bool SamplingShaper::usesAnisotropicCustomTensorQuadrature() const + { + if(m_quadratureType == axom::numerics::QuadratureType::Invalid) + { + return false; + } + + switch(meshDimension()) + { + case 2: + return m_sampleResolution[0] != m_sampleResolution[1]; + case 3: + return m_sampleResolution[0] != m_sampleResolution[1] || + m_sampleResolution[0] != m_sampleResolution[2]; + default: + return false; + } + } + + /** + * \brief Import an initial set of material volume fractions before shaping + * + * \param [in] initialGridFuncions The input data as a map from material names to grid functions + * + * The imported grid functions are interpolated at quadrature points and registered + * with the supplied names as material-based quadrature fields + */ + void SamplingShaper::importInitialVolumeFractions(const std::map& initialGridFunctions) + { + internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug + : slic::message::Warning); + + auto& mfemState = samplingMFEMState(); + auto* mesh = mfemState.m_dc->GetMesh(); + ensureSamplingPositions(mfemState); + auto* positionsQSpace = mfemState.m_inoutShapeQFuncs.Get("positions")->GetSpace(); + + // Interpolate grid functions at quadrature points & register material quad functions + // assume all elements have same integration rule + for(auto& entry : initialGridFunctions) + { + const auto& name = entry.first; + auto* gf = entry.second; + + SLIC_INFO_ROOT(axom::fmt::format("Importing volume fraction field for '{}' material", name)); + + if(gf == nullptr) + { + SLIC_WARNING( + axom::fmt::format("Skipping missing volume fraction field for material '{}'", name)); + continue; + } + + auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); + const auto& ir = matQFunc->GetSpace()->GetIntRule(0); + + if(usesAnisotropicCustomTensorQuadrature()) + { + // Avoid MFEM's tensor quadrature interpolation path only for + // anisotropic custom quad/hex rules. MFEM infers a single q1d from + // ir.GetNPoints(), which cannot represent per-direction sample counts + // such as 3 x 5 or 3 x 5 x 2. + mfem::Vector elemValues; + mfem::Vector qfuncValues; + for(int elem = 0; elem < mesh->GetNE(); ++elem) + { + gf->GetValues(elem, ir, elemValues); + matQFunc->GetValues(elem, qfuncValues); + qfuncValues = elemValues; + } + } + else + { + const auto* interp = gf->FESpace()->GetQuadratureInterpolator(ir); + SLIC_ERROR_IF(interp == nullptr, + axom::fmt::format("Could not create a quadrature interpolator while " + "importing volume fractions for '{}'.", + name)); + interp->Values(*gf, *matQFunc); + } + + const auto matName = axom::fmt::format("mat_inout_{}", name); + materialQFuncs().Register(matName, matQFunc, true); + } + } +#endif + + void SamplingShaper::printRegisteredFieldNames(const std::string& initialMessage) + { +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) + { + shaping::printRegisteredFieldNames(samplingMFEMState(), + m_knownMaterials, + m_vfSampling, + initialMessage); + return; + } +#endif +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + shaping::printRegisteredFieldNames(*m_bp_state, + m_knownMaterials, + m_vfSampling, + initialMessage); + return; + } +#endif + SLIC_INFO_ROOT(axom::fmt::format("SamplingShaper {} has no registered fields.", + initialMessage)); + } + + void SamplingShaper::saveResults(bool extra) + { + Shaper::saveResults(extra); + if(extra) + { + saveQuadraturePoints("shaping_quadrature"); + } + } + + void SamplingShaper::computeVolumeFractionsForMaterial(const std::string& matField) + { +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) + { + shaping::computeVolumeFractionsForMaterial( + samplingMFEMState(), + matField, + m_volfracOrder, + m_sampleResolution, + m_quadratureType); + return; + } +#endif +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + shaping::computeVolumeFractionsForMaterial(*m_bp_state, matField); + return; + } +#endif + SLIC_ERROR("No mesh state is available for SamplingShaper."); + } + + +} // end namespace quest +} // end namespace axom diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index e18cc71e0b..4399559c83 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -139,6 +139,8 @@ class SamplingShaper : public Shaper >; public: +#if defined(AXOM_USE_MFEM) + /// MFEM-compatible constructor SamplingShaper(RuntimePolicy execPolicy, int allocatorId, const klee::ShapeSet& shapeSet, @@ -147,8 +149,10 @@ class SamplingShaper : public Shaper { initializeSamplingMFEMState(); } +#endif #if defined(AXOM_USE_CONDUIT) + /// Sidre-compatible constructor SamplingShaper(RuntimePolicy execPolicy, int allocatorId, const klee::ShapeSet& shapeSet, @@ -157,6 +161,7 @@ class SamplingShaper : public Shaper : Shaper(execPolicy, allocatorId, shapeSet, bpMesh, topo) { } + /// Blueprint-compatible constructor SamplingShaper(RuntimePolicy execPolicy, int allocatorId, const klee::ShapeSet& shapeSet, @@ -260,29 +265,9 @@ class SamplingShaper : public Shaper ///@} -protected: - bool verifyInputMeshImpl(std::string& whyBad) const override - { - bool rval = true; - -#if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr) - { - rval = verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(whyBad); - } -#endif - #if defined(AXOM_USE_MFEM) - if(getDC() != nullptr) - { - rval = verifyMFEMInputMesh(whyBad); - } -#endif + // NOTE: These methods are used in tests. - return rval; - } - -public: /// Returns a pointer to the quadrature function associated with shape \a name if it exists, else nullptr mfem::QuadratureFunction* getShapeQFunction(const std::string& name) const { @@ -293,6 +278,26 @@ class SamplingShaper : public Shaper { return materialQFuncs().Get(name); } +#endif +protected: + /*! + * \brief Verifies the input mesh. + * + * \param[out] whyBad A string containing the reason the mesh was bad. + * + * \return True if the mesh is ok, false if it is bad. The \a whyBad string is set when false. + */ + bool verifyInputMeshImpl(std::string& whyBad) const override; + +#if defined(AXOM_USE_CONDUIT) + /*! + * \brief Save a Blueprint file. + * + * \param n_mesh The Blueprint mesh to save. + * \param filename The name of the file to save. + */ + void saveBlueprintFile(const conduit::Node &n_mesh, const std::string &filename) const; +#endif /*! * \brief Saves the sampling quadrature points as a Blueprint point mesh. @@ -302,104 +307,16 @@ class SamplingShaper : public Shaper * Blueprint-backed sampling, this saves the generated quadrature-point * topology and any fields associated with it. */ - void saveQuadraturePoints(const std::string& filename) const - { -#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED - conduit::Node n_mesh; + void saveQuadraturePoints(const std::string& filename) const; #if defined(AXOM_USE_MFEM) - if(m_mfem_state != nullptr) - { - auto* positions = getShapeQFunction("positions"); - if(positions == nullptr) - { - SLIC_WARNING("No MFEM quadrature positions are available to save."); - return; - } - - const int dim = positions->GetSpace()->GetMesh()->Dimension(); - mfem::real_t* X = const_cast(positions->GetData()); - const int npts = positions->Size() / positions->GetVDim(); - const conduit::index_t stride = dim * sizeof(mfem::real_t); - n_mesh["coordsets/coords/type"] = "explicit"; - n_mesh["coordsets/coords/values/x"].set_external(X, npts, 0, stride); - n_mesh["coordsets/coords/values/y"].set_external(X, npts, sizeof(mfem::real_t), stride); - if(dim > 2) - { - n_mesh["coordsets/coords/values/z"].set_external(X, npts, 2 * sizeof(mfem::real_t), stride); - } - n_mesh["topologies/points/type"] = "unstructured"; - n_mesh["topologies/points/coordset"] = "coords"; - n_mesh["topologies/points/elements/shape"] = "point"; - std::vector tmp(npts); - std::iota(tmp.begin(), tmp.end(), 0); - n_mesh["topologies/points/elements/connectivity"].set(tmp); - n_mesh["topologies/points/elements/offsets"].set(tmp); - std::fill(tmp.begin(), tmp.end(), 1); - n_mesh["topologies/points/elements/sizes"].set(tmp); - - #ifdef CONDUIT_RELAY_MPI_ENABLED - conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, outputProtocol(), m_comm); - #else - conduit::relay::io::blueprint::save_mesh(n_mesh, filename, outputProtocol()); - #endif - SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); - return; - } -#endif - -#if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr) - { - constexpr const char* quadName = "quadrature_points"; - const conduit::Node& bpMesh = m_bp_state->m_internal_node; - - if(!bpMesh.has_path(axom::fmt::format("coordsets/{}", quadName)) || - !bpMesh.has_path(axom::fmt::format("topologies/{}", quadName))) - { - SLIC_WARNING("No Blueprint quadrature point mesh is available to save."); - return; - } - - n_mesh["coordsets"][quadName].update(bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", quadName))); - n_mesh["topologies"][quadName].update(bpMesh.fetch_existing(axom::fmt::format("topologies/{}", quadName))); - - if(bpMesh.has_path("fields")) - { - const conduit::Node& fields = bpMesh.fetch_existing("fields"); - for(conduit::index_t i = 0; i < fields.number_of_children(); ++i) - { - const conduit::Node& field = fields.child(i); - if(field.has_path("topology") && field.fetch_existing("topology").as_string() == quadName) - { - n_mesh["fields"][field.name()].update(field); - } - } - } - - #ifdef CONDUIT_RELAY_MPI_ENABLED - conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, outputProtocol(), m_comm); - #else - conduit::relay::io::blueprint::save_mesh(n_mesh, filename, outputProtocol()); - #endif - SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); - return; - } -#endif - - SLIC_WARNING("No mesh state is available for quadrature-point export."); -#else - AXOM_UNUSED_VAR(filename); - SLIC_WARNING("Quadrature-point export requires Conduit Relay HDF5 support."); -#endif - } - -private: + /// Create the internal MFEM state. This is called by the Shaper::Shaper MFEM constructor. std::unique_ptr createMFEMState() override { return std::make_unique(); } + /// void initializeSamplingMFEMState() { // Shaper constructs its MFEM state in the base constructor, so upgrade it @@ -450,6 +367,7 @@ class SamplingShaper : public Shaper { return samplingMFEMState().m_inoutArrays; } +#endif bool hasValidSampler() const { return !std::holds_alternative(m_sampler); } @@ -497,139 +415,11 @@ class SamplingShaper : public Shaper * * \param shape The shape to load. */ - void loadShape(const klee::Shape& shape) override - { - if(useWindingNumberSampler(shape)) - { - const std::string shapePath = - axom::utilities::filesystem::prefixRelativePath(shape.getGeometry().getPath(), m_prefixPath); - SLIC_INFO_ROOT("Reading file: " << shapePath << "..."); - // Read the MFEM file as curved polygon contours for winding number intersection. - quest::MFEMReader reader; - reader.setFileName(shapePath); - const int rc = reader.read(m_contours); - - SLIC_ERROR_IF(rc != quest::MFEMReader::READ_SUCCESS, - axom::fmt::format("Failed to read MFEM shape '{}' from file '{}'.", - shape.getName(), - shapePath)); - } - else - { - Shaper::loadShape(shape); - } - } + void loadShape(const klee::Shape& shape) override; /// Initializes the spatial index for shaping - void prepareShapeQuery(klee::Dimensions shapeDimension, const klee::Shape& shape) override - { - AXOM_ANNOTATE_SCOPE("prepareShapeQuery"); - - internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug - : slic::message::Warning); - - if(!shape.getGeometry().hasGeometry()) - { - return; - } + void prepareShapeQuery(klee::Dimensions shapeDimension, const klee::Shape& shape) override; - SLIC_INFO_ROOT(axom::fmt::format("{:-^80}", " Generating the spatial index ")); - - const auto& shapeName = shape.getName(); - - // Initialize the sampler based on shape format - // note: ignoring the global shapeDimension for now since it's causing problems - // reading c2c when the dimension is Three - AXOM_UNUSED_VAR(shapeDimension); - const auto format = this->shapeFormat(shape); - if(useWindingNumberSampler(shape)) - { - m_sampler = std::make_unique(shapeName, m_contours.view()); - } - else if(format == "c2c" || format == "mfem") - { - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - } - else if(format == "stl") - { - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - } - else if(format == "proe") - { - using Policy = runtime_policy::Policy; - switch(this->getExecutionPolicy()) - { - case Policy::seq: - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - break; -#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - case Policy::omp: - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - break; -#endif -#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - case Policy::cuda: - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - break; -#endif -#if defined(AXOM_RUNTIME_POLICY_USE_HIP) - case Policy::hip: - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - break; -#endif - default: - SLIC_ERROR("Unsupported execution policy for PrimitiveSampler3D"); - break; - } - } - - SLIC_ASSERT(hasValidSampler()); - - // Use visitor to initialize the sampler - std::visit( - [this](auto& sampler) { - using T = std::decay_t; - if constexpr(std::is_same_v) - { - // no op -- monostate - } - else if constexpr(is_wnsampler_v) - { - sampler->computeBounds(); - sampler->initSpatialIndex(this->m_vertexWeldThreshold); - } - else if constexpr(is_inoutsampler_v) - { - sampler->computeBounds(); - sampler->initSpatialIndex(this->m_vertexWeldThreshold); - } - else if constexpr(is_primitivesampler_v) - { - sampler->computeBounds(); - sampler->initSpatialIndex(); - } - }, - m_sampler); - - // Output some logging info and dump the mesh - if(this->isVerbose() && this->getRank() == 0) - { - if(m_surfaceMesh != nullptr) - { - const int nVerts = m_surfaceMesh->getNumberOfNodes(); - const int nCells = m_surfaceMesh->getNumberOfCells(); - SLIC_INFO(axom::fmt::format("After welding, surface mesh has {} vertices and {} elements.", - nVerts, - nCells)); - mint::write_vtk(m_surfaceMesh.get(), - axom::fmt::format("melded_shape_mesh_{}.vtk", shapeName)); - } - else if(!m_contours.empty()) - { - SLIC_INFO(axom::fmt::format("Contours contain {} curved polygons.", m_contours.size())); - } - } - } void runShapeQuery(const klee::Shape& shape) override { @@ -700,6 +490,7 @@ class SamplingShaper : public Shaper ///@} public: +#if defined(AXOM_USE_MFEM) /** * \brief Import an initial set of material volume fractions before shaping * @@ -708,65 +499,12 @@ class SamplingShaper : public Shaper * The imported grid functions are interpolated at quadrature points and registered * with the supplied names as material-based quadrature fields */ - void importInitialVolumeFractions(const std::map& initialGridFunctions) - { - internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug - : slic::message::Warning); - - auto& mfemState = samplingMFEMState(); - auto* mesh = mfemState.m_dc->GetMesh(); - ensureSamplingPositions(mfemState); - auto* positionsQSpace = mfemState.m_inoutShapeQFuncs.Get("positions")->GetSpace(); - - // Interpolate grid functions at quadrature points & register material quad functions - // assume all elements have same integration rule - for(auto& entry : initialGridFunctions) - { - const auto& name = entry.first; - auto* gf = entry.second; - - SLIC_INFO_ROOT(axom::fmt::format("Importing volume fraction field for '{}' material", name)); - - if(gf == nullptr) - { - SLIC_WARNING( - axom::fmt::format("Skipping missing volume fraction field for material '{}'", name)); - continue; - } - - auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); - const auto& ir = matQFunc->GetSpace()->GetIntRule(0); - - if(usesAnisotropicCustomTensorQuadrature(*mesh)) - { - // Avoid MFEM's tensor quadrature interpolation path only for - // anisotropic custom quad/hex rules. MFEM infers a single q1d from - // ir.GetNPoints(), which cannot represent per-direction sample counts - // such as 3 x 5 or 3 x 5 x 2. - mfem::Vector elemValues; - mfem::Vector qfuncValues; - for(int elem = 0; elem < mesh->GetNE(); ++elem) - { - gf->GetValues(elem, ir, elemValues); - matQFunc->GetValues(elem, qfuncValues); - qfuncValues = elemValues; - } - } - else - { - const auto* interp = gf->FESpace()->GetQuadratureInterpolator(ir); - SLIC_ERROR_IF(interp == nullptr, - axom::fmt::format("Could not create a quadrature interpolator while " - "importing volume fractions for '{}'.", - name)); - interp->Values(*gf, *matQFunc); - } - - const auto matName = axom::fmt::format("mat_inout_{}", name); - materialQFuncs().Register(matName, matQFunc, true); - } - } + void importInitialVolumeFractions(const std::map& initialGridFunctions); +#endif + /*! + * \brief Turn the in/out samples into material in/out fields + */ void adjustVolumeFractions() override { AXOM_ANNOTATE_SCOPE("adjustVolumeFractions"); @@ -793,52 +531,22 @@ class SamplingShaper : public Shaper /// Prints out the names of the registered fields related to shapes and materials /// This function is intended to help with debugging - void printRegisteredFieldNames(const std::string& initialMessage) - { -#if defined(AXOM_USE_MFEM) - if(m_mfem_state != nullptr) - { - shaping::printRegisteredFieldNames(samplingMFEMState(), - m_knownMaterials, - m_vfSampling, - initialMessage); - return; - } -#endif -#if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr) - { - shaping::printRegisteredFieldNames(*m_bp_state, - m_knownMaterials, - m_vfSampling, - initialMessage); - return; - } -#endif - SLIC_INFO_ROOT(axom::fmt::format("SamplingShaper {} has no registered fields.", - initialMessage)); - } + void printRegisteredFieldNames(const std::string& initialMessage); /*! * \brief Save the shaping results to disk. * * \param extra Save extra data when available. */ - virtual void saveResults(bool extra) override - { - Shaper::saveResults(extra); - if(extra) - { - saveQuadraturePoints("shaping_quadrature"); - } - } + virtual void saveResults(bool extra) override; private: +#if defined(AXOM_USE_MFEM) void ensureSamplingPositions(shaping::SamplingMFEMState& mfemState) { shaping::generateSamplingPositions(mfemState, m_sampleResolution, m_quadratureType); } - +#endif #if defined(AXOM_USE_CONDUIT) void ensureSamplingPositions(shaping::BlueprintState& bpState) { @@ -846,18 +554,25 @@ class SamplingShaper : public Shaper } #endif - static int meshDimension(const shaping::SamplingMFEMState& mfemState) + /// Return the mesh dimension. + int meshDimension() const { - return mfemState.m_dc->GetMesh()->Dimension(); - } - + const int InvalidDimension = -1; + int dim = InvalidDimension; #if defined(AXOM_USE_CONDUIT) - int meshDimension(const shaping::BlueprintState& bpState) const - { - AXOM_UNUSED_VAR(bpState); - return getBlueprintMeshDimension(); - } + if(m_mfem_state) + { + dim = m_bp_state->meshDimension(); + } #endif +#if defined(AXOM_USE_CONDUIT) + if(dim == InvalidDimension && m_bp_state) + { + dim = m_bp_state->meshDimension(); + } +#endif + return dim; + } // Handles 2D or 3D shaping for compatible samplers, based on the template and associated parameter template @@ -869,7 +584,7 @@ class SamplingShaper : public Shaper ensureSamplingPositions(meshState); } - const int meshDim = meshDimension(meshState); + const int meshDim = meshDimension(); switch(m_vfSampling) { case shaping::VolFracSampling::SAMPLE_AT_QPTS: @@ -992,7 +707,7 @@ class SamplingShaper : public Shaper void runShapeQueryImpl(shaping::PrimitiveSampler* sampler) { auto runImpl = [this, sampler](auto& meshState) { - const int meshDim = meshDimension(meshState); + const int meshDim = meshDimension(); if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) { ensureSamplingPositions(meshState); @@ -1145,78 +860,15 @@ class SamplingShaper : public Shaper * * \param [in] matField The name of the material */ - void computeVolumeFractionsForMaterial(const std::string& matField) - { -#if defined(AXOM_USE_MFEM) - if(m_mfem_state != nullptr) - { - shaping::computeVolumeFractionsForMaterial( - samplingMFEMState(), - matField, - m_volfracOrder, - m_sampleResolution, - m_quadratureType); - return; - } -#endif -#if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr) - { - shaping::computeVolumeFractionsForMaterial(*m_bp_state, matField); - return; - } -#endif - SLIC_ERROR("No mesh state is available for SamplingShaper."); - } - - bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh) const - { - if(m_quadratureType == axom::numerics::QuadratureType::Invalid) - { - return false; - } - - switch(mesh.GetTypicalElementGeometry()) - { - case mfem::Geometry::SQUARE: - return m_sampleResolution[0] != m_sampleResolution[1]; - case mfem::Geometry::CUBE: - return m_sampleResolution[0] != m_sampleResolution[1] || - m_sampleResolution[0] != m_sampleResolution[2]; - default: - return false; - } - } - - void assembleVolumeFractionRHS(const mfem::FiniteElementSpace& fes, - mfem::QuadratureFunction& inout, - const mfem::IntegrationRule& sampleIR, - mfem::Vector& b) const - { - mfem::QuadratureFunctionCoefficient qfc(inout); - mfem::DomainLFIntegrator rhs(qfc, &sampleIR); + void computeVolumeFractionsForMaterial(const std::string& matField); - if(usesAnisotropicCustomTensorQuadrature(*fes.GetMesh())) - { - mfem::Vector elemVec; - mfem::Array elemVDofs; - for(int elem = 0; elem < fes.GetNE(); ++elem) - { - rhs.AssembleRHSElementVect(*fes.GetFE(elem), *fes.GetElementTransformation(elem), elemVec); - fes.GetElementVDofs(elem, elemVDofs); - b.AddElementVector(elemVDofs, elemVec); - } - } - else - { - mfem::Array elem_marker(fes.GetNE()); - elem_marker.HostWrite(); - elem_marker = 1; - elem_marker.ReadWrite(); - rhs.AssembleDevice(fes, elem_marker, b); - } - } + /*! + * \brief Determines whether we are using an anisotropic quadrature that we need to work around in MFEM. + * + * \return True if the quadrature in use is anisotropic; false otherwise. + */ + bool usesAnisotropicCustomTensorQuadrature() const; private: // Holds an instance of the 2D or 3D sampler; only one can be active at a time diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 041aeb0c01..15f8ced1aa 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -153,6 +153,12 @@ template using PointProjector = axom::function(const primal::Point&)>; +enum class VolFracSampling : int +{ + SAMPLE_AT_DOFS, + SAMPLE_AT_QPTS +}; + #if defined(AXOM_USE_MFEM) /*! @@ -169,6 +175,9 @@ using QFunctionCollection = mfem::NamedFieldsMap; using DenseTensorCollection = mfem::NamedFieldsMap; using MFEMArrayCollection = mfem::NamedFieldsMap>; +/*! + * \brief Contains the mesh and state used for shaping. + */ struct MFEMState { virtual ~MFEMState() = default; @@ -177,6 +186,9 @@ struct MFEMState sidre::MFEMSidreDataCollection* m_dc {nullptr}; }; +/*! + * \brief An MFEMState subclass that contains additional data for sampling. + */ struct SamplingMFEMState : public MFEMState { ~SamplingMFEMState() override @@ -194,6 +206,11 @@ struct SamplingMFEMState : public MFEMState m_inoutArrays.clear(); } + int meshDimension() const + { + return m_dc->GetMesh()->Dimension(); + } + mfem::QuadratureFunction* getShapeFunction(const std::string& name) { return m_inoutShapeQFuncs.Get(name); @@ -238,98 +255,6 @@ struct SamplingMFEMState : public MFEMState DenseTensorCollection m_inoutTensors; MFEMArrayCollection m_inoutArrays; }; -#endif - -#if defined(AXOM_USE_CONDUIT) -struct BlueprintState -{ - virtual ~BlueprintState() = default; - - //! @brief Version of the mesh for computations. - axom::sidre::Group* m_group_ptr {nullptr}; - int m_allocator_id {axom::getDefaultAllocatorID()}; - std::string m_topology_name; - //! @brief Mesh in an external Node, when provided as a Node. - conduit::Node* m_external_node_ptr {nullptr}; - //! @brief Internal Node representation used for blueprint operations. - conduit::Node m_internal_node; - - conduit::Node* getShapeFunction(const std::string& name) - { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; - } - - const conduit::Node* getShapeFunction(const std::string& name) const - { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; - } - - void deleteShapeFunction(const std::string& name) - { - if(m_internal_node.has_path("fields")) - { - conduit::Node &n_fields = m_internal_node["fields"]; - if(n_fields.has_path(name)) - { - n_fields.remove(name); - } - } - } - - conduit::Node* getMaterialFunction(const std::string& name) - { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; - } - - const conduit::Node* getMaterialFunction(const std::string& name) const - { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; - } - - conduit::Node* createMaterialFunction(const std::string& name) - { - constexpr const char* quadratureTopologyName = "quadrature_points"; - SLIC_ERROR_IF(!m_internal_node.has_path("coordsets/quadrature_points/values"), - std::string("Cannot create material function '") + name + - "' without quadrature points."); - - conduit::Node& fieldNode = m_internal_node["fields/" + name]; - fieldNode.reset(); - fieldNode["association"] = "element"; - fieldNode["topology"] = quadratureTopologyName; - - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); - conduit::Node& valuesNode = fieldNode["values"]; - valuesNode.set_allocator(conduitAllocatorId); - - const conduit::Node& values = - m_internal_node["coordsets/quadrature_points"].fetch_existing("values"); - const auto numValues = values.child(0).dtype().number_of_elements(); - valuesNode.set(conduit::DataType::float64(numValues)); - - auto fieldValues = axom::bump::utilities::make_array_view(valuesNode); - for(axom::IndexType i = 0; i < fieldValues.size(); ++i) - { - fieldValues[i] = 0.; - } - - return &fieldNode; - } -}; -#endif - -#if defined(AXOM_USE_MFEM) - -enum class VolFracSampling : int -{ - SAMPLE_AT_DOFS, - SAMPLE_AT_QPTS -}; /** * \brief Prints the registered sampling-related field names for an MFEM-backed @@ -340,17 +265,6 @@ void printRegisteredFieldNames(const SamplingMFEMState& mfemState, VolFracSampling vfSampling, const std::string& initialMessage); -#if defined(AXOM_USE_CONDUIT) -/** - * \brief Prints the registered sampling-related field names for a Blueprint-backed - * sampling state. - */ -void printRegisteredFieldNames(const BlueprintState& bpState, - const std::set& knownMaterials, - VolFracSampling vfSampling, - const std::string& initialMessage); -#endif - /** * \brief Utility function to either return a grid function from the DataCollection \a dc, * or to allocate the grud function through the dc, ensuring the memory doesn't leak @@ -393,16 +307,6 @@ void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfunc); -#if defined(AXOM_USE_CONDUIT) -void replaceMaterial(conduit::Node* shapeNode, conduit::Node* materialNode, bool shouldReplace); - -void copyShapeIntoMaterial(const conduit::Node* shapeNode, - conduit::Node* materialNode, - bool reuseExisting = true); - -conduit::Node* cloneInOutFunction(const conduit::Node* node); -#endif - /** * \brief Generates a "position" quadrature function corresponding to the mesh positions and * store it in \a inoutQFuncs. @@ -433,59 +337,7 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, int volfracOrder, int sampleResolution[3], axom::numerics::QuadratureType quadratureType); - -#if defined(AXOM_USE_CONDUIT) -/** - * \brief Returns the element shape for a supported Blueprint topology node. - * - * Structured topologies may omit `elements/shape`, in which case the shape is - * inferred from `elements/dims`. - */ -std::string getBlueprintCellShape(const conduit::Node& topoNode); - -/** - * \brief Generates a derived Blueprint quadrature point mesh within the - * supplied Blueprint mesh node. - * - * \param bpMeshNode The Blueprint mesh node to augment. - * \param topologyName The source topology name to sample. - * \param allocatorID Allocator id used for generated storage. - * \param sampleResolution The sample resolution in each logical dimension. - * \param quadratureType An int corresponding to `mfem::Quadrature1D` when MFEM - * is enabled, or to `axom::numerics::QuadratureType` otherwise. - */ -void generateQuadraturePointMesh(conduit::Node& bpMeshNode, - const std::string& topologyName, - int allocatorID, - int sampleResolution[3], - axom::numerics::QuadratureType quadratureType); - -/** - * \brief Generates a derived Blueprint quadrature point mesh for the supplied - * Blueprint state. - */ -void generateSamplingPositions(BlueprintState& bpState, - int sampleResolution[3], - axom::numerics::QuadratureType quadratureType); - -void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField); -#endif - -/** - * Implements flux-corrected transport (FCT) to correct the solution obtained - * when converting from inout samples (ones and zeros) to a grid function - * on the degrees of freedom such that the volume fractions are doubles - * between 0 and 1 ( \a y_min and \a y_max ) - */ -void FCT_correct(const double* M, - const int s, - const double* m, - const double y_min, // 0 - const double y_max, // 1 - double* xy, - double* fct_mat); // scratch buffer - -/** +/*! * \brief Identity transform for volume fractions from inout samples * * Copies \a inout samples from the quadrature function directly into volume fraction DOFs. @@ -509,7 +361,7 @@ void computeVolumeFractionsIdentity(mfem::DataCollection* dc, * point is inside or outside of relevant shapes. * * \param [in] shapeName The name of the shape used in making data array names. - * \param [in] dc The data collection containing the mesh and associated query points + * \param [in] mfemState The MFEM state containing the mesh and associated query points * \param [inout] inoutQFuncs A collection of quadrature functions for the shape and material * inout samples * \param [in] sampleRes The sampling resolution in each logical direction. @@ -598,87 +450,6 @@ void sampleInOutField(const std::string shapeName, static_cast(numQueryPoints / timer.elapsed()))); } -#if defined(AXOM_USE_CONDUIT) -template -void sampleInOutField(const std::string& shapeName, - shaping::BlueprintState& bpState, - int AXOM_UNUSED_PARAM(sampleRes)[3], - int AXOM_UNUSED_PARAM(quadratureType), - InsideFunc&& checkInside, - PointProjector projector = {}) -{ - using FromPoint = primal::Point; - using ToPoint = primal::Point; - AXOM_ANNOTATE_SCOPE("sampleInOutField"); - - SLIC_ERROR_IF(FromDim != ToDim && !projector, - "A projector callback function is required when FromDim != ToDim"); - - constexpr const char* quadratureCoordsetName = "quadrature_points"; - constexpr const char* quadratureTopologyName = "quadrature_points"; - const std::string inoutName = axom::fmt::format("inout_{}", shapeName); - - conduit::Node& bpMeshNode = bpState.m_internal_node; - SLIC_ERROR_IF(!bpMeshNode.has_path("coordsets/quadrature_points"), - "Missing Blueprint quadrature coordset. Generate sampling positions first."); - SLIC_ERROR_IF(!bpMeshNode.has_path("topologies/quadrature_points"), - "Missing Blueprint quadrature topology. Generate sampling positions first."); - - conduit::Node& inoutNode = bpMeshNode["fields/" + inoutName]; - inoutNode.reset(); - inoutNode["association"] = "element"; - inoutNode["topology"] = quadratureTopologyName; - - namespace utils = axom::bump::utilities; - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); - conduit::Node& valuesNode = inoutNode["values"]; - valuesNode.set_allocator(conduitAllocatorId); - - axom::utilities::Timer timer(true); - axom::bump::views::dispatch_explicit_coordset( - bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)], [&](auto coordsetView) { - using CoordsetView = typename std::decay::type; - - SLIC_ERROR_IF(CoordsetView::dimension() != FromDim, - axom::fmt::format("Expected {}D quadrature point coordset, got {}D.", - FromDim, - CoordsetView::dimension())); - - const auto numQueryPoints = coordsetView.size(); - valuesNode.set(conduit::DataType::float64(numQueryPoints)); - auto inoutValues = utils::make_array_view(valuesNode); - - for(axom::IndexType i = 0; i < numQueryPoints; ++i) - { - FromPoint fromPt; - const auto coordsetPoint = coordsetView[i]; - for(int d = 0; d < FromDim; ++d) - { - fromPt[d] = coordsetPoint[d]; - } - - const ToPoint queryPt = projector ? projector(fromPt) : ToPoint(fromPt.data()); - inoutValues[i] = checkInside(queryPt) ? 1. : 0.; - } - }); - timer.stop(); - - const auto numQueryPoints = bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)] - .fetch_existing("values") - .child(0) - .dtype() - .number_of_elements(); - - SLIC_INFO_ROOT(axom::fmt::format( - axom::utilities::locale(), - "\t Sampling inout field '{}' took {:.3Lf} seconds (@ {:L} queries per second)", - inoutName, - timer.elapsed(), - static_cast(numQueryPoints / timer.elapsed()))); -} -#endif - /*! * \brief Samples the inout field over the indexed geometry, possibly using a * callback function to project the input points (from the computational mesh) @@ -784,7 +555,284 @@ void computeVolumeFractionsBaseline(const std::string& shapeName, } } } -#endif // defined(AXOM_USE_MFEM) +#endif + +#if defined(AXOM_USE_CONDUIT) +//------------------------------------------------------------------------------ +/** + * \brief Returns the element shape for a supported Blueprint topology node. + * + * Structured topologies may omit `elements/shape`, in which case the shape is + * inferred from `elements/dims`. + */ +std::string getBlueprintCellShape(const conduit::Node& topoNode); + +/*! + * \brief A Blueprint-based state class used for shaping. + */ +struct BlueprintState +{ + virtual ~BlueprintState() = default; + + //! @brief Version of the mesh for computations. + axom::sidre::Group* m_group_ptr {nullptr}; + int m_allocator_id {axom::getDefaultAllocatorID()}; + std::string m_topology_name; + //! @brief Mesh in an external Node, when provided as a Node. + conduit::Node* m_external_node_ptr {nullptr}; + //! @brief Internal Node representation used for blueprint operations. + conduit::Node m_internal_node; + + int meshDimension() const + { + const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); + + if(shapeType == "quad") + { + return 2; + } + if(shapeType == "hex") + { + return 3; + } + + SLIC_ERROR(axom::fmt::format("Unsupported Blueprint cell shape '{}'.", shapeType)); + return -1; + } + + const conduit::Node& getBlueprintTopologyNode() const + { + return m_internal_node.fetch_existing("topologies").fetch_existing(m_topology_name); + } + + conduit::Node* getShapeFunction(const std::string& name) + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + const conduit::Node* getShapeFunction(const std::string& name) const + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + void deleteShapeFunction(const std::string& name) + { + // This method lets us delete the shape functions as we go + if(m_internal_node.has_path("fields")) + { + conduit::Node &n_fields = m_internal_node["fields"]; + if(n_fields.has_path(name)) + { + n_fields.remove(name); + } + } + } + + conduit::Node* getMaterialFunction(const std::string& name) + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + const conduit::Node* getMaterialFunction(const std::string& name) const + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + conduit::Node* createMaterialFunction(const std::string& name) + { + constexpr const char* quadratureTopologyName = "quadrature_points"; + SLIC_ERROR_IF(!m_internal_node.has_path("coordsets/quadrature_points/values"), + std::string("Cannot create material function '") + name + + "' without quadrature points."); + + conduit::Node& fieldNode = m_internal_node["fields/" + name]; + fieldNode.reset(); + fieldNode["association"] = "element"; + fieldNode["topology"] = quadratureTopologyName; + + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); + conduit::Node& valuesNode = fieldNode["values"]; + valuesNode.set_allocator(conduitAllocatorId); + + const conduit::Node& values = + m_internal_node["coordsets/quadrature_points"].fetch_existing("values"); + const auto numValues = values.child(0).dtype().number_of_elements(); + valuesNode.set(conduit::DataType::float64(numValues)); + + auto fieldValues = axom::bump::utilities::make_array_view(valuesNode); + for(axom::IndexType i = 0; i < fieldValues.size(); ++i) + { + fieldValues[i] = 0.; + } + + return &fieldNode; + } +}; + +/** + * \brief Prints the registered sampling-related field names for a Blueprint-backed + * sampling state. + */ +void printRegisteredFieldNames(const BlueprintState& bpState, + const std::set& knownMaterials, + VolFracSampling vfSampling, + const std::string& initialMessage); + +void replaceMaterial(conduit::Node* shapeNode, conduit::Node* materialNode, bool shouldReplace); + +void copyShapeIntoMaterial(const conduit::Node* shapeNode, + conduit::Node* materialNode, + bool reuseExisting = true); + +conduit::Node* cloneInOutFunction(const conduit::Node* node); + +/** + * \brief Generates a derived Blueprint quadrature point mesh within the + * supplied Blueprint mesh node. + * + * \param bpMeshNode The Blueprint mesh node to augment. + * \param topologyName The source topology name to sample. + * \param allocatorID Allocator id used for generated storage. + * \param sampleResolution The sample resolution in each logical dimension. + * \param quadratureType An int corresponding to `mfem::Quadrature1D` when MFEM + * is enabled, or to `axom::numerics::QuadratureType` otherwise. + */ +void generateQuadraturePointMesh(conduit::Node& bpMeshNode, + const std::string& topologyName, + int allocatorID, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType); + +/** + * \brief Generates a derived Blueprint quadrature point mesh for the supplied + * Blueprint state. + */ +void generateSamplingPositions(BlueprintState& bpState, + int sampleResolution[3], + axom::numerics::QuadratureType quadratureType); + +void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField); + +/*! + * \brief Samples the inout field over the indexed geometry, possibly using a + * callback function to project the input points (from the computational mesh) + * to query points on the spatial index + * + * \tparam FromDim The dimension of points from the input mesh + * \tparam ToDim The dimension of points on the indexed shape + * \tparam InsideFunc A function that takes a point and returns a bool indicating whether the + * point is inside or outside of relevant shapes. + * + * \param [in] shapeName The name of the shape used in making data array names. + * \param [in] bpState The Blueprint state containing the mesh and associated query points + * \param [in] sampleRes The sampling resolution in each logical direction. + * For custom quadrature families, these values specify the per-direction + * sample counts directly, which in turn determine the quadrature rule used + * in each logical direction. + * \param [in] quadratureType The quadrature type to use to construct the sample point locations. + * \param [in] checkInside The function that determines whether a point is inside. + * \param [in] projector A callback function to apply to points from the input mesh + * before querying them on the spatial index + * + * \note A projector callback must be supplied when \a FromDim is not equal + * to \a ToDim. + */ +template +void sampleInOutField(const std::string& shapeName, + shaping::BlueprintState& bpState, + int AXOM_UNUSED_PARAM(sampleRes)[3], + int AXOM_UNUSED_PARAM(quadratureType), + InsideFunc&& checkInside, + PointProjector projector = {}) +{ + using FromPoint = primal::Point; + using ToPoint = primal::Point; + AXOM_ANNOTATE_SCOPE("sampleInOutField"); + + SLIC_ERROR_IF(FromDim != ToDim && !projector, + "A projector callback function is required when FromDim != ToDim"); + + constexpr const char* quadratureCoordsetName = "quadrature_points"; + constexpr const char* quadratureTopologyName = "quadrature_points"; + const std::string inoutName = axom::fmt::format("inout_{}", shapeName); + + conduit::Node& bpMeshNode = bpState.m_internal_node; + SLIC_ERROR_IF(!bpMeshNode.has_path("coordsets/quadrature_points"), + "Missing Blueprint quadrature coordset. Generate sampling positions first."); + SLIC_ERROR_IF(!bpMeshNode.has_path("topologies/quadrature_points"), + "Missing Blueprint quadrature topology. Generate sampling positions first."); + + conduit::Node& inoutNode = bpMeshNode["fields/" + inoutName]; + inoutNode.reset(); + inoutNode["association"] = "element"; + inoutNode["topology"] = quadratureTopologyName; + + namespace utils = axom::bump::utilities; + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); + conduit::Node& valuesNode = inoutNode["values"]; + valuesNode.set_allocator(conduitAllocatorId); + + axom::utilities::Timer timer(true); + axom::IndexType numQueryPoints = 0; + axom::bump::views::dispatch_explicit_coordset( + bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)], [&](auto coordsetView) { + using CoordsetView = typename std::decay::type; + + SLIC_ERROR_IF(CoordsetView::dimension() != FromDim, + axom::fmt::format("Expected {}D quadrature point coordset, got {}D.", + FromDim, + CoordsetView::dimension())); + + numQueryPoints = coordsetView.size(); + valuesNode.set(conduit::DataType::float64(numQueryPoints)); + auto inoutValues = utils::make_array_view(valuesNode); + + for(axom::IndexType i = 0; i < numQueryPoints; ++i) + { + // Make a FromPoint from the coordsetView. The coordsetView might have + // float or double, depending on the Blueprint data. + FromPoint fromPt; + const auto coordsetPoint = coordsetView[i]; + for(int d = 0; d < FromDim; ++d) + { + fromPt[d] = coordsetPoint[d]; + } + + // Sample at the query point. + const ToPoint queryPt = projector ? projector(fromPt) : ToPoint(fromPt.data()); + inoutValues[i] = checkInside(queryPt) ? 1. : 0.; + } + }); + timer.stop(); + + SLIC_INFO_ROOT(axom::fmt::format( + axom::utilities::locale(), + "\t Sampling inout field '{}' took {:.3Lf} seconds (@ {:L} queries per second)", + inoutName, + timer.elapsed(), + static_cast(numQueryPoints / timer.elapsed()))); +} +#endif + +/** + * Implements flux-corrected transport (FCT) to correct the solution obtained + * when converting from inout samples (ones and zeros) to a grid function + * on the degrees of freedom such that the volume fractions are doubles + * between 0 and 1 ( \a y_min and \a y_max ) + */ +void FCT_correct(const double* M, + const int s, + const double* m, + const double y_min, // 0 + const double y_max, // 1 + double* xy, + double* fct_mat); // scratch buffer } // end namespace shaping } // end namespace quest diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index a190df6c17..2c78a73291 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -197,6 +197,7 @@ struct Input } #if defined(AXOM_USE_CONDUIT) + /// Generate a Blueprint Cartesian mesh, scaled to the bounding box range std::unique_ptr createBlueprintBoxMesh() { auto ds = std::make_unique(); @@ -720,13 +721,10 @@ int main(int argc, char** argv) shapingMesh = (pmesh != nullptr) ? new mfem::ParMesh(*pmesh) : new mfem::Mesh(*originalMeshDC->GetMesh()); shapingDC.SetMesh(shapingMesh); + printMeshInfo(shapingMesh, "After loading"); #endif } AXOM_ANNOTATE_END("load mesh"); - if(!params.usesInlineBlueprintMesh()) - { - printMeshInfo(shapingDC.GetMesh(), "After loading"); - } //--------------------------------------------------------------------------- // Initialize the shaping query object @@ -750,10 +748,12 @@ int main(int argc, char** argv) } else { +#if defined(AXOM_USE_MFEM) shaper = new quest::SamplingShaper(params.policy, axom::policyToDefaultAllocatorID(params.policy), params.shapeSet, &shapingDC); +#endif } break; case ShapingMethod::Intersection: @@ -771,10 +771,12 @@ int main(int argc, char** argv) } else { +#if defined(AXOM_USE_MFEM) shaper = new quest::IntersectionShaper(params.policy, axom::policyToDefaultAllocatorID(params.policy), params.shapeSet, &shapingDC); +#endif } break; } @@ -857,6 +859,7 @@ int main(int argc, char** argv) } else { +#if defined(AXOM_USE_MFEM) std::map initial_grid_functions; // Generate a background material (w/ volume fractions set to 1) if user provided a name @@ -886,6 +889,7 @@ int main(int argc, char** argv) // Project provided volume fraction grid functions as quadrature point data samplingShaper->importInitialVolumeFractions(initial_grid_functions); +#endif } } AXOM_ANNOTATE_END("setup shaping problem"); @@ -949,7 +953,12 @@ int main(int argc, char** argv) // Compute and print volumes of each material's volume fraction //--------------------------------------------------------------------------- using axom::utilities::string::startsWith; - if(shaper->getDC() != nullptr) + if(params.usesInlineBlueprintMesh()) + { + SLIC_INFO("Volume summaries are not yet implemented for Blueprint-backed shaping in this driver."); + } +#if defined(AXOM_USE_MFEM) + else if(shaper->getDC() != nullptr) { for(auto& kv : shaper->getDC()->GetFieldMap()) { @@ -972,10 +981,7 @@ int main(int argc, char** argv) } } } - else - { - SLIC_INFO("Volume summaries are not yet implemented for Blueprint-backed shaping in this driver."); - } +#endif AXOM_ANNOTATE_END("adjust"); //--------------------------------------------------------------------------- From c7d51858c0c474b56793a47413b9c1afb9ec7e56 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 22 May 2026 00:15:03 +0000 Subject: [PATCH 13/72] Adjust shaping build so we can build without MFEM. --- src/axom/quest/CMakeLists.txt | 2 +- src/axom/quest/SamplingShaper.cpp | 14 ++++++---- src/axom/quest/SamplingShaper.hpp | 27 ++++++++++--------- src/axom/quest/Shaper.cpp | 2 +- .../quest/detail/shaping/InOutSampler.hpp | 6 ++++- .../quest/detail/shaping/PrimitiveSampler.hpp | 6 ++++- .../detail/shaping/WindingNumberSampler.hpp | 8 ++++-- src/axom/quest/examples/CMakeLists.txt | 19 +++++++++---- src/axom/quest/examples/shaping_driver.cpp | 4 ++- 9 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index 3f65b6746d..b0048c5943 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -198,7 +198,7 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE) endif() endif() - if(MFEM_FOUND) + if(MFEM_FOUND OR CONDUIT_FOUND) list(APPEND quest_sources SamplingShaper.cpp) list(APPEND quest_headers SamplingShaper.hpp detail/shaping/InOutSampler.hpp diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index d050efab4b..9653b3de05 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -127,6 +127,7 @@ namespace quest void SamplingShaper::loadShape(const klee::Shape& shape) { +#if defined(AXOM_USE_MFEM) if(useWindingNumberSampler(shape)) { const std::string shapePath = @@ -141,11 +142,14 @@ namespace quest axom::fmt::format("Failed to read MFEM shape '{}' from file '{}'.", shape.getName(), shapePath)); + return; } - else - { - Shaper::loadShape(shape); - } +#else + SLIC_ERROR_IF(useWindingNumberSampler(shape), + "SamplingShaper winding-number sampling for MFEM shapes requires MFEM support."); +#endif + + Shaper::loadShape(shape); } void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const klee::Shape& shape) @@ -421,7 +425,7 @@ void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const kl { const int InvalidDimension = -1; int dim = InvalidDimension; -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_MFEM) if(m_mfem_state) { dim = m_mfem_state->meshDimension(); diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 8971e989c6..1cf76548ce 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -20,8 +20,8 @@ #include "axom/mint.hpp" #include "axom/klee.hpp" -#if !defined(AXOM_USE_MFEM) || !defined(AXOM_USE_SIDRE) - #error SamplingShaper requires Axom to be configured with MFEM and Sidre +#if (!defined(AXOM_USE_MFEM) && !defined(AXOM_USE_CONDUIT)) || !defined(AXOM_USE_SIDRE) + #error SamplingShaper requires Axom to be configured with Sidre and either MFEM or Conduit #endif #include "axom/quest/Shaper.hpp" @@ -31,17 +31,20 @@ #include "axom/quest/detail/shaping/InOutSampler.hpp" #include "axom/quest/detail/shaping/PrimitiveSampler.hpp" #include "axom/quest/detail/shaping/WindingNumberSampler.hpp" -#include "axom/quest/io/MFEMReader.hpp" - -#include "mfem.hpp" -#include "mfem/linalg/dtensor.hpp" +#if defined(AXOM_USE_MFEM) + #include "axom/quest/io/MFEMReader.hpp" + #include "mfem.hpp" + #include "mfem/linalg/dtensor.hpp" +#endif -#include "conduit/conduit_relay_io.hpp" -#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED - #ifdef CONDUIT_RELAY_MPI_ENABLED - #include "conduit/conduit_relay_mpi_io_blueprint.hpp" - #else - #include "conduit/conduit_relay_io_blueprint.hpp" +#if defined(AXOM_USE_CONDUIT) + #include "conduit/conduit_relay_io.hpp" + #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED + #ifdef CONDUIT_RELAY_MPI_ENABLED + #include "conduit/conduit_relay_mpi_io_blueprint.hpp" + #else + #include "conduit/conduit_relay_io_blueprint.hpp" + #endif #endif #endif diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 047d63bc04..11227a3c88 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -471,7 +471,7 @@ void Shaper::saveResults(bool AXOM_UNUSED_PARAM(extra)) int Shaper::getRank() const { -#if defined(AXOM_USE_MPI) +#if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) if(!mpiIsActive()) { return 0; diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index c7f8bac004..900fbb3550 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -24,7 +24,9 @@ #include "axom/fmt.hpp" -#include "mfem.hpp" +#if defined(AXOM_USE_MFEM) + #include "mfem.hpp" +#endif namespace axom { @@ -32,8 +34,10 @@ namespace quest { namespace shaping { +#if defined(AXOM_USE_MFEM) using QFunctionCollection = mfem::NamedFieldsMap; using DenseTensorCollection = mfem::NamedFieldsMap; +#endif template class InOutSampler diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index f30eb1074c..fe9d53c4cc 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -23,7 +23,9 @@ #include "axom/fmt.hpp" -#include "mfem.hpp" +#if defined(AXOM_USE_MFEM) + #include "mfem.hpp" +#endif namespace axom { @@ -31,8 +33,10 @@ namespace quest { namespace shaping { +#if defined(AXOM_USE_MFEM) using QFunctionCollection = mfem::NamedFieldsMap; using DenseTensorCollection = mfem::NamedFieldsMap; +#endif template class PrimitiveSampler diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index ede7d446f1..9b13b78df1 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -18,8 +18,10 @@ #include "axom/fmt.hpp" -#include "mfem.hpp" -#include "mfem/linalg/dtensor.hpp" +#if defined(AXOM_USE_MFEM) + #include "mfem.hpp" + #include "mfem/linalg/dtensor.hpp" +#endif #include @@ -30,8 +32,10 @@ namespace quest namespace shaping { +#if defined(AXOM_USE_MFEM) using QFunctionCollection = mfem::NamedFieldsMap; using DenseTensorCollection = mfem::NamedFieldsMap; +#endif namespace detail { diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 31468fdee3..886b41260c 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -144,17 +144,26 @@ if (CONDUIT_FOUND AND UMPIRE_FOUND) endif() # Shaping example ------------------------------------------------------------- -if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI - AND AXOM_ENABLE_SIDRE - AND AXOM_ENABLE_KLEE) +set(shaping_dependencies ) +if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI) + set(AXOM_HAS_MFEM_WITH_MPI TRUE) + list(APPEND shaping_dependencies mfem) +endif() +if(CONDUIT_FOUND) + list(APPEND shaping_dependencies conduit) +endif() + +if((AXOM_HAS_MFEM_WITH_MPI OR CONDUIT_FOUND) AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) axom_add_executable( NAME quest_shaping_driver_ex SOURCES shaping_driver.cpp OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} - DEPENDS_ON ${quest_example_depends} mfem + DEPENDS_ON ${quest_example_depends} ${shaping_dependencies} FOLDER axom/quest/examples ) - +endif() +# Existing tests require MFEM or C2C input. +if(AXOM_HAS_MFEM_WITH_MPI AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) if(AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) # 2D shaping tests set(_nranks 1) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 505a519646..bd3341f61b 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -34,7 +34,9 @@ #endif #endif -#include "mfem.hpp" +#if defined(AXOM_USE_MFEM) + #include "mfem.hpp" +#endif #ifdef AXOM_USE_MPI #include "mpi.h" From 07ce3e1b54ddf99e018035251d6d00073fb053fc Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 22 May 2026 00:40:05 +0000 Subject: [PATCH 14/72] Removed duplicated aliases --- src/axom/quest/detail/shaping/InOutSampler.hpp | 5 ----- src/axom/quest/detail/shaping/PrimitiveSampler.hpp | 5 ----- src/axom/quest/detail/shaping/WindingNumberSampler.hpp | 5 ----- 3 files changed, 15 deletions(-) diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index 900fbb3550..8cb1b906bf 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -34,11 +34,6 @@ namespace quest { namespace shaping { -#if defined(AXOM_USE_MFEM) -using QFunctionCollection = mfem::NamedFieldsMap; -using DenseTensorCollection = mfem::NamedFieldsMap; -#endif - template class InOutSampler { diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index fe9d53c4cc..a25469080a 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -33,11 +33,6 @@ namespace quest { namespace shaping { -#if defined(AXOM_USE_MFEM) -using QFunctionCollection = mfem::NamedFieldsMap; -using DenseTensorCollection = mfem::NamedFieldsMap; -#endif - template class PrimitiveSampler { diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index 9b13b78df1..1b933c887f 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -32,11 +32,6 @@ namespace quest namespace shaping { -#if defined(AXOM_USE_MFEM) -using QFunctionCollection = mfem::NamedFieldsMap; -using DenseTensorCollection = mfem::NamedFieldsMap; -#endif - namespace detail { From 05715c63af0b214a7f811885dcc353220d3ab4e2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 22 May 2026 01:27:35 +0000 Subject: [PATCH 15/72] Some refactoring to separate MFEM and Conduit logic. --- src/axom/quest/CMakeLists.txt | 14 +++---- .../quest/detail/shaping/shaping_helpers.cpp | 7 +++- src/axom/quest/examples/shaping_driver.cpp | 39 ++++++++++++++++--- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index b0048c5943..43ca7deaa3 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -151,10 +151,15 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE) list(APPEND quest_headers Shaper.hpp DiscreteShape.hpp IntersectionShaper.hpp + SamplingShaper.hpp detail/shaping/shaping_helpers.hpp + detail/shaping/InOutSampler.hpp + detail/shaping/PrimitiveSampler.hpp + detail/shaping/WindingNumberSampler.hpp ) list(APPEND quest_sources Shaper.cpp DiscreteShape.cpp + SamplingShaper.cpp detail/shaping/shaping_helpers.cpp ) list(APPEND quest_depends_on klee) @@ -197,15 +202,6 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE) list(APPEND quest_sources detail/clipping/TetMeshClipper.cpp) endif() endif() - - if(MFEM_FOUND OR CONDUIT_FOUND) - list(APPEND quest_sources SamplingShaper.cpp) - list(APPEND quest_headers SamplingShaper.hpp - detail/shaping/InOutSampler.hpp - detail/shaping/PrimitiveSampler.hpp - detail/shaping/WindingNumberSampler.hpp - ) - endif() endif() if(C2C_FOUND) diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 5ef57c7ef8..f3a741c562 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -797,12 +797,15 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, vf->HostReadWrite(); } +#endif // defined(AXOM_USE_MFEM) + #if defined(AXOM_USE_CONDUIT) void printRegisteredFieldNames(const BlueprintState& bpState, const std::set& knownMaterials, VolFracSampling AXOM_UNUSED_PARAM(vfSampling), const std::string& initialMessage) { +#pragma message "Compiling Conduit printRegisteredFieldNames!" auto extractChildren = [](const conduit::Node& node) { std::vector names; if(node.dtype().is_object()) @@ -1147,7 +1150,9 @@ conduit::Node* cloneInOutFunction(const conduit::Node* node) SLIC_ASSERT(node != nullptr); return new conduit::Node(*node); } -#endif +#endif // defined(AXOM_USE_CONDUIT) + +#if defined(AXOM_USE_MFEM) void FCT_correct(const double* M, // Mass matrix const int s, // num dofs diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index bd3341f61b..402c9625ac 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -146,6 +146,7 @@ struct Input bool usesInlineBlueprintMesh() const { return inlineMeshKind == InlineMeshKind::Blueprint; } /// Generate an mfem Cartesian mesh, scaled to the bounding box range +#if defined(AXOM_USE_MFEM) mfem::Mesh* createBoxMesh() { mfem::Mesh* mesh = nullptr; @@ -197,6 +198,7 @@ struct Input return mesh; } +#endif #if defined(AXOM_USE_CONDUIT) /// Generate a Blueprint Cartesian mesh, scaled to the bounding box range @@ -271,6 +273,7 @@ struct Input } #endif + #if defined(AXOM_USE_MFEM) std::unique_ptr loadComputationalMesh() { constexpr bool dc_owns_data = true; @@ -288,6 +291,7 @@ struct Input return dc; } + #endif std::string getDCMeshName() const { @@ -518,6 +522,7 @@ struct Input * * \note In MPI-based configurations, this is a collective call, but only prints on rank 0 */ +#if defined(AXOM_USE_MFEM) void printMeshInfo(mfem::Mesh* mesh, const std::string& prefixMessage = "") { namespace primal = axom::primal; @@ -571,6 +576,7 @@ void printMeshInfo(mfem::Mesh* mesh, const std::string& prefixMessage = "") slic::flushStreams(); } +#endif /// \brief Utility function to initialize the logger void initializeLogger() @@ -690,11 +696,13 @@ int main(int argc, char** argv) //--------------------------------------------------------------------------- // Load the computational mesh //--------------------------------------------------------------------------- - std::unique_ptr originalMeshDC; #if defined(AXOM_USE_CONDUIT) std::unique_ptr originalBlueprintMeshDS; sidre::Group* originalBlueprintMeshGroup = nullptr; #endif +#if defined(AXOM_USE_MFEM) + std::unique_ptr originalMeshDC; +#endif //--------------------------------------------------------------------------- // Set up DataCollection for shaping @@ -715,8 +723,8 @@ int main(int argc, char** argv) } else { - originalMeshDC = params.loadComputationalMesh(); #if defined(AXOM_USE_MFEM) + originalMeshDC = params.loadComputationalMesh(); shapingDC.SetMeshNodesName("positions"); auto* pmesh = dynamic_cast(originalMeshDC->GetMesh()); @@ -724,6 +732,8 @@ int main(int argc, char** argv) (pmesh != nullptr) ? new mfem::ParMesh(*pmesh) : new mfem::Mesh(*originalMeshDC->GetMesh()); shapingDC.SetMesh(shapingMesh); printMeshInfo(shapingMesh, "After loading"); +#else + SLIC_ERROR_ROOT("MFEM-backed meshes in shaping_driver require Axom to be configured with MFEM."); #endif } AXOM_ANNOTATE_END("load mesh"); @@ -796,10 +806,12 @@ int main(int argc, char** argv) // Associate any fields that begin with "vol_frac" with "material" so when // the data collection is written, a matset will be created. +#if defined(AXOM_USE_MFEM) if(shaper->getDC() != nullptr) { shaper->getDC()->AssociateMaterialSet("vol_frac", "material"); } +#endif // Set specific parameters for a SamplingShaper, if appropriate if(auto* samplingShaper = dynamic_cast(shaper)) @@ -816,7 +828,19 @@ int main(int argc, char** argv) res[i] = params.samplingResolution[i]; } } - axom::ArrayView sampleRes(res, shaper->getDC()->GetMesh()->Dimension()); + int meshDim = -1; +#if defined(AXOM_USE_MFEM) + if(shaper->getDC() != nullptr) + { + meshDim = shaper->getDC()->GetMesh()->Dimension(); + } +#endif + if(meshDim < 0 && params.usesInlineBlueprintMesh()) + { + meshDim = params.boxDim; + } + SLIC_ERROR_IF(meshDim < 0, "Unable to determine mesh dimension for sampling setup."); + axom::ArrayView sampleRes(res, meshDim); samplingShaper->setSamplingType(params.vfSampling); samplingShaper->setSamplingResolution(sampleRes); @@ -825,12 +849,15 @@ int main(int argc, char** argv) samplingShaper->setSamplingMethod(params.samplingMethod); // register point projectors - int meshDim = -1; + meshDim = -1; +#if defined(AXOM_USE_MFEM) if(shaper->getDC() != nullptr) { - meshDim = shapingDC.GetMesh()->Dimension(); + meshDim = shaper->getDC()->GetMesh()->Dimension(); } - else if(params.usesInlineBlueprintMesh()) + else +#endif + if(params.usesInlineBlueprintMesh()) { meshDim = params.boxDim; } From 802850cffbd11ee30882e8d5f3e70f5efae324bc Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 22 May 2026 02:32:41 +0000 Subject: [PATCH 16/72] Separated Blueprint and MFEM helpers into different files. --- src/axom/quest/CMakeLists.txt | 8 + .../quest/detail/shaping/shaping_helpers.cpp | 1357 +---------------- .../quest/detail/shaping/shaping_helpers.hpp | 740 +-------- .../shaping/shaping_helpers_blueprint.cpp | 497 ++++++ .../shaping/shaping_helpers_blueprint.hpp | 238 +++ .../detail/shaping/shaping_helpers_mfem.cpp | 886 +++++++++++ .../detail/shaping/shaping_helpers_mfem.hpp | 316 ++++ 7 files changed, 1969 insertions(+), 2073 deletions(-) create mode 100644 src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp create mode 100644 src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp create mode 100644 src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp create mode 100644 src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index 43ca7deaa3..184ea7184d 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -162,6 +162,14 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE) SamplingShaper.cpp detail/shaping/shaping_helpers.cpp ) + if(MFEM_FOUND) + list(APPEND quest_headers detail/shaping/shaping_helpers_mfem.hpp) + list(APPEND quest_sources detail/shaping/shaping_helpers_mfem.cpp) + endif() + if(CONDUIT_FOUND) + list(APPEND quest_headers detail/shaping/shaping_helpers_blueprint.hpp) + list(APPEND quest_sources detail/shaping/shaping_helpers_blueprint.cpp) + endif() list(APPEND quest_depends_on klee) endif() diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index f3a741c562..cc74347073 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -5,1359 +5,6 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "shaping_helpers.hpp" -#include "GenerateQuadratureMesh.hpp" -#include "axom/config.hpp" -#include "axom/core.hpp" -#include "axom/core/numerics/quadrature.hpp" -#include "axom/slic.hpp" -#include "axom/sidre.hpp" - -#include "axom/fmt.hpp" - -#include -#include - -#if defined(AXOM_USE_CONDUIT) - #include "axom/bump/views/dispatch_coordset.hpp" - #include "axom/bump/views/dispatch_topology.hpp" - #include "axom/bump/views/dispatch_unstructured_topology.hpp" - #include "conduit_blueprint_mesh.hpp" -#endif - -#if defined(AXOM_USE_MFEM) - #include "mfem/linalg/dtensor.hpp" -#endif - -namespace axom -{ -namespace quest -{ -namespace shaping -{ - -template -void checkSampleResolution(const MeshState& meshState, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType) -{ - SLIC_ERROR_IF(quadratureType != axom::numerics::QuadratureType::Invalid && sampleResolution.size() != meshState.meshDimension(), "Inconsistent mesh dimension and sample resolutions."); -} - -#if defined(AXOM_USE_CONDUIT) -namespace -{ - -constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; -constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; -constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; -constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; -constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; - -numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureType quadratureType, - int npts, - int allocatorID) -{ - SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); - SLIC_ERROR_IF(!axom::numerics::is_supported_quadrature_type(quadratureType), - axom::fmt::format( - "Quadrature type {} is not yet supported for Blueprint quadrature meshes.", - static_cast(quadratureType))); - - return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); -} - -std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) -{ - const std::string topoType = topoNode.fetch_existing("type").as_string(); - if(topoNode.has_path("elements/shape")) - { - return topoNode.fetch_existing("elements/shape").as_string(); - } - - if(topoType == "structured") - { - const conduit::Node& dimsNode = topoNode.fetch_existing("elements/dims"); - if(dimsNode.has_child("k")) - { - return "hex"; - } - if(dimsNode.has_child("j")) - { - return "quad"; - } - if(dimsNode.has_child("i")) - { - return "line"; - } - - SLIC_ERROR("Structured Blueprint topology is missing recognizable element dims."); - } - - SLIC_ERROR( - axom::fmt::format("Blueprint topology type '{}' is missing 'elements/shape'.", topoType)); - return ""; -} - -template -void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, - const conduit::Node& coordsetNode, - const CoordsetView& coordsetView, - int allocatorID, - const numerics::QuadratureRule& ruleX, - const numerics::QuadratureRule& ruleY, - const numerics::QuadratureRule& ruleZ, - conduit::Node& meshNode) -{ - namespace views = axom::bump::views; - constexpr int SupportedShapes = views::select_shapes(views::Quad_ShapeID, views::Hex_ShapeID); - - views::dispatch_topology( - topoNode, - [&](const auto&, auto topoView) { - GenerateQuadratureMesh generator(topoView, - coordsetView); - generator.setAllocatorID(allocatorID); - generator.execute(topoNode, - coordsetNode, - QUADRATURE_TOPOLOGY_NAME, - QUADRATURE_COORDSET_NAME, - ORIGINAL_ELEMENTS_FIELD_NAME, - QUADRATURE_WEIGHTS_FIELD_NAME, - QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME, - ruleX, - ruleY, - ruleZ, - meshNode); - }); -} - -} // namespace - -std::string getBlueprintCellShape(const conduit::Node& topoNode) -{ - return getBlueprintCellShapeImpl(topoNode); -} -#endif - -#if defined(AXOM_USE_MFEM) - -namespace -{ - -class OwnedQuadratureSpace : public mfem::QuadratureSpace -{ -public: - OwnedQuadratureSpace(mfem::Mesh& mesh, std::unique_ptr ir) - : mfem::QuadratureSpace(mesh, *ir) - , m_ir(std::move(ir)) - { } - -private: - std::unique_ptr m_ir; -}; - -} // namespace - -bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType) -{ - if(quadratureType == axom::numerics::QuadratureType::Invalid) - { - return false; - } - - const auto dim = mesh.Dimension(); - SLIC_ERROR_IF(sampleResolution.size() != static_cast(dim), - "Sample resolution dimension does not match mesh dimension"); - - if(mesh.GetNE() > 0) - { - switch(mesh.GetTypicalElementGeometry()) - { - case mfem::Geometry::SQUARE: - return sampleResolution[0] != sampleResolution[1]; - case mfem::Geometry::CUBE: - return sampleResolution[0] != sampleResolution[1] || sampleResolution[0] != sampleResolution[2]; - default: - return false; - } - } - return mesh.Dimension(); -} - -int to_mfem_quadrature_type(axom::numerics::QuadratureType quadratureType) -{ - switch(quadratureType) - { - case axom::numerics::QuadratureType::Invalid: - return mfem::Quadrature1D::Invalid; - case axom::numerics::QuadratureType::GaussLegendre: - return mfem::Quadrature1D::GaussLegendre; - case axom::numerics::QuadratureType::GaussLobatto: - return mfem::Quadrature1D::GaussLobatto; - case axom::numerics::QuadratureType::OpenUniform: - return mfem::Quadrature1D::OpenUniform; - case axom::numerics::QuadratureType::ClosedUniform: - return mfem::Quadrature1D::ClosedUniform; - case axom::numerics::QuadratureType::OpenHalfUniform: - return mfem::Quadrature1D::OpenHalfUniform; - case axom::numerics::QuadratureType::ClosedGL: - return mfem::Quadrature1D::ClosedGL; - } - - SLIC_ERROR(axom::fmt::format("Invalid quadrature type {}.", static_cast(quadratureType))); - return mfem::Quadrature1D::Invalid; -} - -// Utility function to either return a gf from the dc, or to allocate it through the dc -mfem::GridFunction* getOrAllocateL2GridFunction(mfem::DataCollection* dc, - const std::string& gf_name, - int order, - int dim, - const int basis) -{ - if(dc == nullptr) - { - SLIC_WARNING("Cannot allocate grid function into null data collection"); - return nullptr; - } - - mfem::GridFunction* gf = nullptr; - - if(dc->HasField(gf_name)) - { - gf = dc->GetField(gf_name); - } - else - { - auto* fec = new mfem::L2_FECollection(order, dim, basis); - auto* mesh = dc->GetMesh(); - mfem::FiniteElementSpace* fes = new mfem::FiniteElementSpace(mesh, fec); - - // allocate data through sidre and tell the grid function to use it - // the grid function will manage memory for the fec and fes - auto* sidreDC = dynamic_cast(dc); - if(sidreDC) - { - const int sz = fes->GetVSize(); - auto* vw = sidreDC->AllocNamedBuffer(gf_name, sz); - gf = new mfem::GridFunction(); - gf->MakeRef(fes, vw->getData()); - } - else - { - gf = new mfem::GridFunction(fes); - } - - gf->MakeOwner(fec); - gf->HostReadWrite(); - *gf = 0.; - - dc->RegisterField(gf_name, gf); - } - - return gf; -} - -void replaceMaterial(mfem::QuadratureFunction* shapeQFunc, - mfem::QuadratureFunction* materialQFunc, - bool shapeReplacesMaterial) -{ - SLIC_ASSERT(shapeQFunc != nullptr); - SLIC_ASSERT(materialQFunc != nullptr); - SLIC_ASSERT(materialQFunc->Size() == shapeQFunc->Size()); - - const int SZ = materialQFunc->Size(); - double* mData = materialQFunc->HostReadWrite(); - double* sData = shapeQFunc->HostReadWrite(); - - if(shapeReplacesMaterial) - { - // If shapeReplacesMaterial, clear material samples that are inside current shape - for(int j = 0; j < SZ; ++j) - { - mData[j] = sData[j] > 0 ? 0 : mData[j]; - } - } - else - { - // Otherwise, clear current shape samples that are in the material - for(int j = 0; j < SZ; ++j) - { - sData[j] = mData[j] > 0 ? 0 : sData[j]; - } - } -} - -/// Utility function to copy in_out quadrature samples from one QFunc to another -void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, - mfem::QuadratureFunction* materialQFunc, - bool reuseExisting) -{ - SLIC_ASSERT(shapeQFunc != nullptr); - SLIC_ASSERT(materialQFunc != nullptr); - SLIC_ASSERT(materialQFunc->Size() == shapeQFunc->Size()); - - const int SZ = materialQFunc->Size(); - double* mData = materialQFunc->HostReadWrite(); - const double* sData = shapeQFunc->HostRead(); - - // When reuseExisting, don't reset material values; otherwise, just copy values over - if(reuseExisting) - { - for(int j = 0; j < SZ; ++j) - { - mData[j] = sData[j] > 0 ? 1 : mData[j]; - } - } - else - { - for(int j = 0; j < SZ; ++j) - { - mData[j] = sData[j]; - } - } -} - -mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfunc) -{ - SLIC_ASSERT(qfunc != nullptr); - return new mfem::QuadratureFunction(*qfunc); -} - -void printRegisteredFieldNames(const SamplingMFEMState& mfemState, - const std::set& knownMaterials, - VolFracSampling vfSampling, - const std::string& initialMessage) -{ - SLIC_ASSERT(mfemState.m_dc != nullptr); - - auto extractKeys = [](const auto& map) { - std::vector keys; - for(const auto& kv : map) - { - keys.push_back(kv.first); - } - return keys; - }; - - axom::fmt::memory_buffer out; - axom::fmt::format_to(std::back_inserter(out), - "List of registered fields in the SamplingShaper {}" - "\n\t* Data collection grid funcs: {}" - "\n\t* Data collection qfuncs: {}" - "\n\t* Known materials: {}", - initialMessage, - axom::fmt::join(extractKeys(mfemState.m_dc->GetFieldMap()), ", "), - axom::fmt::join(extractKeys(mfemState.m_dc->GetQFieldMap()), ", "), - axom::fmt::join(knownMaterials, ", ")); - - if(vfSampling == VolFracSampling::SAMPLE_AT_QPTS) - { - axom::fmt::format_to(std::back_inserter(out), - "\n\t* Shape qfuncs: {}" - "\n\t* Mat qfuncs: {}", - axom::fmt::join(extractKeys(mfemState.m_inoutShapeQFuncs), ", "), - axom::fmt::join(extractKeys(mfemState.m_inoutMaterialQFuncs), ", ")); - } - else if(vfSampling == VolFracSampling::SAMPLE_AT_DOFS) - { - axom::fmt::format_to(std::back_inserter(out), - "\n\t* Shaping tensors: {}", - axom::fmt::join(extractKeys(mfemState.m_inoutTensors), ", ")); - } - - SLIC_INFO_ROOT(axom::fmt::to_string(out)); -} - -mfem::QuadratureSpace* makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRes) -{ - SLIC_ASSERT(mesh != nullptr); - const int NE = mesh->GetNE(); - - if(NE < 1) - { - SLIC_WARNING("Mesh has no elements!"); - return nullptr; - } - - // convert requested samples into a compatible polynomial order - // that will use that many samples: 2n-1 and 2n-2 will work - // NOTE: Might be different for simplices - const int sampleOrder = 2 * sampleRes - 1; - return new mfem::QuadratureSpace(mesh, sampleOrder); -} - -mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, - axom::ArrayView sampleRes, - axom::numerics::QuadratureType quadratureType) -{ - SLIC_ASSERT(mesh != nullptr); - const int NE = mesh->GetNE(); - const int dim = mesh->Dimension(); - - SLIC_ERROR_IF(sampleRes.size() != static_cast(dim), - "Sample resolution dimension does not match mesh dimension"); - - if(NE < 1) - { - SLIC_WARNING("Mesh has no elements!"); - return nullptr; - } - - // Make custom integration rule - mfem::IntegrationRule ird[3]; - for(int d = 0; d < dim; d++) - { - SLIC_ERROR_IF(sampleRes[d] < 1, - axom::fmt::format("Invalid sample value {} for dimension {}.", sampleRes[d], d)); - switch(quadratureType) - { - case axom::numerics::QuadratureType::GaussLegendre: - mfem::QuadratureFunctions1D::GaussLegendre(sampleRes[d], &ird[d]); - break; - case axom::numerics::QuadratureType::GaussLobatto: - mfem::QuadratureFunctions1D::GaussLobatto(sampleRes[d], &ird[d]); - break; - case axom::numerics::QuadratureType::OpenUniform: - mfem::QuadratureFunctions1D::OpenUniform(sampleRes[d], &ird[d]); - break; - case axom::numerics::QuadratureType::ClosedUniform: - mfem::QuadratureFunctions1D::ClosedUniform(sampleRes[d], &ird[d]); - break; - case axom::numerics::QuadratureType::OpenHalfUniform: - mfem::QuadratureFunctions1D::OpenHalfUniform(sampleRes[d], &ird[d]); - break; - case axom::numerics::QuadratureType::ClosedGL: - mfem::QuadratureFunctions1D::ClosedGL(sampleRes[d], &ird[d]); - break; - case axom::numerics::QuadratureType::Invalid: - default: - SLIC_ERROR(axom::fmt::format("Invalid quadrature type {}.", static_cast(quadratureType))); - break; - } - } - std::unique_ptr ir; - if(dim == 1) - { - ir = std::make_unique(ird[0]); - } - else if(dim == 2) - { - ir = std::make_unique(ird[0], ird[1]); - } - else if(dim == 3) - { - ir = std::make_unique(ird[0], ird[1], ird[2]); - } - - return new OwnedQuadratureSpace(*mesh, std::move(ir)); -} - -void assembleVolumeFractionRHS(const mfem::FiniteElementSpace& fes, - mfem::QuadratureFunction& inout, - const mfem::IntegrationRule& sampleIR, - bool useAnisotropicAssembly, - mfem::Vector& b) -{ - mfem::QuadratureFunctionCoefficient qfc(inout); - mfem::DomainLFIntegrator rhs(qfc, &sampleIR); - - if(useAnisotropicAssembly) - { - mfem::Vector elemVec; - mfem::Array elemVDofs; - const int NE = fes.GetNE(); - for(int elem = 0; elem < NE; ++elem) - { - rhs.AssembleRHSElementVect(*fes.GetFE(elem), *fes.GetElementTransformation(elem), elemVec); - fes.GetElementVDofs(elem, elemVDofs); - b.AddElementVector(elemVDofs, elemVec); - } - } - else - { - mfem::Array elem_marker(fes.GetNE()); - elem_marker.HostWrite(); - elem_marker = 1; - elem_marker.ReadWrite(); - rhs.AssembleDevice(fes, elem_marker, b); - } -} - -/// Generates a quadrature function corresponding to the mesh "positions" field -void generatePositionsQFunction(mfem::Mesh* mesh, - QFunctionCollection& inoutQFuncs, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType) -{ - SLIC_ASSERT(mesh != nullptr); - const int NE = mesh->GetNE(); - const int dim = mesh->Dimension(); - - if(NE < 1) - { - SLIC_WARNING("Mesh has no elements!"); - return; - } - - // Make a quadrature space to determine the point locations in each element. - mfem::QuadratureSpace* sp = nullptr; - if(quadratureType == axom::numerics::QuadratureType::Invalid) - { - SLIC_ERROR_IF(sampleResolution.empty(), "Invalid sampleResolution."); - sp = makeDefaultQuadratureSpace(mesh, sampleResolution[0]); - } - else - { - sp = makeCustomQuadratureSpace(mesh, sampleResolution, quadratureType); - } - SLIC_ERROR_IF(sp == nullptr, "Null QuadratureSpace."); - - // Assume all elements have the same integration rule - const auto& ir = sp->GetElementIntRule(0); - const int nq = ir.GetNPoints(); - - mfem::QuadratureFunction* pos_coef = new mfem::QuadratureFunction(sp, dim); - pos_coef->SetOwnsSpace(true); - auto pos = mfem::Reshape(pos_coef->HostWrite(), dim, nq, NE); - - if(!usesAnisotropicCustomTensorQuadrature(*mesh, sampleResolution, quadratureType)) - { - const auto* geomFactors = mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); - geomFactors->X.HostRead(); - - // Rearrange positions into quadrature function - for(int i = 0; i < NE; ++i) - { - const int gf_elStartIdx = i * nq * dim; - for(int j = 0; j < dim; ++j) - { - for(int k = 0; k < nq; ++k) - { - // X has dims nqpts x sdim x ne - pos(j, k, i) = geomFactors->X(gf_elStartIdx + (j * nq) + k); - } - } - } - - // Delete the geometric factors associated w/ our quadrature rule - mesh->DeleteGeometricFactors(); - } - else - { - // MFEM's tensor quadrature interpolation assumes the same number of - // points in each logical dimension. For anisotropic custom tensor-product - // rules, map the integration points explicitly through each element. - mfem::DenseMatrix pointMat(dim, nq); - for(int i = 0; i < NE; ++i) - { - auto* transform = sp->GetTransformation(i); - transform->Transform(ir, pointMat); - - for(int j = 0; j < dim; ++j) - { - for(int k = 0; k < nq; ++k) - { - pos(j, k, i) = pointMat(j, k); - } - } - } - } - - // register positions with the QFunction collection, which will handle its deletion - inoutQFuncs.Register("positions", pos_coef, true); -} - -void generateSamplingPositions(SamplingMFEMState& mfemState, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType) -{ - checkSampleResolution(mfemState, sampleResolution, quadratureType); - - if(mfemState.m_inoutShapeQFuncs.Has("positions")) - { - return; - } - - generatePositionsQFunction(mfemState.m_dc->GetMesh(), - mfemState.m_inoutShapeQFuncs, - sampleResolution, - quadratureType); -} - -void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, - const std::string& matField, - int volfracOrder, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType) -{ - AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); - - SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); - auto* inout = mfemState.getMaterialFunction(matField); - SLIC_ASSERT(inout != nullptr); - - auto* dc = mfemState.m_dc; - SLIC_ASSERT(dc != nullptr); - - const auto& sampleIR = inout->GetSpace()->GetIntRule(0); - const int sampleOrder = sampleIR.GetOrder(); - const int sampleNQ = sampleIR.GetNPoints(); - const int sampleSZ = inout->GetSpace()->GetSize(); - - mfem::Mesh* mesh = dc->GetMesh(); - const int dim = mesh->Dimension(); - const int NE = mesh->GetNE(); - - auto samples_per_dim = [=](auto sampleRes, int dim) -> std::string { - switch(dim) - { - case 2: - return axom::fmt::format(" ({} * {})", sampleRes[0], sampleRes[1]); - case 3: - return axom::fmt::format(" ({} * {} * {})", sampleRes[0], sampleRes[1], sampleRes[2]); - default: - return std::string(); - } - }; - - SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), - "In computeVolumeFractions(): num samples per element {}{} | " - "sample polynomial order {} | total samples {:L}", - sampleNQ, - samples_per_dim(sampleResolution, dim), - sampleOrder, - sampleSZ)); - - SLIC_INFO_ROOT( - axom::fmt::format(axom::utilities::locale(), "Mesh has dim {} and {:L} elements", dim, NE)); - - const auto vf_name = axom::fmt::format("vol_frac_{}", matField.substr(10)); - mfem::GridFunction* vf = - getOrAllocateL2GridFunction(dc, vf_name, volfracOrder, dim, mfem::BasisType::Positive); - const mfem::FiniteElementSpace* fes = vf->FESpace(); - const int dofs = fes->GetTypicalFE()->GetDof(); - - mfem::DenseTensor* mass_mat {nullptr}; - const std::string mass_matrix_name = "shaping_mass_matrix"; - if(mfemState.m_inoutTensors.Has(mass_matrix_name)) - { - mass_mat = mfemState.m_inoutTensors.Get(mass_matrix_name); - } - else - { - AXOM_ANNOTATE_SCOPE("mass integrator assemble"); - - mass_mat = new mfem::DenseTensor(dofs, dofs, NE); - mass_mat->HostWrite(); - (*mass_mat) = 0.; - mass_mat->ReadWrite(); - - mfem::ConstantCoefficient one_coef(1.0); - mfem::MassIntegrator mass_integrator(one_coef, &sampleIR); - - if(usesAnisotropicCustomTensorQuadrature(*fes->GetMesh(), sampleResolution, quadratureType)) - { - mfem::DenseMatrix elemMat; - mass_mat->HostWrite(); - for(int elem = 0; elem < NE; ++elem) - { - mass_integrator.AssembleElementMatrix(*fes->GetFE(elem), - *fes->GetElementTransformation(elem), - elemMat); - for(int j = 0; j < dofs; ++j) - { - for(int i = 0; i < dofs; ++i) - { - (*mass_mat)(i, j, elem) = elemMat(i, j); - } - } - } - } - else - { - const int sz = mass_mat->TotalSize(); - mfem::Vector mass_vec; - mfem::Swap(mass_mat->GetMemory(), mass_vec.GetMemory()); - mass_vec.SetSize(sz); - mass_integrator.AssembleEA(*fes, mass_vec, false); - mfem::Swap(mass_mat->GetMemory(), mass_vec.GetMemory()); - } - - mfemState.m_inoutTensors.Register(mass_matrix_name, mass_mat, true); - } - - mfem::DenseTensor* mass_mat_inv {nullptr}; - mfem::Array* mass_mat_pivots {nullptr}; - const std::string minv_name = "shaping_mass_matrix_inv"; - const std::string pivots_name = "shaping_mass_matrix_pivots"; - if(mfemState.m_inoutTensors.Has(minv_name) && mfemState.m_inoutArrays.Has(pivots_name)) - { - mass_mat_inv = mfemState.m_inoutTensors.Get(minv_name); - mass_mat_pivots = mfemState.m_inoutArrays.Get(pivots_name); - } - else - { - AXOM_ANNOTATE_SCOPE("batch lu factor"); - - mass_mat->ReadWrite(); - mass_mat_inv = new mfem::DenseTensor(*mass_mat); - mass_mat_pivots = new mfem::Array(dofs * NE); - - mass_mat_inv->ReadWrite(); - mass_mat_pivots->Write(); - mfem::BatchLUFactor(*mass_mat_inv, *mass_mat_pivots); - - mfemState.m_inoutTensors.Register(minv_name, mass_mat_inv, true); - mfemState.m_inoutArrays.Register(pivots_name, mass_mat_pivots, true); - } - - mfem::DenseTensor* shaping_scratch_buffer {nullptr}; - const std::string scratch_buffer_name = "shaping_scratch_buffer"; - if(mfemState.m_inoutTensors.Has(scratch_buffer_name)) - { - shaping_scratch_buffer = mfemState.m_inoutTensors.Get(scratch_buffer_name); - } - else - { - shaping_scratch_buffer = new mfem::DenseTensor(dofs, dofs, NE); - shaping_scratch_buffer->HostWrite(); - (*shaping_scratch_buffer) = 0.; - mfemState.m_inoutTensors.Register(scratch_buffer_name, shaping_scratch_buffer, true); - } - - axom::utilities::Timer timer(true); - { - mfem::Vector b(fes->GetVSize()); - SLIC_ASSERT(b.Size() == dofs * NE); - { - AXOM_ANNOTATE_SCOPE("domain lf integrator assemble"); - - inout->ReadWrite(); - b.HostWrite(); - b = 0.; - b.ReadWrite(); - - assembleVolumeFractionRHS(*fes, - *inout, - sampleIR, - usesAnisotropicCustomTensorQuadrature(*fes->GetMesh(), - sampleResolution, - quadratureType), - b); - } - inout->HostReadWrite(); - - { - AXOM_ANNOTATE_SCOPE("batch lu solve"); - - mass_mat_inv->Read(); - mass_mat_pivots->Read(); - - vf->HostReadWrite(); - (*vf) = b; - vf->ReadWrite(); - mfem::BatchLUSolve(*mass_mat_inv, *mass_mat_pivots, *vf); - } - mass_mat_inv->HostReadWrite(); - mass_mat_pivots->HostReadWrite(); - - constexpr double minY = 0.; - constexpr double maxY = 1.; - - auto m_d = mfem::Reshape(mass_mat->HostReadWrite(), dofs, dofs, NE); - auto b_d = mfem::Reshape(b.HostReadWrite(), dofs, NE); - auto vf_d = mfem::Reshape(vf->HostReadWrite(), dofs, NE); - auto fct_mat_d = mfem::Reshape(shaping_scratch_buffer->HostReadWrite(), dofs, dofs, NE); - - AXOM_ANNOTATE_BEGIN("fct project"); - axom::for_all(0, NE, [=](int i) { - FCT_correct(&m_d(0, 0, i), - dofs, - &b_d(0, i), - minY, - maxY, - &vf_d(0, i), - &fct_mat_d(0, 0, i)); - }); - AXOM_ANNOTATE_END("fct project"); - } - timer.stop(); - - SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), - "\t Generating volume fractions '{}' took {:.3f} seconds (@ " - "{:L} dofs processed per second)", - vf_name, - timer.elapsed(), - static_cast(fes->GetNDofs() / timer.elapsed()))); - - vf->HostReadWrite(); -} - -#endif // defined(AXOM_USE_MFEM) - -#if defined(AXOM_USE_CONDUIT) -void printRegisteredFieldNames(const BlueprintState& bpState, - const std::set& knownMaterials, - VolFracSampling AXOM_UNUSED_PARAM(vfSampling), - const std::string& initialMessage) -{ -#pragma message "Compiling Conduit printRegisteredFieldNames!" - auto extractChildren = [](const conduit::Node& node) { - std::vector names; - if(node.dtype().is_object()) - { - names.reserve(node.number_of_children()); - for(conduit::index_t i = 0; i < node.number_of_children(); ++i) - { - names.push_back(node.child(i).name()); - } - } - return names; - }; - - auto extractMatchingFields = [&](const std::string& prefix) { - std::vector names; - if(bpState.m_internal_node.has_path("fields")) - { - const conduit::Node& fieldsNode = bpState.m_internal_node.fetch_existing("fields"); - for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) - { - const std::string name = fieldsNode.child(i).name(); - if(axom::utilities::string::startsWith(name, prefix)) - { - names.push_back(name); - } - } - } - return names; - }; - - auto extractOtherFields = [&]() { - std::vector names; - if(bpState.m_internal_node.has_path("fields")) - { - const conduit::Node& fieldsNode = bpState.m_internal_node.fetch_existing("fields"); - for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) - { - const std::string name = fieldsNode.child(i).name(); - if(!axom::utilities::string::startsWith(name, "inout_") && - !axom::utilities::string::startsWith(name, "mat_inout_") && - !axom::utilities::string::startsWith(name, "vol_frac_")) - { - names.push_back(name); - } - } - } - return names; - }; - - const std::vector topologyNames = - bpState.m_internal_node.has_path("topologies") - ? extractChildren(bpState.m_internal_node.fetch_existing("topologies")) - : std::vector {}; - const std::vector coordsetNames = - bpState.m_internal_node.has_path("coordsets") - ? extractChildren(bpState.m_internal_node.fetch_existing("coordsets")) - : std::vector {}; - const std::vector fieldNames = - bpState.m_internal_node.has_path("fields") - ? extractChildren(bpState.m_internal_node.fetch_existing("fields")) - : std::vector {}; - - axom::fmt::memory_buffer out; - axom::fmt::format_to(std::back_inserter(out), - "List of registered fields in the SamplingShaper {}" - "\n\t* Blueprint topologies: {}" - "\n\t* Blueprint coordsets: {}" - "\n\t* Blueprint fields: {}" - "\n\t* Known materials: {}" - "\n\t* Shape inout fields: {}" - "\n\t* Mat inout fields: {}" - "\n\t* Volume fraction fields: {}" - "\n\t* Other Blueprint fields: {}", - initialMessage, - axom::fmt::join(topologyNames, ", "), - axom::fmt::join(coordsetNames, ", "), - axom::fmt::join(fieldNames, ", "), - axom::fmt::join(knownMaterials, ", "), - axom::fmt::join(extractMatchingFields("inout_"), ", "), - axom::fmt::join(extractMatchingFields("mat_inout_"), ", "), - axom::fmt::join(extractMatchingFields("vol_frac_"), ", "), - axom::fmt::join(extractOtherFields(), ", ")); - - SLIC_INFO_ROOT(axom::fmt::to_string(out)); -} - -void generateQuadraturePointMesh(conduit::Node& bpMeshNode, - const std::string& topologyName, - int allocatorID, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType) -{ - if(bpMeshNode.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) - { - return; - } - - const conduit::Node& topoNode = - bpMeshNode.fetch_existing("topologies").fetch_existing(topologyName); - const std::string topoType = topoNode.fetch_existing("type").as_string(); - SLIC_ERROR_IF(topoType != "unstructured" && topoType != "structured", - axom::fmt::format( - "Unsupported Blueprint topology type '{}' for quadrature mesh generation.", - topoType)); - - const std::string shape = shaping::getBlueprintCellShape(topoNode); - SLIC_ERROR_IF(shape != "quad" && shape != "hex", - axom::fmt::format("Unsupported Blueprint element shape '{}' for quadrature mesh generation.", - shape)); - - const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); - const conduit::Node& coordsetNode = bpMeshNode.fetch_existing("coordsets").fetch_existing(coordsetName); - const std::string coordsetType = coordsetNode.fetch_existing("type").as_string(); - SLIC_ERROR_IF(coordsetType != "explicit", - axom::fmt::format("Unsupported Blueprint coordset type '{}' for quadrature mesh generation.", - coordsetType)); - - int selectedAllocatorID = allocatorID; - if(!axom::execution_space::usesAllocId(selectedAllocatorID) && - !axom::execution_space::usesAllocId(selectedAllocatorID) -#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - && !axom::execution_space::usesAllocId(selectedAllocatorID) -#endif -#if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - && !axom::execution_space::usesAllocId(selectedAllocatorID) -#endif - ) - { - selectedAllocatorID = axom::execution_space::allocatorID(); - } - - auto ruleX = getBlueprintQuadratureRule(quadratureType, sampleResolution[0], selectedAllocatorID); - auto ruleY = getBlueprintQuadratureRule(quadratureType, sampleResolution[1], selectedAllocatorID); - auto ruleZ = getBlueprintQuadratureRule(quadratureType, sampleResolution[2], selectedAllocatorID); - - axom::bump::views::dispatch_explicit_coordset(coordsetNode, [&](auto coordsetView) { -#if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - if(axom::execution_space::usesAllocId(selectedAllocatorID)) - { - buildBlueprintQuadratureMesh(topoNode, - coordsetNode, - coordsetView, - selectedAllocatorID, - ruleX, - ruleY, - ruleZ, - bpMeshNode); - return; - } -#endif -#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) - if(axom::execution_space::usesAllocId(selectedAllocatorID)) - { - buildBlueprintQuadratureMesh(topoNode, - coordsetNode, - coordsetView, - selectedAllocatorID, - ruleX, - ruleY, - ruleZ, - bpMeshNode); - return; - } -#endif - if(axom::execution_space::usesAllocId(selectedAllocatorID)) - { - buildBlueprintQuadratureMesh(topoNode, - coordsetNode, - coordsetView, - selectedAllocatorID, - ruleX, - ruleY, - ruleZ, - bpMeshNode); - return; - } - - buildBlueprintQuadratureMesh(topoNode, - coordsetNode, - coordsetView, - selectedAllocatorID, - ruleX, - ruleY, - ruleZ, - bpMeshNode); - }); -} - -void generateSamplingPositions(BlueprintState& bpState, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType) -{ - checkSampleResolution(bpState, sampleResolution, quadratureType); - - if(bpState.m_internal_node.has_path( - axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) - { - return; - } - - generateQuadraturePointMesh(bpState.m_internal_node, - bpState.m_topology_name, - bpState.m_allocator_id, - sampleResolution, - quadratureType); -} - -void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField) -{ - AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); - - SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); - - conduit::Node* inout = bpState.getMaterialFunction(matField); - SLIC_ERROR_IF(inout == nullptr, - axom::fmt::format("Missing Blueprint material field '{}' for volume fraction projection.", - matField)); - - conduit::Node& bpMeshNode = bpState.m_internal_node; - SLIC_ERROR_IF(!bpMeshNode.has_path("fields/originalElements/values"), - "Missing Blueprint originalElements field for volume fraction projection."); - SLIC_ERROR_IF( - !bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") && - !bpMeshNode.has_path("fields/quadratureWeights/values"), - "Missing Blueprint quadrature weight field for volume fraction projection."); - - const conduit::Node& topoNode = - bpMeshNode.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); - - const axom::IndexType numZones = conduit::blueprint::mesh::topology::length(topoNode); - - namespace utils = axom::bump::utilities; - const auto originalElements = - utils::make_array_view(bpMeshNode["fields/originalElements/values"]); - const conduit::Node& quadratureWeightsNode = - bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") - ? bpMeshNode["fields/quadraturePhysicalWeights/values"] - : bpMeshNode["fields/quadratureWeights/values"]; - const auto quadratureWeights = utils::make_array_view(quadratureWeightsNode); - const auto inoutValues = utils::make_array_view(inout->fetch_existing("values")); - - SLIC_ASSERT(originalElements.size() == quadratureWeights.size()); - SLIC_ASSERT(originalElements.size() == inoutValues.size()); - - const std::string vfName = axom::fmt::format("vol_frac_{}", matField.substr(10)); - conduit::Node& vfNode = bpMeshNode["fields/" + vfName]; - vfNode.reset(); - vfNode["association"] = "element"; - vfNode["topology"] = bpState.m_topology_name; - - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); - conduit::Node& valuesNode = vfNode["values"]; - valuesNode.set_allocator(conduitAllocatorId); - valuesNode.set(conduit::DataType::float64(numZones)); - auto vfValues = utils::make_array_view(valuesNode); - axom::Array totalWeights(numZones, numZones, bpState.m_allocator_id); - auto totalWeightsView = totalWeights.view(); - - for(axom::IndexType zoneIdx = 0; zoneIdx < vfValues.size(); ++zoneIdx) - { - vfValues[zoneIdx] = 0.; - totalWeightsView[zoneIdx] = 0.; - } - - for(axom::IndexType pointIdx = 0; pointIdx < inoutValues.size(); ++pointIdx) - { - const conduit::index_t zoneIdx = originalElements[pointIdx]; - SLIC_ASSERT(zoneIdx >= 0); - SLIC_ASSERT(zoneIdx < vfValues.size()); - vfValues[zoneIdx] += inoutValues[pointIdx] * quadratureWeights[pointIdx]; - totalWeightsView[zoneIdx] += quadratureWeights[pointIdx]; - } - - for(axom::IndexType zoneIdx = 0; zoneIdx < vfValues.size(); ++zoneIdx) - { - SLIC_ERROR_IF(axom::utilities::isNearlyEqual(totalWeightsView[zoneIdx], 0.0), - axom::fmt::format( - "Blueprint quadrature weights sum to zero in zone {} during volume fraction projection.", - zoneIdx)); - vfValues[zoneIdx] /= totalWeightsView[zoneIdx]; - } -} - -void replaceMaterial(conduit::Node* shapeNode, - conduit::Node* materialNode, - bool shapeReplacesMaterial) -{ - SLIC_ASSERT(shapeNode != nullptr); - SLIC_ASSERT(materialNode != nullptr); - - namespace utils = axom::bump::utilities; - auto shapeValues = utils::make_array_view(shapeNode->fetch_existing("values")); - auto materialValues = utils::make_array_view(materialNode->fetch_existing("values")); - - SLIC_ASSERT(shapeValues.size() == materialValues.size()); - - for(axom::IndexType i = 0; i < materialValues.size(); ++i) - { - if(shapeReplacesMaterial) - { - materialValues[i] = shapeValues[i] > 0. ? 0. : materialValues[i]; - } - else - { - shapeValues[i] = materialValues[i] > 0. ? 0. : shapeValues[i]; - } - } -} - -void copyShapeIntoMaterial(const conduit::Node* shapeNode, - conduit::Node* materialNode, - bool reuseExisting) -{ - SLIC_ASSERT(shapeNode != nullptr); - SLIC_ASSERT(materialNode != nullptr); - - namespace utils = axom::bump::utilities; - const auto shapeValues = utils::make_array_view(shapeNode->fetch_existing("values")); - auto materialValues = utils::make_array_view(materialNode->fetch_existing("values")); - - SLIC_ASSERT(shapeValues.size() == materialValues.size()); - - if(reuseExisting) - { - for(axom::IndexType i = 0; i < materialValues.size(); ++i) - { - materialValues[i] = shapeValues[i] > 0. ? 1. : materialValues[i]; - } - } - else - { - for(axom::IndexType i = 0; i < materialValues.size(); ++i) - { - materialValues[i] = shapeValues[i]; - } - } -} - -conduit::Node* cloneInOutFunction(const conduit::Node* node) -{ - SLIC_ASSERT(node != nullptr); - return new conduit::Node(*node); -} -#endif // defined(AXOM_USE_CONDUIT) - -#if defined(AXOM_USE_MFEM) - -void FCT_correct(const double* M, // Mass matrix - const int s, // num dofs - const double* m, // rhs (incorporating the inout samples) - const double y_min, // lower bound for FCT - const double y_max, // upper bound for FCt - double* xy, // uncorrected volume fraction dofs - double* fct_mat) // use as scratch buffer -{ - // [IN] - M, s, m, y_min, y_max - // [INOUT] - xy - - constexpr int STACK_CAPACITY = 64; - using StackArray = axom::StackArray; - - // Q0 solutions can't be adjusted conservatively. It is what it is. - if(s == 1) - { - return; - } - - StackArray ML_stack; - StackArray z_stack; - StackArray beta_stack; - axom::Array ML_heap; - axom::Array z_heap; - axom::Array beta_heap; - - double* ML = nullptr; - double* z = nullptr; - double* beta = nullptr; - - if(s <= STACK_CAPACITY) - { - ML = ML_stack.data(); - z = z_stack.data(); - beta = beta_stack.data(); - } - else - { - ML_heap.resize(s); - z_heap.resize(s); - beta_heap.resize(s); - - ML = ML_heap.data(); - z = z_heap.data(); - beta = beta_heap.data(); - } - - // Compute the lumped mass matrix in ML: M.GetRowSums(ML); - for(int r = 0; r < s; ++r) - { - double dot = 0.; - for(int c = 0; c < s; ++c) - { - dot += M[r + c * s]; - } - ML[r] = dot; - } - - double sum_ML = 0.; - double sum_m = 0.; - for(int i = 0; i < s; ++i) - { - sum_ML += ML[i]; - sum_m += m[i]; - } - - const double y_avg = sum_m / sum_ML; - - #ifdef AXOM_DEBUG - constexpr double EPS = 1e-12; - SLIC_WARNING_IF( - !(y_min < y_avg + EPS && y_avg < y_max + EPS), - axom::fmt::format("Average ({}) is out of bounds [{},{}]: ", y_avg, y_min - EPS, y_max + EPS)); - #endif - - double sum_beta = 0.; - for(int i = 0; i < s; ++i) - { - // Some different options for beta: - //beta[i] = 1.0; - beta[i] = ML[i]; - //beta[i] = ML[i]*(1. + 1e-14); - - // The low order flux correction - z[i] = m[i] - ML[i] * y_avg; - sum_beta += beta[i]; - } - - // Make beta_i sum to 1 - for(int i = 0; i < s; ++i) - { - beta[i] /= sum_beta; - } - - for(int i = 1; i < s; ++i) - { - for(int j = 0; j < i; ++j) - { - const int idx = i + j * s; - fct_mat[idx] = M[idx] * (xy[i] - xy[j]) + (beta[j] * z[i] - beta[i] * z[j]); - } - } - - // NOTE: `z' and `beta' are no longer used. - // Zero them out and reuse their memory under different aliases: gp and gm - auto* gp = z; - auto* gm = beta; - for(int t = 0; t < s; ++t) - { - gp[t] = 0.0; - gm[t] = 0.0; - } - - for(int i = 1; i < s; ++i) - { - for(int j = 0; j < i; ++j) - { - const int idx = i + j * s; - const double fij = fct_mat[idx]; - if(fij >= 0.0) - { - gp[i] += fij; - gm[j] -= fij; - } - else - { - gm[i] += fij; - gp[j] -= fij; - } - } - } - - for(int i = 0; i < s; ++i) - { - xy[i] = y_avg; - } - - for(int i = 0; i < s; ++i) - { - const double mi = ML[i]; - const double xyLi = xy[i]; - const double rp = axom::utilities::max(mi * (y_max - xyLi), 0.0); - const double rm = axom::utilities::min(mi * (y_min - xyLi), 0.0); - const double sp = gp[i]; - const double sm = gm[i]; - - gp[i] = (rp < sp) ? rp / sp : 1.0; - gm[i] = (rm > sm) ? rm / sm : 1.0; - } - - for(int i = 1; i < s; ++i) - { - for(int j = 0; j < i; ++j) - { - double fij = fct_mat[i + j * s]; - - const double aij = - fij >= 0.0 ? axom::utilities::min(gp[i], gm[j]) : axom::utilities::min(gm[i], gp[j]); - fij *= aij; - xy[i] += fij / ML[i]; - xy[j] -= fij / ML[j]; - } - } - - #ifdef AXOM_DEBUG - // check that volume fractions are in bounds - for(int i = 0; i < s; ++i) - { - SLIC_WARNING_IF(!(y_min < xy[i] + EPS && xy[i] < y_max + EPS), - axom::fmt::format("Volume fraction {} w/ value {} is out of bounds [{},{}]: ", - i, - xy[i], - y_min - EPS, - y_max + EPS)); - } - #endif -} - -// Note: This function is not currently being used, but might be in the near future -void computeVolumeFractionsIdentity(mfem::DataCollection* dc, - mfem::QuadratureFunction* inout, - const std::string& name) -{ - const int order = inout->GetSpace()->GetIntRule(0).GetOrder(); - - mfem::Mesh* mesh = dc->GetMesh(); - const int dim = mesh->Dimension(); - const int NE = mesh->GetNE(); - - std::cout << axom::fmt::format("Mesh has dim {} and {} elements", dim, NE) << std::endl; - - mfem::L2_FECollection* fec = new mfem::L2_FECollection(order, dim, mfem::BasisType::Positive); - mfem::FiniteElementSpace* fes = new mfem::FiniteElementSpace(mesh, fec); - mfem::GridFunction* volFrac = new mfem::GridFunction(fes); - volFrac->MakeOwner(fec); - volFrac->HostReadWrite(); - dc->RegisterField(name, volFrac); - - (*volFrac) = (*inout); -} - -#endif // defined(AXOM_USE_MFEM) - -} // end namespace shaping -} // end namespace quest -} // end namespace axom +// Common shaping helpers are header-only. Backend-specific implementations live in +// shaping_helpers_mfem.cpp and shaping_helpers_blueprint.cpp. diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 629712212c..2dfef97d12 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -7,7 +7,7 @@ /** * \file shaping_helpers.hpp * - * \brief Free-standing helper functions in support of shaping query + * \brief Common shaping helper utilities and backend-specific helper facade */ #ifndef AXOM_QUEST_SHAPING_HELPERS__HPP_ @@ -15,21 +15,14 @@ #include "axom/config.hpp" #include "axom/core.hpp" +#include "axom/core/numerics/quadrature.hpp" #include "axom/primal.hpp" #include "axom/sidre.hpp" +#include "axom/slic.hpp" -#if defined(AXOM_USE_MFEM) - #include "mfem.hpp" - #include "mfem/linalg/dtensor.hpp" -#endif -#if defined(AXOM_USE_CONDUIT) - #include "conduit_node.hpp" - #include "axom/bump/utilities/conduit_memory.hpp" - #include "axom/bump/views/dispatch_coordset.hpp" -#endif - -#include -#include +#include +#include +#include namespace axom { @@ -39,11 +32,11 @@ class function; /** * \brief Basic implementation of a host/device compatible analogue to std::function - * + * * \tparam R The return type of the callable object * \tparam Args The parameter types of the callable object * \tparam MaxSize The maximum size of the callable (including its captured variables) - * + * * \note We will extend this and move it to the core component */ template @@ -55,24 +48,12 @@ class function public: AXOM_HOST_DEVICE function() : invoke(nullptr) { } - /** - * \brief Constructs a function object from a callable object - * - * \tparam Callable The type of the callable object - * \param callable The callable object to store and invoke - * - * This constructor stores the callable object in the internal storage - * and sets up the invoke function pointer to call the stored object. - * The callable object must be trivially copyable and its size must not - * exceed the maximum storage size. - */ template AXOM_HOST_DEVICE function(Callable callable) { static_assert(sizeof(Callable) <= MaxSize, "Callable object too large!"); static_assert(std::is_trivially_copyable::value, "Callable must be trivially copyable!"); - //SLIC_WARNING("sizeof(Callable): " << sizeof(Callable)); invoke = [](const void* storage, Args... args) -> R { return (*reinterpret_cast(storage))(std::forward(args)...); @@ -80,15 +61,6 @@ class function new(&storage) Callable(std::move(callable)); } - /** - * \brief invoke the stored callable object - * - * \param args The arguments to be forwarded to the callable object - * - * \return The result of invoking the callable object with the provided arguments. - * If the callable object is not set (i.e., `invoke` is null), a default-constructed - * value of type R is returned. - */ AXOM_HOST_DEVICE R operator()(Args... args) const { if(!invoke) @@ -98,11 +70,6 @@ class function return invoke(&storage, std::forward(args)...); } - /** - * \brief Explicit conversion operator to check the validity of the object - * - * \return True if `invoke` is not null, false otherwise - */ AXOM_HOST_DEVICE explicit operator bool() const { return invoke != nullptr; } private: @@ -117,6 +84,7 @@ auto make_host_device_function(Lambda&& lambda) using Signature = decltype(&Lambda::operator()); return function(std::forward(lambda)); } + namespace quest { @@ -147,8 +115,6 @@ using seq_exec = axom::SEQ_EXEC; namespace shaping { -/// Alias to function pointer that projects a \a FromDim dimensional input point to -/// a \a ToDim dimensional query point when sampling the InOut field template using PointProjector = axom::function(const primal::Point&)>; @@ -159,688 +125,26 @@ enum class VolFracSampling : int SAMPLE_AT_QPTS }; -#if defined(AXOM_USE_MFEM) - -/*! - * \brief Converts an Axom quadrature family to the corresponding MFEM - * `Quadrature1D` value. - * - * \note All `axom::numerics::QuadratureType` enumerators currently map 1:1 to - * MFEM names, even when Axom core numerics does not yet implement the - * corresponding rule family. - */ -int to_mfem_quadrature_type(axom::numerics::QuadratureType quadratureType); - -using QFunctionCollection = mfem::NamedFieldsMap; -using DenseTensorCollection = mfem::NamedFieldsMap; -using MFEMArrayCollection = mfem::NamedFieldsMap>; - -/*! - * \brief Contains the mesh and state used for shaping. - */ -struct MFEMState -{ - virtual ~MFEMState() = default; - - int meshDimension() const - { - return m_dc->GetMesh()->Dimension(); - } - - // For mesh represented as MFEMSidreDataCollection - sidre::MFEMSidreDataCollection* m_dc {nullptr}; -}; - -/*! - * \brief An MFEMState subclass that contains additional data for sampling. - */ -struct SamplingMFEMState : public MFEMState +template +void checkSampleResolution(const MeshState& meshState, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType) { - ~SamplingMFEMState() override - { - m_inoutShapeQFuncs.DeleteData(true); - m_inoutShapeQFuncs.clear(); - - m_inoutMaterialQFuncs.DeleteData(true); - m_inoutMaterialQFuncs.clear(); - - m_inoutTensors.DeleteData(true); - m_inoutTensors.clear(); - - m_inoutArrays.DeleteData(true); - m_inoutArrays.clear(); - } - - mfem::QuadratureFunction* getShapeFunction(const std::string& name) - { - return m_inoutShapeQFuncs.Get(name); - } - - const mfem::QuadratureFunction* getShapeFunction(const std::string& name) const - { - return m_inoutShapeQFuncs.Get(name); - } - - void deleteShapeFunction(const std::string& AXOM_UNUSED_PARAM(name)) - { - // TODO: remove the function from m_inoutShapeQFuncs if it exists. - } - - mfem::QuadratureFunction* getMaterialFunction(const std::string& name) - { - return m_inoutMaterialQFuncs.Get(name); - } - - const mfem::QuadratureFunction* getMaterialFunction(const std::string& name) const - { - return m_inoutMaterialQFuncs.Get(name); - } - - mfem::QuadratureFunction* createMaterialFunction(const std::string& name) - { - auto* positions = m_inoutShapeQFuncs.Get("positions"); - SLIC_ERROR_IF(positions == nullptr, - std::string("Cannot create material function '") + name + - "' without positions."); - - auto* qfunc = new mfem::QuadratureFunction(positions->GetSpace(), 1); - qfunc->HostWrite(); - *qfunc = 0.; - m_inoutMaterialQFuncs.Register(name, qfunc, true); - return qfunc; - } - - QFunctionCollection m_inoutShapeQFuncs; - QFunctionCollection m_inoutMaterialQFuncs; - DenseTensorCollection m_inoutTensors; - MFEMArrayCollection m_inoutArrays; -}; - -/** - * \brief Prints the registered sampling-related field names for an MFEM-backed - * sampling state. - */ -void printRegisteredFieldNames(const SamplingMFEMState& mfemState, - const std::set& knownMaterials, - VolFracSampling vfSampling, - const std::string& initialMessage); - -/** - * \brief Utility function to either return a grid function from the DataCollection \a dc, - * or to allocate the grud function through the dc, ensuring the memory doesn't leak - * - * \return A pointer to the (allocated) grid function. nullptr if it cannot be allocated - */ -mfem::GridFunction* getOrAllocateL2GridFunction(mfem::DataCollection* dc, - const std::string& gf_name, - int order, - int dim, - const int basis); - -/** - * Utility function to zero out inout quadrature points for a material replaced by a shape - * - * Each location in space can only be covered by one material. - * When \a shouldReplace is true, we clear all values in \a materialQFunc - * that are set in \a shapeQFunc. When it is false, we do the opposite. - * - * \param shapeQFunc The inout quadrature function for the shape samples - * \param materialQFunc The inout quadrature function for the material samples - * \param shapeReplacesMaterial Flag for whether the shape replaces the material - * or whether the material remains and we should zero out the shape sample (when false) - */ -void replaceMaterial(mfem::QuadratureFunction* shapeQFunc, - mfem::QuadratureFunction* materialQFunc, - bool shouldReplace); - -/** - * \brief Utility function to copy inout quadrature point values from \a shapeQFunc to \a materialQFunc - * - * \param shapeQFunc The inout samples for the current shape - * \param materialQFunc The inout samples for the material we're writing into - * \param reuseExisting When a value is not set in \a shapeQFunc, should we retain existing values - * from \a materialQFunc or overwrite them based on \a shapeQFunc. The default is to retain values - */ -void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, - mfem::QuadratureFunction* materialQFunc, - bool reuseExisting = true); - -mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfunc); - -/** - * \brief Generates a "position" quadrature function corresponding to the mesh positions and - * store it in \a inoutQFuncs. - * - * \param mesh The mesh - * \param inoutQFuncs A collection of quadrature functions where the new "position" function will be added. - * \param sampleResolution The sample resolution in each logical dimension. The size of the view should be - * 1 for Invalid \a quadratureType and be equal to the mesh dimension for other - * \a quadratureType values. - * \param quadratureType An int corresponding to mfem::Quadrature1D enum values. If - * Invalid is used then the default quadrature is constructed. - * Otherwise, custom quadrature is constructed using the supplied - * quadratureType -- the same type per dimension but the sampling - * can vary. - */ -void generatePositionsQFunction(mfem::Mesh* mesh, - QFunctionCollection& inoutQFuncs, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType); - -/** - * \brief Generates a "position" quadrature function for the supplied MFEM state. - */ -void generateSamplingPositions(SamplingMFEMState& mfemState, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType); - -void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, - const std::string& matField, - int volfracOrder, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType); -/*! - * \brief Identity transform for volume fractions from inout samples - * - * Copies \a inout samples from the quadrature function directly into volume fraction DOFs. - * \param dc The data collection to which we will add the volume fractions - * \param inout The inout samples - * \param name The name of the generated volume fraction function - * \note Assumes that the inout samples are co-located with the grid function DOFs. - */ -void computeVolumeFractionsIdentity(mfem::DataCollection* dc, - mfem::QuadratureFunction* inout, - const std::string& name); - -/*! - * \brief Determines whether the quadrature is anisotropic. - * - * \param The MFEM mesh used being sampled onto. - * \param sampleResolution The sample resolution for each dimension. If \a quadratureType - * is Invalid, there must be one value, which will be used for each - * dimension. For other \a quadratureType values, there must be - * one value per mesh dimension. - * \param quadratureType A quadrature type. - * - * \return True if the specified quadrature is anisotropic, false otherwise. - */ -bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType); - -/*! - * \brief Samples the inout field over the indexed geometry, possibly using a - * callback function to project the input points (from the computational mesh) - * to query points on the spatial index - * - * \tparam FromDim The dimension of points from the input mesh - * \tparam ToDim The dimension of points on the indexed shape - * \tparam InsideFunc A function that takes a point and returns a bool indicating whether the - * point is inside or outside of relevant shapes. - * - * \param [in] shapeName The name of the shape used in making data array names. - * \param [in] mfemState The MFEM state containing the mesh and associated query points - * \param [inout] inoutQFuncs A collection of quadrature functions for the shape and material - * inout samples - * \param [in] sampleRes The sampling resolution in each logical direction. For Invalid quadratureType, - * there must be 1 value, which will be used for each quadrature dimension. For - * other quadrature types, there must be 1 value per mesh dimension. - * For custom quadrature families, these values specify the per-direction - * sample counts directly, which in turn determine the quadrature rule used - * in each logical direction. - * \param [in] quadratureType The quadrature type to use to construct the sample point locations. - * \param [in] checkInside The function that determines whether a point is inside. - * \param [in] projector A callback function to apply to points from the input mesh - * before querying them on the spatial index - * - * \note A projector callback must be supplied when \a FromDim is not equal - * to \a ToDim. - */ -template -void sampleInOutField(const std::string shapeName, - shaping::SamplingMFEMState& mfemState, - InsideFunc&& checkInside, - PointProjector projector = {}) -{ - using FromPoint = primal::Point; - using ToPoint = primal::Point; - AXOM_ANNOTATE_SCOPE("sampleInOutField"); - - SLIC_ERROR_IF(FromDim != ToDim && !projector, - "A projector callback function is required when FromDim != ToDim"); - - auto* mesh = mfemState.m_dc->GetMesh(); - SLIC_ASSERT(mesh != nullptr); - const int NE = mesh->GetNE(); - const int dim = mesh->Dimension(); - - auto& inoutQFuncs = mfemState.m_inoutShapeQFuncs; - SLIC_ASSERT(inoutQFuncs.Has("positions")); - - // Access the positions QFunc and associated QuadratureSpace - mfem::QuadratureFunction* pos_coef = inoutQFuncs.Get("positions"); - auto* sp = pos_coef->GetSpace(); - const int nq = sp->GetIntRule(0).GetNPoints(); - const int numQueryPoints = sp->GetSize(); - SLIC_ASSERT(numQueryPoints == NE * nq); - - const auto pos = mfem::Reshape(pos_coef->HostRead(), dim, nq, NE); - - // Sample the in/out field at each point - // store in QField which we register with the QFunc collection - const std::string inoutName = axom::fmt::format("inout_{}", shapeName); - const int vdim = 1; - auto* inout = new mfem::QuadratureFunction(sp, vdim); - inoutQFuncs.Register(inoutName, inout, true); - auto inout_vals = mfem::Reshape(inout->HostWrite(), nq, NE); - - axom::utilities::Timer timer(true); - if(projector) - { - for(int i = 0; i < NE; ++i) - { - for(int p = 0; p < nq; ++p) - { - const ToPoint pt = projector(FromPoint(&pos(0, p, i), dim)); - inout_vals(p, i) = checkInside(pt) ? 1. : 0.; - } - } - } - else - { - for(int i = 0; i < NE; ++i) - { - for(int p = 0; p < nq; ++p) - { - const ToPoint pt(&pos(0, p, i), dim); - inout_vals(p, i) = checkInside(pt) ? 1. : 0.; - } - } - } - timer.stop(); - - // print stats for rank 0 - SLIC_INFO_ROOT(axom::fmt::format( - axom::utilities::locale(), - "\t Sampling inout field '{}' took {:.3Lf} seconds (@ {:L} queries per second)", - inoutName, - timer.elapsed(), - static_cast(numQueryPoints / timer.elapsed()))); + SLIC_ERROR_IF(quadratureType != axom::numerics::QuadratureType::Invalid && + sampleResolution.size() != meshState.meshDimension(), + "Inconsistent mesh dimension and sample resolutions."); } -/*! - * \brief Samples the inout field over the indexed geometry, possibly using a - * callback function to project the input points (from the computational mesh) - * to query points on the spatial index - * - * \tparam FromDim The dimension of points from the input mesh - * \tparam ToDim The dimension of points on the indexed shape - * \tparam InsideFunc A function that takes a point and returns a bool indicating whether the - * point is inside or outside of relevant shapes. - * - * \param [in] shapeName The name of the shape used in making data array names. - * \param [in] dc The data collection containing the mesh and associated query points - * \param [in] outputOrder The order of the output inout field - * \param [in] checkInside The function that determines whether a point is inside. - * \param [in] projector A callback function to apply to points from the input mesh - * before querying them on the spatial index - * - * \note A projector callback must be supplied when \a FromDim is not equal - * to \a ToDim. - */ -template -void computeVolumeFractionsBaseline(const std::string& shapeName, - shaping::SamplingMFEMState& mfemState, - int outputOrder, - InsideFunc&& checkInside, - PointProjector projector = {}) -{ - using FromPoint = primal::Point; - using ToPoint = primal::Point; - AXOM_ANNOTATE_SCOPE("computeVolumeFractionsBaseline"); - - // Step 1 -- generate a QField w/ the spatial coordinates - mfem::DataCollection* dc = mfemState.m_dc; - mfem::Mesh* mesh = dc->GetMesh(); - const int NE = mesh->GetNE(); - const int dim = mesh->Dimension(); - - if(NE < 1) - { - SLIC_WARNING("Mesh has no elements!"); - return; - } - - const auto volFracName = axom::fmt::format("vol_frac_{}", shapeName); - mfem::GridFunction* volFrac = - shaping::getOrAllocateL2GridFunction(dc, volFracName, outputOrder, dim, mfem::BasisType::Positive); - const mfem::FiniteElementSpace* fes = volFrac->FESpace(); - - auto* fe = fes->GetFE(0); - auto& ir = fe->GetNodes(); - - // Assume all elements have the same integration rule - const int nq = ir.GetNPoints(); - const auto* geomFactors = mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); - - mfem::DenseTensor pos_coef(dim, nq, NE); - - // Rearrange positions into quadrature function - { - for(int i = 0; i < NE; ++i) - { - for(int j = 0; j < dim; ++j) - { - for(int k = 0; k < nq; ++k) - { - pos_coef(j, k, i) = geomFactors->X((i * nq * dim) + (j * nq) + k); - } - } - } - } - - // Step 2 -- sample the in/out field at each point -- store directly in volFrac grid function - mfem::Vector res(nq); - mfem::Array dofs; - if(projector) - { - for(int i = 0; i < NE; ++i) - { - const mfem::DenseMatrix& m = pos_coef(i); - for(int p = 0; p < nq; ++p) - { - const ToPoint pt = projector(FromPoint(m.GetColumn(p), dim)); - res(p) = checkInside(pt) ? 1. : 0.; - } - - fes->GetElementDofs(i, dofs); - volFrac->SetSubVector(dofs, res); - } - } - else - { - for(int i = 0; i < NE; ++i) - { - const mfem::DenseMatrix& m = pos_coef(i); - for(int p = 0; p < nq; ++p) - { - const ToPoint pt(m.GetColumn(p), dim); - res(p) = checkInside(pt) ? 1. : 0.; - } +} // end namespace shaping +} // end namespace quest +} // end namespace axom - fes->GetElementDofs(i, dofs); - volFrac->SetSubVector(dofs, res); - } - } -} +#if defined(AXOM_USE_MFEM) + #include "shaping_helpers_mfem.hpp" #endif #if defined(AXOM_USE_CONDUIT) -//------------------------------------------------------------------------------ -/** - * \brief Returns the element shape for a supported Blueprint topology node. - * - * Structured topologies may omit `elements/shape`, in which case the shape is - * inferred from `elements/dims`. - */ -std::string getBlueprintCellShape(const conduit::Node& topoNode); - -/*! - * \brief A Blueprint-based state class used for shaping. - */ -struct BlueprintState -{ - virtual ~BlueprintState() = default; - - //! @brief Version of the mesh for computations. - axom::sidre::Group* m_group_ptr {nullptr}; - int m_allocator_id {axom::getDefaultAllocatorID()}; - std::string m_topology_name; - //! @brief Mesh in an external Node, when provided as a Node. - conduit::Node* m_external_node_ptr {nullptr}; - //! @brief Internal Node representation used for blueprint operations. - conduit::Node m_internal_node; - - int meshDimension() const - { - const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); - - if(shapeType == "quad") - { - return 2; - } - if(shapeType == "hex") - { - return 3; - } - - SLIC_ERROR(axom::fmt::format("Unsupported Blueprint cell shape '{}'.", shapeType)); - return -1; - } - - const conduit::Node& getBlueprintTopologyNode() const - { - return m_internal_node.fetch_existing("topologies").fetch_existing(m_topology_name); - } - - conduit::Node* getShapeFunction(const std::string& name) - { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; - } - - const conduit::Node* getShapeFunction(const std::string& name) const - { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; - } - - void deleteShapeFunction(const std::string& name) - { - // This method lets us delete the shape functions as we go - if(m_internal_node.has_path("fields")) - { - conduit::Node &n_fields = m_internal_node["fields"]; - if(n_fields.has_path(name)) - { - n_fields.remove(name); - } - } - } - - conduit::Node* getMaterialFunction(const std::string& name) - { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; - } - - const conduit::Node* getMaterialFunction(const std::string& name) const - { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; - } - - conduit::Node* createMaterialFunction(const std::string& name) - { - constexpr const char* quadratureTopologyName = "quadrature_points"; - SLIC_ERROR_IF(!m_internal_node.has_path("coordsets/quadrature_points/values"), - std::string("Cannot create material function '") + name + - "' without quadrature points."); - - conduit::Node& fieldNode = m_internal_node["fields/" + name]; - fieldNode.reset(); - fieldNode["association"] = "element"; - fieldNode["topology"] = quadratureTopologyName; - - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); - conduit::Node& valuesNode = fieldNode["values"]; - valuesNode.set_allocator(conduitAllocatorId); - - const conduit::Node& values = - m_internal_node["coordsets/quadrature_points"].fetch_existing("values"); - const auto numValues = values.child(0).dtype().number_of_elements(); - valuesNode.set(conduit::DataType::float64(numValues)); - - auto fieldValues = axom::bump::utilities::make_array_view(valuesNode); - for(axom::IndexType i = 0; i < fieldValues.size(); ++i) - { - fieldValues[i] = 0.; - } - - return &fieldNode; - } -}; - -/** - * \brief Prints the registered sampling-related field names for a Blueprint-backed - * sampling state. - */ -void printRegisteredFieldNames(const BlueprintState& bpState, - const std::set& knownMaterials, - VolFracSampling vfSampling, - const std::string& initialMessage); - -void replaceMaterial(conduit::Node* shapeNode, conduit::Node* materialNode, bool shouldReplace); - -void copyShapeIntoMaterial(const conduit::Node* shapeNode, - conduit::Node* materialNode, - bool reuseExisting = true); - -conduit::Node* cloneInOutFunction(const conduit::Node* node); - -// NOTE: exposed so we can call it from testing functions. -void generateQuadraturePointMesh(conduit::Node& bpMeshNode, - const std::string& topologyName, - int allocatorID, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType); -/*! - * \brief Generates a derived Blueprint quadrature point mesh for the supplied - * Blueprint state. - */ -void generateSamplingPositions(BlueprintState& bpState, - axom::ArrayView sampleResolution, - axom::numerics::QuadratureType quadratureType); - -void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField); - -/*! - * \brief Samples the inout field over the indexed geometry, possibly using a - * callback function to project the input points (from the computational mesh) - * to query points on the spatial index - * - * \tparam FromDim The dimension of points from the input mesh - * \tparam ToDim The dimension of points on the indexed shape - * \tparam InsideFunc A function that takes a point and returns a bool indicating whether the - * point is inside or outside of relevant shapes. - * - * \param [in] shapeName The name of the shape used in making data array names. - * \param [in] bpState The Blueprint state containing the mesh and associated query points - * \param [in] sampleRes The sampling resolution in each logical direction. - * For custom quadrature families, these values specify the per-direction - * sample counts directly, which in turn determine the quadrature rule used - * in each logical direction. - * \param [in] quadratureType The quadrature type to use to construct the sample point locations. - * \param [in] checkInside The function that determines whether a point is inside. - * \param [in] projector A callback function to apply to points from the input mesh - * before querying them on the spatial index - * - * \note A projector callback must be supplied when \a FromDim is not equal - * to \a ToDim. - */ -template -void sampleInOutField(const std::string& shapeName, - shaping::BlueprintState& bpState, - InsideFunc&& checkInside, - PointProjector projector = {}) -{ - using FromPoint = primal::Point; - using ToPoint = primal::Point; - AXOM_ANNOTATE_SCOPE("sampleInOutField"); - - SLIC_ERROR_IF(FromDim != ToDim && !projector, - "A projector callback function is required when FromDim != ToDim"); - - constexpr const char* quadratureCoordsetName = "quadrature_points"; - constexpr const char* quadratureTopologyName = "quadrature_points"; - const std::string inoutName = axom::fmt::format("inout_{}", shapeName); - - conduit::Node& bpMeshNode = bpState.m_internal_node; - SLIC_ERROR_IF(!bpMeshNode.has_path("coordsets/quadrature_points"), - "Missing Blueprint quadrature coordset. Generate sampling positions first."); - SLIC_ERROR_IF(!bpMeshNode.has_path("topologies/quadrature_points"), - "Missing Blueprint quadrature topology. Generate sampling positions first."); - - conduit::Node& inoutNode = bpMeshNode["fields/" + inoutName]; - inoutNode.reset(); - inoutNode["association"] = "element"; - inoutNode["topology"] = quadratureTopologyName; - - namespace utils = axom::bump::utilities; - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); - conduit::Node& valuesNode = inoutNode["values"]; - valuesNode.set_allocator(conduitAllocatorId); - - axom::utilities::Timer timer(true); - axom::IndexType numQueryPoints = 0; - axom::bump::views::dispatch_explicit_coordset( - bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)], [&](auto coordsetView) { - using CoordsetView = typename std::decay::type; - - SLIC_ERROR_IF(CoordsetView::dimension() != FromDim, - axom::fmt::format("Expected {}D quadrature point coordset, got {}D.", - FromDim, - CoordsetView::dimension())); - - numQueryPoints = coordsetView.size(); - valuesNode.set(conduit::DataType::float64(numQueryPoints)); - auto inoutValues = utils::make_array_view(valuesNode); - - for(axom::IndexType i = 0; i < numQueryPoints; ++i) - { - // Make a FromPoint from the coordsetView. The coordsetView might have - // float or double, depending on the Blueprint data. - FromPoint fromPt; - const auto coordsetPoint = coordsetView[i]; - for(int d = 0; d < FromDim; ++d) - { - fromPt[d] = coordsetPoint[d]; - } - - // Sample at the query point. - const ToPoint queryPt = projector ? projector(fromPt) : ToPoint(fromPt.data()); - inoutValues[i] = checkInside(queryPt) ? 1. : 0.; - } - }); - timer.stop(); - - SLIC_INFO_ROOT(axom::fmt::format( - axom::utilities::locale(), - "\t Sampling inout field '{}' took {:.3Lf} seconds (@ {:L} queries per second)", - inoutName, - timer.elapsed(), - static_cast(numQueryPoints / timer.elapsed()))); -} + #include "shaping_helpers_blueprint.hpp" #endif -/** - * Implements flux-corrected transport (FCT) to correct the solution obtained - * when converting from inout samples (ones and zeros) to a grid function - * on the degrees of freedom such that the volume fractions are doubles - * between 0 and 1 ( \a y_min and \a y_max ) - */ -void FCT_correct(const double* M, - const int s, - const double* m, - const double y_min, // 0 - const double y_max, // 1 - double* xy, - double* fct_mat); // scratch buffer - -} // end namespace shaping -} // end namespace quest -} // end namespace axom - #endif // AXOM_QUEST_SHAPING_HELPERS__HPP_ diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp new file mode 100644 index 0000000000..32aff5d607 --- /dev/null +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -0,0 +1,497 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "shaping_helpers_blueprint.hpp" +#include "GenerateQuadratureMesh.hpp" + +#if defined(AXOM_USE_CONDUIT) + +#include "axom/bump/views/dispatch_topology.hpp" +#include "axom/bump/views/dispatch_unstructured_topology.hpp" + +#include "conduit_blueprint_mesh.hpp" + +#include + +namespace axom +{ +namespace quest +{ +namespace shaping +{ + +namespace +{ + +constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; +constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; +constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; +constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; +constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = + "quadraturePhysicalWeights"; + +numerics::QuadratureRule getBlueprintQuadratureRule( + axom::numerics::QuadratureType quadratureType, + int npts, + int allocatorID) +{ + SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); + SLIC_ERROR_IF(!axom::numerics::is_supported_quadrature_type(quadratureType), + axom::fmt::format( + "Quadrature type {} is not yet supported for Blueprint quadrature meshes.", + static_cast(quadratureType))); + + return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); +} + +std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) +{ + const std::string topoType = topoNode.fetch_existing("type").as_string(); + if(topoNode.has_path("elements/shape")) + { + return topoNode.fetch_existing("elements/shape").as_string(); + } + + if(topoType == "structured") + { + const conduit::Node& dimsNode = topoNode.fetch_existing("elements/dims"); + if(dimsNode.has_child("k")) + { + return "hex"; + } + if(dimsNode.has_child("j")) + { + return "quad"; + } + if(dimsNode.has_child("i")) + { + return "line"; + } + + SLIC_ERROR("Structured Blueprint topology is missing recognizable element dims."); + } + + SLIC_ERROR( + axom::fmt::format("Blueprint topology type '{}' is missing 'elements/shape'.", topoType)); + return ""; +} + +template +void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, + const conduit::Node& coordsetNode, + const CoordsetView& coordsetView, + int allocatorID, + const numerics::QuadratureRule& ruleX, + const numerics::QuadratureRule& ruleY, + const numerics::QuadratureRule& ruleZ, + conduit::Node& meshNode) +{ + namespace views = axom::bump::views; + constexpr int SupportedShapes = + views::select_shapes(views::Quad_ShapeID, views::Hex_ShapeID); + + views::dispatch_topology( + topoNode, + [&](const auto&, auto topoView) { + GenerateQuadratureMesh generator( + topoView, + coordsetView); + generator.setAllocatorID(allocatorID); + generator.execute(topoNode, + coordsetNode, + QUADRATURE_TOPOLOGY_NAME, + QUADRATURE_COORDSET_NAME, + ORIGINAL_ELEMENTS_FIELD_NAME, + QUADRATURE_WEIGHTS_FIELD_NAME, + QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME, + ruleX, + ruleY, + ruleZ, + meshNode); + }); +} + +} // namespace + +std::string getBlueprintCellShape(const conduit::Node& topoNode) +{ + return getBlueprintCellShapeImpl(topoNode); +} + +void printRegisteredFieldNames(const BlueprintState& bpState, + const std::set& knownMaterials, + VolFracSampling AXOM_UNUSED_PARAM(vfSampling), + const std::string& initialMessage) +{ + auto extractChildren = [](const conduit::Node& node) { + std::vector names; + if(node.dtype().is_object()) + { + names.reserve(node.number_of_children()); + for(conduit::index_t i = 0; i < node.number_of_children(); ++i) + { + names.push_back(node.child(i).name()); + } + } + return names; + }; + + auto extractMatchingFields = [&](const std::string& prefix) { + std::vector names; + if(bpState.m_internal_node.has_path("fields")) + { + const conduit::Node& fieldsNode = + bpState.m_internal_node.fetch_existing("fields"); + for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) + { + const std::string name = fieldsNode.child(i).name(); + if(axom::utilities::string::startsWith(name, prefix)) + { + names.push_back(name); + } + } + } + return names; + }; + + auto extractOtherFields = [&]() { + std::vector names; + if(bpState.m_internal_node.has_path("fields")) + { + const conduit::Node& fieldsNode = + bpState.m_internal_node.fetch_existing("fields"); + for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) + { + const std::string name = fieldsNode.child(i).name(); + if(!axom::utilities::string::startsWith(name, "inout_") && + !axom::utilities::string::startsWith(name, "mat_inout_") && + !axom::utilities::string::startsWith(name, "vol_frac_")) + { + names.push_back(name); + } + } + } + return names; + }; + + const std::vector topologyNames = + bpState.m_internal_node.has_path("topologies") + ? extractChildren(bpState.m_internal_node.fetch_existing("topologies")) + : std::vector {}; + const std::vector coordsetNames = + bpState.m_internal_node.has_path("coordsets") + ? extractChildren(bpState.m_internal_node.fetch_existing("coordsets")) + : std::vector {}; + const std::vector fieldNames = + bpState.m_internal_node.has_path("fields") + ? extractChildren(bpState.m_internal_node.fetch_existing("fields")) + : std::vector {}; + + axom::fmt::memory_buffer out; + axom::fmt::format_to(std::back_inserter(out), + "List of registered fields in the SamplingShaper {}" + "\n\t* Blueprint topologies: {}" + "\n\t* Blueprint coordsets: {}" + "\n\t* Blueprint fields: {}" + "\n\t* Known materials: {}" + "\n\t* Shape inout fields: {}" + "\n\t* Mat inout fields: {}" + "\n\t* Volume fraction fields: {}" + "\n\t* Other Blueprint fields: {}", + initialMessage, + axom::fmt::join(topologyNames, ", "), + axom::fmt::join(coordsetNames, ", "), + axom::fmt::join(fieldNames, ", "), + axom::fmt::join(knownMaterials, ", "), + axom::fmt::join(extractMatchingFields("inout_"), ", "), + axom::fmt::join(extractMatchingFields("mat_inout_"), ", "), + axom::fmt::join(extractMatchingFields("vol_frac_"), ", "), + axom::fmt::join(extractOtherFields(), ", ")); + + SLIC_INFO_ROOT(axom::fmt::to_string(out)); +} + +void generateQuadraturePointMesh(conduit::Node& bpMeshNode, + const std::string& topologyName, + int allocatorID, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType) +{ + if(bpMeshNode.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) + { + return; + } + + const conduit::Node& topoNode = + bpMeshNode.fetch_existing("topologies").fetch_existing(topologyName); + const std::string topoType = topoNode.fetch_existing("type").as_string(); + SLIC_ERROR_IF(topoType != "unstructured" && topoType != "structured", + axom::fmt::format( + "Unsupported Blueprint topology type '{}' for quadrature mesh generation.", + topoType)); + + const std::string shape = shaping::getBlueprintCellShape(topoNode); + SLIC_ERROR_IF(shape != "quad" && shape != "hex", + axom::fmt::format( + "Unsupported Blueprint element shape '{}' for quadrature mesh generation.", + shape)); + + const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); + const conduit::Node& coordsetNode = + bpMeshNode.fetch_existing("coordsets").fetch_existing(coordsetName); + const std::string coordsetType = coordsetNode.fetch_existing("type").as_string(); + SLIC_ERROR_IF(coordsetType != "explicit", + axom::fmt::format( + "Unsupported Blueprint coordset type '{}' for quadrature mesh generation.", + coordsetType)); + + int selectedAllocatorID = allocatorID; + if(!axom::execution_space::usesAllocId(selectedAllocatorID) && + !axom::execution_space::usesAllocId(selectedAllocatorID) +#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + && !axom::execution_space::usesAllocId(selectedAllocatorID) +#endif +#if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + && !axom::execution_space::usesAllocId(selectedAllocatorID) +#endif + ) + { + selectedAllocatorID = axom::execution_space::allocatorID(); + } + + auto ruleX = getBlueprintQuadratureRule( + quadratureType, + sampleResolution[0], + selectedAllocatorID); + auto ruleY = getBlueprintQuadratureRule( + quadratureType, + sampleResolution[1], + selectedAllocatorID); + auto ruleZ = getBlueprintQuadratureRule( + quadratureType, + sampleResolution[2], + selectedAllocatorID); + + axom::bump::views::dispatch_explicit_coordset(coordsetNode, [&](auto coordsetView) { +#if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + if(axom::execution_space::usesAllocId(selectedAllocatorID)) + { + buildBlueprintQuadratureMesh(topoNode, + coordsetNode, + coordsetView, + selectedAllocatorID, + ruleX, + ruleY, + ruleZ, + bpMeshNode); + return; + } +#endif +#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + if(axom::execution_space::usesAllocId(selectedAllocatorID)) + { + buildBlueprintQuadratureMesh(topoNode, + coordsetNode, + coordsetView, + selectedAllocatorID, + ruleX, + ruleY, + ruleZ, + bpMeshNode); + return; + } +#endif + if(axom::execution_space::usesAllocId(selectedAllocatorID)) + { + buildBlueprintQuadratureMesh(topoNode, + coordsetNode, + coordsetView, + selectedAllocatorID, + ruleX, + ruleY, + ruleZ, + bpMeshNode); + return; + } + + buildBlueprintQuadratureMesh(topoNode, + coordsetNode, + coordsetView, + selectedAllocatorID, + ruleX, + ruleY, + ruleZ, + bpMeshNode); + }); +} + +void generateSamplingPositions(BlueprintState& bpState, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType) +{ + checkSampleResolution(bpState, sampleResolution, quadratureType); + + if(bpState.m_internal_node.has_path( + axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) + { + return; + } + + generateQuadraturePointMesh(bpState.m_internal_node, + bpState.m_topology_name, + bpState.m_allocator_id, + sampleResolution, + quadratureType); +} + +void computeVolumeFractionsForMaterial(BlueprintState& bpState, + const std::string& matField) +{ + AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); + + SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); + + conduit::Node* inout = bpState.getMaterialFunction(matField); + SLIC_ERROR_IF(inout == nullptr, + axom::fmt::format( + "Missing Blueprint material field '{}' for volume fraction projection.", + matField)); + + conduit::Node& bpMeshNode = bpState.m_internal_node; + SLIC_ERROR_IF(!bpMeshNode.has_path("fields/originalElements/values"), + "Missing Blueprint originalElements field for volume fraction projection."); + SLIC_ERROR_IF( + !bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") && + !bpMeshNode.has_path("fields/quadratureWeights/values"), + "Missing Blueprint quadrature weight field for volume fraction projection."); + + const conduit::Node& topoNode = + bpMeshNode.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); + + const axom::IndexType numZones = conduit::blueprint::mesh::topology::length(topoNode); + + namespace utils = axom::bump::utilities; + const auto originalElements = + utils::make_array_view(bpMeshNode["fields/originalElements/values"]); + const conduit::Node& quadratureWeightsNode = + bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") + ? bpMeshNode["fields/quadraturePhysicalWeights/values"] + : bpMeshNode["fields/quadratureWeights/values"]; + const auto quadratureWeights = utils::make_array_view(quadratureWeightsNode); + const auto inoutValues = utils::make_array_view(inout->fetch_existing("values")); + + SLIC_ASSERT(originalElements.size() == quadratureWeights.size()); + SLIC_ASSERT(originalElements.size() == inoutValues.size()); + + const std::string vfName = axom::fmt::format("vol_frac_{}", matField.substr(10)); + conduit::Node& vfNode = bpMeshNode["fields/" + vfName]; + vfNode.reset(); + vfNode["association"] = "element"; + vfNode["topology"] = bpState.m_topology_name; + + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); + conduit::Node& valuesNode = vfNode["values"]; + valuesNode.set_allocator(conduitAllocatorId); + valuesNode.set(conduit::DataType::float64(numZones)); + auto vfValues = utils::make_array_view(valuesNode); + axom::Array totalWeights(numZones, numZones, bpState.m_allocator_id); + auto totalWeightsView = totalWeights.view(); + + for(axom::IndexType zoneIdx = 0; zoneIdx < vfValues.size(); ++zoneIdx) + { + vfValues[zoneIdx] = 0.; + totalWeightsView[zoneIdx] = 0.; + } + + for(axom::IndexType pointIdx = 0; pointIdx < inoutValues.size(); ++pointIdx) + { + const conduit::index_t zoneIdx = originalElements[pointIdx]; + SLIC_ASSERT(zoneIdx >= 0); + SLIC_ASSERT(zoneIdx < vfValues.size()); + vfValues[zoneIdx] += inoutValues[pointIdx] * quadratureWeights[pointIdx]; + totalWeightsView[zoneIdx] += quadratureWeights[pointIdx]; + } + + for(axom::IndexType zoneIdx = 0; zoneIdx < vfValues.size(); ++zoneIdx) + { + SLIC_ERROR_IF(axom::utilities::isNearlyEqual(totalWeightsView[zoneIdx], 0.0), + axom::fmt::format( + "Blueprint quadrature weights sum to zero in zone {} during volume fraction projection.", + zoneIdx)); + vfValues[zoneIdx] /= totalWeightsView[zoneIdx]; + } +} + +void replaceMaterial(conduit::Node* shapeNode, + conduit::Node* materialNode, + bool shapeReplacesMaterial) +{ + SLIC_ASSERT(shapeNode != nullptr); + SLIC_ASSERT(materialNode != nullptr); + + namespace utils = axom::bump::utilities; + auto shapeValues = utils::make_array_view(shapeNode->fetch_existing("values")); + auto materialValues = + utils::make_array_view(materialNode->fetch_existing("values")); + + SLIC_ASSERT(shapeValues.size() == materialValues.size()); + + for(axom::IndexType i = 0; i < materialValues.size(); ++i) + { + if(shapeReplacesMaterial) + { + materialValues[i] = shapeValues[i] > 0. ? 0. : materialValues[i]; + } + else + { + shapeValues[i] = materialValues[i] > 0. ? 0. : shapeValues[i]; + } + } +} + +void copyShapeIntoMaterial(const conduit::Node* shapeNode, + conduit::Node* materialNode, + bool reuseExisting) +{ + SLIC_ASSERT(shapeNode != nullptr); + SLIC_ASSERT(materialNode != nullptr); + + namespace utils = axom::bump::utilities; + const auto shapeValues = + utils::make_array_view(shapeNode->fetch_existing("values")); + auto materialValues = + utils::make_array_view(materialNode->fetch_existing("values")); + + SLIC_ASSERT(shapeValues.size() == materialValues.size()); + + if(reuseExisting) + { + for(axom::IndexType i = 0; i < materialValues.size(); ++i) + { + materialValues[i] = shapeValues[i] > 0. ? 1. : materialValues[i]; + } + } + else + { + for(axom::IndexType i = 0; i < materialValues.size(); ++i) + { + materialValues[i] = shapeValues[i]; + } + } +} + +conduit::Node* cloneInOutFunction(const conduit::Node* node) +{ + SLIC_ASSERT(node != nullptr); + return new conduit::Node(*node); +} + +} // end namespace shaping +} // end namespace quest +} // end namespace axom + +#endif // defined(AXOM_USE_CONDUIT) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp new file mode 100644 index 0000000000..40d32b5b48 --- /dev/null +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -0,0 +1,238 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef AXOM_QUEST_SHAPING_HELPERS_BLUEPRINT__HPP_ +#define AXOM_QUEST_SHAPING_HELPERS_BLUEPRINT__HPP_ + +#include "shaping_helpers.hpp" + +#if defined(AXOM_USE_CONDUIT) + +#include "axom/bump/utilities/conduit_memory.hpp" +#include "axom/bump/views/dispatch_coordset.hpp" +#include "axom/fmt.hpp" + +#include "conduit_node.hpp" + +#include +#include +#include + +namespace axom +{ +namespace quest +{ +namespace shaping +{ + +std::string getBlueprintCellShape(const conduit::Node& topoNode); + +struct BlueprintState +{ + virtual ~BlueprintState() = default; + + axom::sidre::Group* m_group_ptr {nullptr}; + int m_allocator_id {axom::getDefaultAllocatorID()}; + std::string m_topology_name; + conduit::Node* m_external_node_ptr {nullptr}; + conduit::Node m_internal_node; + + int meshDimension() const + { + const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); + + if(shapeType == "quad") + { + return 2; + } + if(shapeType == "hex") + { + return 3; + } + + SLIC_ERROR(axom::fmt::format("Unsupported Blueprint cell shape '{}'.", shapeType)); + return -1; + } + + const conduit::Node& getBlueprintTopologyNode() const + { + return m_internal_node.fetch_existing("topologies").fetch_existing(m_topology_name); + } + + conduit::Node* getShapeFunction(const std::string& name) + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + const conduit::Node* getShapeFunction(const std::string& name) const + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + void deleteShapeFunction(const std::string& name) + { + if(m_internal_node.has_path("fields")) + { + conduit::Node& n_fields = m_internal_node["fields"]; + if(n_fields.has_path(name)) + { + n_fields.remove(name); + } + } + } + + conduit::Node* getMaterialFunction(const std::string& name) + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + const conduit::Node* getMaterialFunction(const std::string& name) const + { + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] + : nullptr; + } + + conduit::Node* createMaterialFunction(const std::string& name) + { + constexpr const char* quadratureTopologyName = "quadrature_points"; + SLIC_ERROR_IF(!m_internal_node.has_path("coordsets/quadrature_points/values"), + std::string("Cannot create material function '") + name + + "' without quadrature points."); + + conduit::Node& fieldNode = m_internal_node["fields/" + name]; + fieldNode.reset(); + fieldNode["association"] = "element"; + fieldNode["topology"] = quadratureTopologyName; + + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); + conduit::Node& valuesNode = fieldNode["values"]; + valuesNode.set_allocator(conduitAllocatorId); + + const conduit::Node& values = + m_internal_node["coordsets/quadrature_points"].fetch_existing("values"); + const auto numValues = values.child(0).dtype().number_of_elements(); + valuesNode.set(conduit::DataType::float64(numValues)); + + auto fieldValues = axom::bump::utilities::make_array_view(valuesNode); + for(axom::IndexType i = 0; i < fieldValues.size(); ++i) + { + fieldValues[i] = 0.; + } + + return &fieldNode; + } +}; + +void printRegisteredFieldNames(const BlueprintState& bpState, + const std::set& knownMaterials, + VolFracSampling vfSampling, + const std::string& initialMessage); + +void replaceMaterial(conduit::Node* shapeNode, + conduit::Node* materialNode, + bool shouldReplace); + +void copyShapeIntoMaterial(const conduit::Node* shapeNode, + conduit::Node* materialNode, + bool reuseExisting = true); + +conduit::Node* cloneInOutFunction(const conduit::Node* node); + +void generateQuadraturePointMesh(conduit::Node& bpMeshNode, + const std::string& topologyName, + int allocatorID, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType); + +void generateSamplingPositions(BlueprintState& bpState, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType); + +void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField); + +template +void sampleInOutField(const std::string& shapeName, + shaping::BlueprintState& bpState, + InsideFunc&& checkInside, + PointProjector projector = {}) +{ + using FromPoint = primal::Point; + using ToPoint = primal::Point; + AXOM_ANNOTATE_SCOPE("sampleInOutField"); + + SLIC_ERROR_IF(FromDim != ToDim && !projector, + "A projector callback function is required when FromDim != ToDim"); + + constexpr const char* quadratureCoordsetName = "quadrature_points"; + constexpr const char* quadratureTopologyName = "quadrature_points"; + const std::string inoutName = axom::fmt::format("inout_{}", shapeName); + + conduit::Node& bpMeshNode = bpState.m_internal_node; + SLIC_ERROR_IF(!bpMeshNode.has_path("coordsets/quadrature_points"), + "Missing Blueprint quadrature coordset. Generate sampling positions first."); + SLIC_ERROR_IF(!bpMeshNode.has_path("topologies/quadrature_points"), + "Missing Blueprint quadrature topology. Generate sampling positions first."); + + conduit::Node& inoutNode = bpMeshNode["fields/" + inoutName]; + inoutNode.reset(); + inoutNode["association"] = "element"; + inoutNode["topology"] = quadratureTopologyName; + + namespace utils = axom::bump::utilities; + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); + conduit::Node& valuesNode = inoutNode["values"]; + valuesNode.set_allocator(conduitAllocatorId); + + axom::utilities::Timer timer(true); + axom::IndexType numQueryPoints = 0; + axom::bump::views::dispatch_explicit_coordset( + bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)], [&](auto coordsetView) { + using CoordsetView = typename std::decay::type; + + SLIC_ERROR_IF(CoordsetView::dimension() != FromDim, + axom::fmt::format("Expected {}D quadrature point coordset, got {}D.", + FromDim, + CoordsetView::dimension())); + + numQueryPoints = coordsetView.size(); + valuesNode.set(conduit::DataType::float64(numQueryPoints)); + auto inoutValues = utils::make_array_view(valuesNode); + + for(axom::IndexType i = 0; i < numQueryPoints; ++i) + { + FromPoint fromPt; + const auto coordsetPoint = coordsetView[i]; + for(int d = 0; d < FromDim; ++d) + { + fromPt[d] = coordsetPoint[d]; + } + + const ToPoint queryPt = projector ? projector(fromPt) : ToPoint(fromPt.data()); + inoutValues[i] = checkInside(queryPt) ? 1. : 0.; + } + }); + timer.stop(); + + SLIC_INFO_ROOT(axom::fmt::format( + axom::utilities::locale(), + "\t Sampling inout field '{}' took {:.3Lf} seconds (@ {:L} queries per second)", + inoutName, + timer.elapsed(), + static_cast(numQueryPoints / timer.elapsed()))); +} + +} // end namespace shaping +} // end namespace quest +} // end namespace axom + +#endif // defined(AXOM_USE_CONDUIT) + +#endif // AXOM_QUEST_SHAPING_HELPERS_BLUEPRINT__HPP_ diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp new file mode 100644 index 0000000000..6cae0bdfbd --- /dev/null +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp @@ -0,0 +1,886 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "shaping_helpers_mfem.hpp" + +#if defined(AXOM_USE_MFEM) + +#include +#include + +namespace axom +{ +namespace quest +{ +namespace shaping +{ + +namespace +{ + +class OwnedQuadratureSpace : public mfem::QuadratureSpace +{ +public: + OwnedQuadratureSpace(mfem::Mesh& mesh, std::unique_ptr ir) + : mfem::QuadratureSpace(mesh, *ir) + , m_ir(std::move(ir)) + { } + +private: + std::unique_ptr m_ir; +}; + +} // namespace + +bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType) +{ + if(quadratureType == axom::numerics::QuadratureType::Invalid) + { + return false; + } + + const auto dim = mesh.Dimension(); + SLIC_ERROR_IF(sampleResolution.size() != static_cast(dim), + "Sample resolution dimension does not match mesh dimension"); + + if(mesh.GetNE() > 0) + { + switch(mesh.GetTypicalElementGeometry()) + { + case mfem::Geometry::SQUARE: + return sampleResolution[0] != sampleResolution[1]; + case mfem::Geometry::CUBE: + return sampleResolution[0] != sampleResolution[1] || + sampleResolution[0] != sampleResolution[2]; + default: + return false; + } + } + return mesh.Dimension(); +} + +int to_mfem_quadrature_type(axom::numerics::QuadratureType quadratureType) +{ + switch(quadratureType) + { + case axom::numerics::QuadratureType::Invalid: + return mfem::Quadrature1D::Invalid; + case axom::numerics::QuadratureType::GaussLegendre: + return mfem::Quadrature1D::GaussLegendre; + case axom::numerics::QuadratureType::GaussLobatto: + return mfem::Quadrature1D::GaussLobatto; + case axom::numerics::QuadratureType::OpenUniform: + return mfem::Quadrature1D::OpenUniform; + case axom::numerics::QuadratureType::ClosedUniform: + return mfem::Quadrature1D::ClosedUniform; + case axom::numerics::QuadratureType::OpenHalfUniform: + return mfem::Quadrature1D::OpenHalfUniform; + case axom::numerics::QuadratureType::ClosedGL: + return mfem::Quadrature1D::ClosedGL; + } + + SLIC_ERROR( + axom::fmt::format("Invalid quadrature type {}.", static_cast(quadratureType))); + return mfem::Quadrature1D::Invalid; +} + +mfem::GridFunction* getOrAllocateL2GridFunction(mfem::DataCollection* dc, + const std::string& gf_name, + int order, + int dim, + const int basis) +{ + if(dc == nullptr) + { + SLIC_WARNING("Cannot allocate grid function into null data collection"); + return nullptr; + } + + mfem::GridFunction* gf = nullptr; + + if(dc->HasField(gf_name)) + { + gf = dc->GetField(gf_name); + } + else + { + auto* fec = new mfem::L2_FECollection(order, dim, basis); + auto* mesh = dc->GetMesh(); + mfem::FiniteElementSpace* fes = new mfem::FiniteElementSpace(mesh, fec); + + auto* sidreDC = dynamic_cast(dc); + if(sidreDC) + { + const int sz = fes->GetVSize(); + auto* vw = sidreDC->AllocNamedBuffer(gf_name, sz); + gf = new mfem::GridFunction(); + gf->MakeRef(fes, vw->getData()); + } + else + { + gf = new mfem::GridFunction(fes); + } + + gf->MakeOwner(fec); + gf->HostReadWrite(); + *gf = 0.; + + dc->RegisterField(gf_name, gf); + } + + return gf; +} + +void replaceMaterial(mfem::QuadratureFunction* shapeQFunc, + mfem::QuadratureFunction* materialQFunc, + bool shapeReplacesMaterial) +{ + SLIC_ASSERT(shapeQFunc != nullptr); + SLIC_ASSERT(materialQFunc != nullptr); + SLIC_ASSERT(materialQFunc->Size() == shapeQFunc->Size()); + + const int SZ = materialQFunc->Size(); + double* mData = materialQFunc->HostReadWrite(); + double* sData = shapeQFunc->HostReadWrite(); + + if(shapeReplacesMaterial) + { + for(int j = 0; j < SZ; ++j) + { + mData[j] = sData[j] > 0 ? 0 : mData[j]; + } + } + else + { + for(int j = 0; j < SZ; ++j) + { + sData[j] = mData[j] > 0 ? 0 : sData[j]; + } + } +} + +void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, + mfem::QuadratureFunction* materialQFunc, + bool reuseExisting) +{ + SLIC_ASSERT(shapeQFunc != nullptr); + SLIC_ASSERT(materialQFunc != nullptr); + SLIC_ASSERT(materialQFunc->Size() == shapeQFunc->Size()); + + const int SZ = materialQFunc->Size(); + double* mData = materialQFunc->HostReadWrite(); + const double* sData = shapeQFunc->HostRead(); + + if(reuseExisting) + { + for(int j = 0; j < SZ; ++j) + { + mData[j] = sData[j] > 0 ? 1 : mData[j]; + } + } + else + { + for(int j = 0; j < SZ; ++j) + { + mData[j] = sData[j]; + } + } +} + +mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfunc) +{ + SLIC_ASSERT(qfunc != nullptr); + return new mfem::QuadratureFunction(*qfunc); +} + +void printRegisteredFieldNames(const SamplingMFEMState& mfemState, + const std::set& knownMaterials, + VolFracSampling vfSampling, + const std::string& initialMessage) +{ + SLIC_ASSERT(mfemState.m_dc != nullptr); + + auto extractKeys = [](const auto& map) { + std::vector keys; + for(const auto& kv : map) + { + keys.push_back(kv.first); + } + return keys; + }; + + axom::fmt::memory_buffer out; + axom::fmt::format_to(std::back_inserter(out), + "List of registered fields in the SamplingShaper {}" + "\n\t* Data collection grid funcs: {}" + "\n\t* Data collection qfuncs: {}" + "\n\t* Known materials: {}", + initialMessage, + axom::fmt::join(extractKeys(mfemState.m_dc->GetFieldMap()), ", "), + axom::fmt::join(extractKeys(mfemState.m_dc->GetQFieldMap()), ", "), + axom::fmt::join(knownMaterials, ", ")); + + if(vfSampling == VolFracSampling::SAMPLE_AT_QPTS) + { + axom::fmt::format_to(std::back_inserter(out), + "\n\t* Shape qfuncs: {}" + "\n\t* Mat qfuncs: {}", + axom::fmt::join(extractKeys(mfemState.m_inoutShapeQFuncs), ", "), + axom::fmt::join(extractKeys(mfemState.m_inoutMaterialQFuncs), ", ")); + } + else if(vfSampling == VolFracSampling::SAMPLE_AT_DOFS) + { + axom::fmt::format_to(std::back_inserter(out), + "\n\t* Shaping tensors: {}", + axom::fmt::join(extractKeys(mfemState.m_inoutTensors), ", ")); + } + + SLIC_INFO_ROOT(axom::fmt::to_string(out)); +} + +namespace +{ + +mfem::QuadratureSpace* makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRes) +{ + SLIC_ASSERT(mesh != nullptr); + const int NE = mesh->GetNE(); + + if(NE < 1) + { + SLIC_WARNING("Mesh has no elements!"); + return nullptr; + } + + const int sampleOrder = 2 * sampleRes - 1; + return new mfem::QuadratureSpace(mesh, sampleOrder); +} + +mfem::QuadratureSpace* makeCustomQuadratureSpace( + mfem::Mesh* mesh, + axom::ArrayView sampleRes, + axom::numerics::QuadratureType quadratureType) +{ + SLIC_ASSERT(mesh != nullptr); + const int NE = mesh->GetNE(); + const int dim = mesh->Dimension(); + + SLIC_ERROR_IF(sampleRes.size() != static_cast(dim), + "Sample resolution dimension does not match mesh dimension"); + + if(NE < 1) + { + SLIC_WARNING("Mesh has no elements!"); + return nullptr; + } + + mfem::IntegrationRule ird[3]; + for(int d = 0; d < dim; d++) + { + SLIC_ERROR_IF(sampleRes[d] < 1, + axom::fmt::format( + "Invalid sample value {} for dimension {}.", + sampleRes[d], + d)); + switch(quadratureType) + { + case axom::numerics::QuadratureType::GaussLegendre: + mfem::QuadratureFunctions1D::GaussLegendre(sampleRes[d], &ird[d]); + break; + case axom::numerics::QuadratureType::GaussLobatto: + mfem::QuadratureFunctions1D::GaussLobatto(sampleRes[d], &ird[d]); + break; + case axom::numerics::QuadratureType::OpenUniform: + mfem::QuadratureFunctions1D::OpenUniform(sampleRes[d], &ird[d]); + break; + case axom::numerics::QuadratureType::ClosedUniform: + mfem::QuadratureFunctions1D::ClosedUniform(sampleRes[d], &ird[d]); + break; + case axom::numerics::QuadratureType::OpenHalfUniform: + mfem::QuadratureFunctions1D::OpenHalfUniform(sampleRes[d], &ird[d]); + break; + case axom::numerics::QuadratureType::ClosedGL: + mfem::QuadratureFunctions1D::ClosedGL(sampleRes[d], &ird[d]); + break; + case axom::numerics::QuadratureType::Invalid: + default: + SLIC_ERROR(axom::fmt::format( + "Invalid quadrature type {}.", + static_cast(quadratureType))); + break; + } + } + std::unique_ptr ir; + if(dim == 1) + { + ir = std::make_unique(ird[0]); + } + else if(dim == 2) + { + ir = std::make_unique(ird[0], ird[1]); + } + else if(dim == 3) + { + ir = std::make_unique(ird[0], ird[1], ird[2]); + } + + return new OwnedQuadratureSpace(*mesh, std::move(ir)); +} + +void assembleVolumeFractionRHS(const mfem::FiniteElementSpace& fes, + mfem::QuadratureFunction& inout, + const mfem::IntegrationRule& sampleIR, + bool useAnisotropicAssembly, + mfem::Vector& b) +{ + mfem::QuadratureFunctionCoefficient qfc(inout); + mfem::DomainLFIntegrator rhs(qfc, &sampleIR); + + if(useAnisotropicAssembly) + { + mfem::Vector elemVec; + mfem::Array elemVDofs; + const int NE = fes.GetNE(); + for(int elem = 0; elem < NE; ++elem) + { + rhs.AssembleRHSElementVect( + *fes.GetFE(elem), + *fes.GetElementTransformation(elem), + elemVec); + fes.GetElementVDofs(elem, elemVDofs); + b.AddElementVector(elemVDofs, elemVec); + } + } + else + { + mfem::Array elem_marker(fes.GetNE()); + elem_marker.HostWrite(); + elem_marker = 1; + elem_marker.ReadWrite(); + rhs.AssembleDevice(fes, elem_marker, b); + } +} + +} // namespace + +void generatePositionsQFunction(mfem::Mesh* mesh, + QFunctionCollection& inoutQFuncs, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType) +{ + SLIC_ASSERT(mesh != nullptr); + const int NE = mesh->GetNE(); + const int dim = mesh->Dimension(); + + if(NE < 1) + { + SLIC_WARNING("Mesh has no elements!"); + return; + } + + mfem::QuadratureSpace* sp = nullptr; + if(quadratureType == axom::numerics::QuadratureType::Invalid) + { + SLIC_ERROR_IF(sampleResolution.empty(), "Invalid sampleResolution."); + sp = makeDefaultQuadratureSpace(mesh, sampleResolution[0]); + } + else + { + sp = makeCustomQuadratureSpace(mesh, sampleResolution, quadratureType); + } + SLIC_ERROR_IF(sp == nullptr, "Null QuadratureSpace."); + + const auto& ir = sp->GetElementIntRule(0); + const int nq = ir.GetNPoints(); + + auto* pos_coef = new mfem::QuadratureFunction(sp, dim); + pos_coef->SetOwnsSpace(true); + auto pos = mfem::Reshape(pos_coef->HostWrite(), dim, nq, NE); + + if(!usesAnisotropicCustomTensorQuadrature(*mesh, sampleResolution, quadratureType)) + { + const auto* geomFactors = + mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); + geomFactors->X.HostRead(); + + for(int i = 0; i < NE; ++i) + { + const int gf_elStartIdx = i * nq * dim; + for(int j = 0; j < dim; ++j) + { + for(int k = 0; k < nq; ++k) + { + pos(j, k, i) = geomFactors->X(gf_elStartIdx + (j * nq) + k); + } + } + } + + mesh->DeleteGeometricFactors(); + } + else + { + mfem::DenseMatrix pointMat(dim, nq); + for(int i = 0; i < NE; ++i) + { + auto* transform = sp->GetTransformation(i); + transform->Transform(ir, pointMat); + + for(int j = 0; j < dim; ++j) + { + for(int k = 0; k < nq; ++k) + { + pos(j, k, i) = pointMat(j, k); + } + } + } + } + + inoutQFuncs.Register("positions", pos_coef, true); +} + +void generateSamplingPositions(SamplingMFEMState& mfemState, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType) +{ + checkSampleResolution(mfemState, sampleResolution, quadratureType); + + if(mfemState.m_inoutShapeQFuncs.Has("positions")) + { + return; + } + + generatePositionsQFunction(mfemState.m_dc->GetMesh(), + mfemState.m_inoutShapeQFuncs, + sampleResolution, + quadratureType); +} + +void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, + const std::string& matField, + int volfracOrder, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType) +{ + AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); + + SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); + auto* inout = mfemState.getMaterialFunction(matField); + SLIC_ASSERT(inout != nullptr); + + auto* dc = mfemState.m_dc; + SLIC_ASSERT(dc != nullptr); + + const auto& sampleIR = inout->GetSpace()->GetIntRule(0); + const int sampleOrder = sampleIR.GetOrder(); + const int sampleNQ = sampleIR.GetNPoints(); + const int sampleSZ = inout->GetSpace()->GetSize(); + + mfem::Mesh* mesh = dc->GetMesh(); + const int dim = mesh->Dimension(); + const int NE = mesh->GetNE(); + + auto samples_per_dim = [=](auto sampleRes, int dimValue) -> std::string { + switch(dimValue) + { + case 2: + return axom::fmt::format(" ({} * {})", sampleRes[0], sampleRes[1]); + case 3: + return axom::fmt::format( + " ({} * {} * {})", + sampleRes[0], + sampleRes[1], + sampleRes[2]); + default: + return std::string(); + } + }; + + SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), + "In computeVolumeFractions(): num samples per element {}{} | " + "sample polynomial order {} | total samples {:L}", + sampleNQ, + samples_per_dim(sampleResolution, dim), + sampleOrder, + sampleSZ)); + + SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), + "Mesh has dim {} and {:L} elements", + dim, + NE)); + + const auto vf_name = axom::fmt::format("vol_frac_{}", matField.substr(10)); + mfem::GridFunction* vf = getOrAllocateL2GridFunction( + dc, + vf_name, + volfracOrder, + dim, + mfem::BasisType::Positive); + const mfem::FiniteElementSpace* fes = vf->FESpace(); + const int dofs = fes->GetTypicalFE()->GetDof(); + + mfem::DenseTensor* mass_mat {nullptr}; + const std::string mass_matrix_name = "shaping_mass_matrix"; + if(mfemState.m_inoutTensors.Has(mass_matrix_name)) + { + mass_mat = mfemState.m_inoutTensors.Get(mass_matrix_name); + } + else + { + AXOM_ANNOTATE_SCOPE("mass integrator assemble"); + + mass_mat = new mfem::DenseTensor(dofs, dofs, NE); + mass_mat->HostWrite(); + (*mass_mat) = 0.; + mass_mat->ReadWrite(); + + mfem::ConstantCoefficient one_coef(1.0); + mfem::MassIntegrator mass_integrator(one_coef, &sampleIR); + + if(usesAnisotropicCustomTensorQuadrature( + *fes->GetMesh(), + sampleResolution, + quadratureType)) + { + mfem::DenseMatrix elemMat; + mass_mat->HostWrite(); + for(int elem = 0; elem < NE; ++elem) + { + mass_integrator.AssembleElementMatrix(*fes->GetFE(elem), + *fes->GetElementTransformation(elem), + elemMat); + for(int j = 0; j < dofs; ++j) + { + for(int i = 0; i < dofs; ++i) + { + (*mass_mat)(i, j, elem) = elemMat(i, j); + } + } + } + } + else + { + const int sz = mass_mat->TotalSize(); + mfem::Vector mass_vec; + mfem::Swap(mass_mat->GetMemory(), mass_vec.GetMemory()); + mass_vec.SetSize(sz); + mass_integrator.AssembleEA(*fes, mass_vec, false); + mfem::Swap(mass_mat->GetMemory(), mass_vec.GetMemory()); + } + + mfemState.m_inoutTensors.Register(mass_matrix_name, mass_mat, true); + } + + mfem::DenseTensor* mass_mat_inv {nullptr}; + mfem::Array* mass_mat_pivots {nullptr}; + const std::string minv_name = "shaping_mass_matrix_inv"; + const std::string pivots_name = "shaping_mass_matrix_pivots"; + if(mfemState.m_inoutTensors.Has(minv_name) && + mfemState.m_inoutArrays.Has(pivots_name)) + { + mass_mat_inv = mfemState.m_inoutTensors.Get(minv_name); + mass_mat_pivots = mfemState.m_inoutArrays.Get(pivots_name); + } + else + { + AXOM_ANNOTATE_SCOPE("batch lu factor"); + + mass_mat->ReadWrite(); + mass_mat_inv = new mfem::DenseTensor(*mass_mat); + mass_mat_pivots = new mfem::Array(dofs * NE); + + mass_mat_inv->ReadWrite(); + mass_mat_pivots->Write(); + mfem::BatchLUFactor(*mass_mat_inv, *mass_mat_pivots); + + mfemState.m_inoutTensors.Register(minv_name, mass_mat_inv, true); + mfemState.m_inoutArrays.Register(pivots_name, mass_mat_pivots, true); + } + + mfem::DenseTensor* shaping_scratch_buffer {nullptr}; + const std::string scratch_buffer_name = "shaping_scratch_buffer"; + if(mfemState.m_inoutTensors.Has(scratch_buffer_name)) + { + shaping_scratch_buffer = mfemState.m_inoutTensors.Get(scratch_buffer_name); + } + else + { + shaping_scratch_buffer = new mfem::DenseTensor(dofs, dofs, NE); + shaping_scratch_buffer->HostWrite(); + (*shaping_scratch_buffer) = 0.; + mfemState.m_inoutTensors.Register( + scratch_buffer_name, + shaping_scratch_buffer, + true); + } + + axom::utilities::Timer timer(true); + { + mfem::Vector b(fes->GetVSize()); + SLIC_ASSERT(b.Size() == dofs * NE); + { + AXOM_ANNOTATE_SCOPE("domain lf integrator assemble"); + + inout->ReadWrite(); + b.HostWrite(); + b = 0.; + b.ReadWrite(); + + assembleVolumeFractionRHS( + *fes, + *inout, + sampleIR, + usesAnisotropicCustomTensorQuadrature( + *fes->GetMesh(), + sampleResolution, + quadratureType), + b); + } + inout->HostReadWrite(); + + { + AXOM_ANNOTATE_SCOPE("batch lu solve"); + + mass_mat_inv->Read(); + mass_mat_pivots->Read(); + + vf->HostReadWrite(); + (*vf) = b; + vf->ReadWrite(); + mfem::BatchLUSolve(*mass_mat_inv, *mass_mat_pivots, *vf); + } + mass_mat_inv->HostReadWrite(); + mass_mat_pivots->HostReadWrite(); + + constexpr double minY = 0.; + constexpr double maxY = 1.; + + auto m_d = mfem::Reshape(mass_mat->HostReadWrite(), dofs, dofs, NE); + auto b_d = mfem::Reshape(b.HostReadWrite(), dofs, NE); + auto vf_d = mfem::Reshape(vf->HostReadWrite(), dofs, NE); + auto fct_mat_d = + mfem::Reshape(shaping_scratch_buffer->HostReadWrite(), dofs, dofs, NE); + + AXOM_ANNOTATE_BEGIN("fct project"); + axom::for_all(0, NE, [=](int i) { + FCT_correct(&m_d(0, 0, i), + dofs, + &b_d(0, i), + minY, + maxY, + &vf_d(0, i), + &fct_mat_d(0, 0, i)); + }); + AXOM_ANNOTATE_END("fct project"); + } + timer.stop(); + + SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), + "\t Generating volume fractions '{}' took {:.3f} seconds (@ " + "{:L} dofs processed per second)", + vf_name, + timer.elapsed(), + static_cast(fes->GetNDofs() / timer.elapsed()))); + + vf->HostReadWrite(); +} + +void FCT_correct(const double* M, + const int s, + const double* m, + const double y_min, + const double y_max, + double* xy, + double* fct_mat) +{ + constexpr int STACK_CAPACITY = 64; + using StackArray = axom::StackArray; + + if(s == 1) + { + return; + } + + StackArray ML_stack; + StackArray z_stack; + StackArray beta_stack; + axom::Array ML_heap; + axom::Array z_heap; + axom::Array beta_heap; + + double* ML = nullptr; + double* z = nullptr; + double* beta = nullptr; + + if(s <= STACK_CAPACITY) + { + ML = ML_stack.data(); + z = z_stack.data(); + beta = beta_stack.data(); + } + else + { + ML_heap.resize(s); + z_heap.resize(s); + beta_heap.resize(s); + + ML = ML_heap.data(); + z = z_heap.data(); + beta = beta_heap.data(); + } + + for(int r = 0; r < s; ++r) + { + double dot = 0.; + for(int c = 0; c < s; ++c) + { + dot += M[r + c * s]; + } + ML[r] = dot; + } + + double sum_ML = 0.; + double sum_m = 0.; + for(int i = 0; i < s; ++i) + { + sum_ML += ML[i]; + sum_m += m[i]; + } + + const double y_avg = sum_m / sum_ML; + +#ifdef AXOM_DEBUG + constexpr double EPS = 1e-12; + SLIC_WARNING_IF(!(y_min < y_avg + EPS && y_avg < y_max + EPS), + axom::fmt::format("Average ({}) is out of bounds [{},{}]: ", + y_avg, + y_min - EPS, + y_max + EPS)); +#endif + + double sum_beta = 0.; + for(int i = 0; i < s; ++i) + { + beta[i] = ML[i]; + z[i] = m[i] - ML[i] * y_avg; + sum_beta += beta[i]; + } + + for(int i = 0; i < s; ++i) + { + beta[i] /= sum_beta; + } + + for(int i = 1; i < s; ++i) + { + for(int j = 0; j < i; ++j) + { + const int idx = i + j * s; + fct_mat[idx] = M[idx] * (xy[i] - xy[j]) + (beta[j] * z[i] - beta[i] * z[j]); + } + } + + auto* gp = z; + auto* gm = beta; + for(int t = 0; t < s; ++t) + { + gp[t] = 0.0; + gm[t] = 0.0; + } + + for(int i = 1; i < s; ++i) + { + for(int j = 0; j < i; ++j) + { + const int idx = i + j * s; + const double fij = fct_mat[idx]; + if(fij >= 0.0) + { + gp[i] += fij; + gm[j] -= fij; + } + else + { + gm[i] += fij; + gp[j] -= fij; + } + } + } + + for(int i = 0; i < s; ++i) + { + xy[i] = y_avg; + } + + for(int i = 0; i < s; ++i) + { + const double mi = ML[i]; + const double xyLi = xy[i]; + const double rp = axom::utilities::max(mi * (y_max - xyLi), 0.0); + const double rm = axom::utilities::min(mi * (y_min - xyLi), 0.0); + const double sp = gp[i]; + const double sm = gm[i]; + + gp[i] = (rp < sp) ? rp / sp : 1.0; + gm[i] = (rm > sm) ? rm / sm : 1.0; + } + + for(int i = 1; i < s; ++i) + { + for(int j = 0; j < i; ++j) + { + double fij = fct_mat[i + j * s]; + + const double aij = fij >= 0.0 ? axom::utilities::min(gp[i], gm[j]) + : axom::utilities::min(gm[i], gp[j]); + fij *= aij; + xy[i] += fij / ML[i]; + xy[j] -= fij / ML[j]; + } + } + +#ifdef AXOM_DEBUG + for(int i = 0; i < s; ++i) + { + SLIC_WARNING_IF(!(y_min < xy[i] + EPS && xy[i] < y_max + EPS), + axom::fmt::format("Volume fraction {} w/ value {} is out of bounds [{},{}]: ", + i, + xy[i], + y_min - EPS, + y_max + EPS)); + } +#endif +} + +void computeVolumeFractionsIdentity(mfem::DataCollection* dc, + mfem::QuadratureFunction* inout, + const std::string& name) +{ + const int order = inout->GetSpace()->GetIntRule(0).GetOrder(); + + mfem::Mesh* mesh = dc->GetMesh(); + const int dim = mesh->Dimension(); + const int NE = mesh->GetNE(); + + std::cout << axom::fmt::format("Mesh has dim {} and {} elements", dim, NE) + << std::endl; + + auto* fec = new mfem::L2_FECollection(order, dim, mfem::BasisType::Positive); + auto* fes = new mfem::FiniteElementSpace(mesh, fec); + auto* volFrac = new mfem::GridFunction(fes); + volFrac->MakeOwner(fec); + volFrac->HostReadWrite(); + dc->RegisterField(name, volFrac); + + (*volFrac) = (*inout); +} + +} // end namespace shaping +} // end namespace quest +} // end namespace axom + +#endif // defined(AXOM_USE_MFEM) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp new file mode 100644 index 0000000000..07fd41a875 --- /dev/null +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp @@ -0,0 +1,316 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef AXOM_QUEST_SHAPING_HELPERS_MFEM__HPP_ +#define AXOM_QUEST_SHAPING_HELPERS_MFEM__HPP_ + +#include "shaping_helpers.hpp" + +#if defined(AXOM_USE_MFEM) + +#include "axom/fmt.hpp" + +#include "mfem.hpp" +#include "mfem/linalg/dtensor.hpp" + +#include +#include +#include + +namespace axom +{ +namespace quest +{ +namespace shaping +{ + +int to_mfem_quadrature_type(axom::numerics::QuadratureType quadratureType); + +using QFunctionCollection = mfem::NamedFieldsMap; +using DenseTensorCollection = mfem::NamedFieldsMap; +using MFEMArrayCollection = mfem::NamedFieldsMap>; + +struct MFEMState +{ + virtual ~MFEMState() = default; + + int meshDimension() const { return m_dc->GetMesh()->Dimension(); } + + sidre::MFEMSidreDataCollection* m_dc {nullptr}; +}; + +struct SamplingMFEMState : public MFEMState +{ + ~SamplingMFEMState() override + { + m_inoutShapeQFuncs.DeleteData(true); + m_inoutShapeQFuncs.clear(); + + m_inoutMaterialQFuncs.DeleteData(true); + m_inoutMaterialQFuncs.clear(); + + m_inoutTensors.DeleteData(true); + m_inoutTensors.clear(); + + m_inoutArrays.DeleteData(true); + m_inoutArrays.clear(); + } + + mfem::QuadratureFunction* getShapeFunction(const std::string& name) + { + return m_inoutShapeQFuncs.Get(name); + } + + const mfem::QuadratureFunction* getShapeFunction(const std::string& name) const + { + return m_inoutShapeQFuncs.Get(name); + } + + void deleteShapeFunction(const std::string& AXOM_UNUSED_PARAM(name)) + { + // TODO: remove the function from m_inoutShapeQFuncs if it exists. + } + + mfem::QuadratureFunction* getMaterialFunction(const std::string& name) + { + return m_inoutMaterialQFuncs.Get(name); + } + + const mfem::QuadratureFunction* getMaterialFunction(const std::string& name) const + { + return m_inoutMaterialQFuncs.Get(name); + } + + mfem::QuadratureFunction* createMaterialFunction(const std::string& name) + { + auto* positions = m_inoutShapeQFuncs.Get("positions"); + SLIC_ERROR_IF(positions == nullptr, + std::string("Cannot create material function '") + name + + "' without positions."); + + auto* qfunc = new mfem::QuadratureFunction(positions->GetSpace(), 1); + qfunc->HostWrite(); + *qfunc = 0.; + m_inoutMaterialQFuncs.Register(name, qfunc, true); + return qfunc; + } + + QFunctionCollection m_inoutShapeQFuncs; + QFunctionCollection m_inoutMaterialQFuncs; + DenseTensorCollection m_inoutTensors; + MFEMArrayCollection m_inoutArrays; +}; + +void printRegisteredFieldNames(const SamplingMFEMState& mfemState, + const std::set& knownMaterials, + VolFracSampling vfSampling, + const std::string& initialMessage); + +mfem::GridFunction* getOrAllocateL2GridFunction(mfem::DataCollection* dc, + const std::string& gf_name, + int order, + int dim, + const int basis); + +void replaceMaterial(mfem::QuadratureFunction* shapeQFunc, + mfem::QuadratureFunction* materialQFunc, + bool shouldReplace); + +void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, + mfem::QuadratureFunction* materialQFunc, + bool reuseExisting = true); + +mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfunc); + +void generatePositionsQFunction(mfem::Mesh* mesh, + QFunctionCollection& inoutQFuncs, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType); + +void generateSamplingPositions(SamplingMFEMState& mfemState, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType); + +void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, + const std::string& matField, + int volfracOrder, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType); + +void computeVolumeFractionsIdentity(mfem::DataCollection* dc, + mfem::QuadratureFunction* inout, + const std::string& name); + +bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType); + +template +void sampleInOutField(const std::string shapeName, + shaping::SamplingMFEMState& mfemState, + InsideFunc&& checkInside, + PointProjector projector = {}) +{ + using FromPoint = primal::Point; + using ToPoint = primal::Point; + AXOM_ANNOTATE_SCOPE("sampleInOutField"); + + SLIC_ERROR_IF(FromDim != ToDim && !projector, + "A projector callback function is required when FromDim != ToDim"); + + auto* mesh = mfemState.m_dc->GetMesh(); + SLIC_ASSERT(mesh != nullptr); + const int NE = mesh->GetNE(); + const int dim = mesh->Dimension(); + + auto& inoutQFuncs = mfemState.m_inoutShapeQFuncs; + SLIC_ASSERT(inoutQFuncs.Has("positions")); + + mfem::QuadratureFunction* pos_coef = inoutQFuncs.Get("positions"); + auto* sp = pos_coef->GetSpace(); + const int nq = sp->GetIntRule(0).GetNPoints(); + const int numQueryPoints = sp->GetSize(); + SLIC_ASSERT(numQueryPoints == NE * nq); + + const auto pos = mfem::Reshape(pos_coef->HostRead(), dim, nq, NE); + + const std::string inoutName = axom::fmt::format("inout_{}", shapeName); + auto* inout = new mfem::QuadratureFunction(sp, 1); + inoutQFuncs.Register(inoutName, inout, true); + auto inout_vals = mfem::Reshape(inout->HostWrite(), nq, NE); + + axom::utilities::Timer timer(true); + if(projector) + { + for(int i = 0; i < NE; ++i) + { + for(int p = 0; p < nq; ++p) + { + const ToPoint pt = projector(FromPoint(&pos(0, p, i), dim)); + inout_vals(p, i) = checkInside(pt) ? 1. : 0.; + } + } + } + else + { + for(int i = 0; i < NE; ++i) + { + for(int p = 0; p < nq; ++p) + { + const ToPoint pt(&pos(0, p, i), dim); + inout_vals(p, i) = checkInside(pt) ? 1. : 0.; + } + } + } + timer.stop(); + + SLIC_INFO_ROOT(axom::fmt::format( + axom::utilities::locale(), + "\t Sampling inout field '{}' took {:.3Lf} seconds (@ {:L} queries per second)", + inoutName, + timer.elapsed(), + static_cast(numQueryPoints / timer.elapsed()))); +} + +template +void computeVolumeFractionsBaseline(const std::string& shapeName, + shaping::SamplingMFEMState& mfemState, + int outputOrder, + InsideFunc&& checkInside, + PointProjector projector = {}) +{ + using FromPoint = primal::Point; + using ToPoint = primal::Point; + AXOM_ANNOTATE_SCOPE("computeVolumeFractionsBaseline"); + + mfem::DataCollection* dc = mfemState.m_dc; + mfem::Mesh* mesh = dc->GetMesh(); + const int NE = mesh->GetNE(); + const int dim = mesh->Dimension(); + + if(NE < 1) + { + SLIC_WARNING("Mesh has no elements!"); + return; + } + + const auto volFracName = axom::fmt::format("vol_frac_{}", shapeName); + mfem::GridFunction* volFrac = shaping::getOrAllocateL2GridFunction( + dc, + volFracName, + outputOrder, + dim, + mfem::BasisType::Positive); + const mfem::FiniteElementSpace* fes = volFrac->FESpace(); + + auto* fe = fes->GetFE(0); + auto& ir = fe->GetNodes(); + + const int nq = ir.GetNPoints(); + const auto* geomFactors = + mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); + + mfem::DenseTensor pos_coef(dim, nq, NE); + for(int i = 0; i < NE; ++i) + { + for(int j = 0; j < dim; ++j) + { + for(int k = 0; k < nq; ++k) + { + pos_coef(j, k, i) = geomFactors->X((i * nq * dim) + (j * nq) + k); + } + } + } + + mfem::Vector res(nq); + mfem::Array dofs; + if(projector) + { + for(int i = 0; i < NE; ++i) + { + const mfem::DenseMatrix& m = pos_coef(i); + for(int p = 0; p < nq; ++p) + { + const ToPoint pt = projector(FromPoint(m.GetColumn(p), dim)); + res(p) = checkInside(pt) ? 1. : 0.; + } + + fes->GetElementDofs(i, dofs); + volFrac->SetSubVector(dofs, res); + } + } + else + { + for(int i = 0; i < NE; ++i) + { + const mfem::DenseMatrix& m = pos_coef(i); + for(int p = 0; p < nq; ++p) + { + const ToPoint pt(m.GetColumn(p), dim); + res(p) = checkInside(pt) ? 1. : 0.; + } + + fes->GetElementDofs(i, dofs); + volFrac->SetSubVector(dofs, res); + } + } +} + +void FCT_correct(const double* M, + const int s, + const double* m, + const double y_min, + const double y_max, + double* xy, + double* fct_mat); + +} // end namespace shaping +} // end namespace quest +} // end namespace axom + +#endif // defined(AXOM_USE_MFEM) + +#endif // AXOM_QUEST_SHAPING_HELPERS_MFEM__HPP_ From 269bf0f6b4893120abb99e727c89834947959667 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 22 May 2026 10:56:44 -0700 Subject: [PATCH 17/72] Moved some functions out of the header. Added timing. Fixed a bug. --- src/axom/quest/SamplingShaper.cpp | 52 +++++++++++++++++++ src/axom/quest/SamplingShaper.hpp | 46 ++-------------- .../shaping/shaping_helpers_blueprint.cpp | 4 +- .../detail/shaping/shaping_helpers_mfem.cpp | 2 + 4 files changed, 61 insertions(+), 43 deletions(-) diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 9653b3de05..1daf60ce37 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -10,6 +10,58 @@ namespace axom { namespace quest { + + void SamplingShaper::setQuadratureType(axom::numerics::QuadratureType qtype) + { + if(m_bp_state != nullptr) + { + // For Blueprint, we rely on Axom quadrature types and not all are implementd yet. + if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) + { +std::cout << "Setting m_quadratureType = " << static_cast(qtype) << std::endl; + m_quadratureType = qtype; + } + else + { + SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); + } + } + } + + void SamplingShaper::setSamplingResolution(int sampleRes) + { + SLIC_ERROR_IF(sampleRes < 1, "Invalid sample resolution"); + m_samplingResolution.clear(); + const auto dim = meshDimension(); + for(int d = 0; d < dim; d++) + { + m_samplingResolution.push_back(sampleRes); + } + } + + void SamplingShaper::setSamplingResolution(axom::ArrayView sampleRes) + { + const auto dim = meshDimension(); + SLIC_ERROR_IF(static_cast(dim) != sampleRes.size(), + "Number of sample resolutions does not match mesh dimension."); + m_samplingResolution.clear(); + for(int d = 0; d < dim; d++) + { + SLIC_ERROR_IF(sampleRes[d] < 1, "Invalid sample resolution"); + m_samplingResolution.push_back(sampleRes[d]); + } + } + + void SamplingShaper::initializeSamplingResolution() + { + // Initialize the default number of samples based on the mesh dimension. + const int dim = meshDimension(); + for(int d = 0; d < dim; d++) + { + m_samplingResolution.push_back(5); + } + } + bool SamplingShaper::verifyInputMeshImpl(std::string& whyBad) const { bool rval = true; diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 1cf76548ce..64a5cda9d7 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -201,17 +201,7 @@ class SamplingShaper : public Shaper * * \param [in] qtype Quadrature family selection. */ - void setQuadratureType(axom::numerics::QuadratureType qtype) - { - if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) - { - m_quadratureType = qtype; - } - else - { - SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); - } - } + void setQuadratureType(axom::numerics::QuadratureType qtype); /*! * \brief Sets an isotropic sampling resolution for custom quadrature. @@ -224,16 +214,7 @@ class SamplingShaper : public Shaper * \param [in] sampleRes Number of sample points to use per logical * direction. */ - void setSamplingResolution(int sampleRes) - { - SLIC_ERROR_IF(sampleRes < 1, "Invalid sample resolution"); - m_samplingResolution.clear(); - const auto dim = meshDimension(); - for(int d = 0; d < dim; d++) - { - m_samplingResolution.push_back(sampleRes); - } - } + void setSamplingResolution(int sampleRes); /*! * \brief Sets an anisotropic sampling resolution for custom quadrature. @@ -248,18 +229,7 @@ class SamplingShaper : public Shaper * direction. The size needs to match the number of * mesh dimensions. */ - void setSamplingResolution(axom::ArrayView sampleRes) - { - const auto dim = meshDimension(); - SLIC_ERROR_IF(static_cast(dim) != sampleRes.size(), - "Number of sample resolutions does not match mesh dimension."); - m_samplingResolution.clear(); - for(int d = 0; d < dim; d++) - { - SLIC_ERROR_IF(sampleRes[d] < 1, "Invalid sample resolution"); - m_samplingResolution.push_back(sampleRes[d]); - } - } + void setSamplingResolution(axom::ArrayView sampleRes); // Deprecated backward compatibility method [[deprecated]] void setQuadratureOrder(int order) { setSamplingResolution(order); } @@ -296,15 +266,7 @@ class SamplingShaper : public Shaper #endif protected: /// Initializes the sampling resolution array based on the mesh dimension. - void initializeSamplingResolution() - { - // Initialize the default number of samples based on the mesh dimension. - const int dim = meshDimension(); - for(int d = 0; d < dim; d++) - { - m_samplingResolution.push_back(5); - } - } + void initializeSamplingResolution(); /*! * \brief Verifies the input mesh. diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 32aff5d607..3e598b0091 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -270,9 +270,10 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, quadratureType, sampleResolution[1], selectedAllocatorID); + const int nz = (sampleResolution.size() > 2) ? sampleResolution[2] : 1; auto ruleZ = getBlueprintQuadratureRule( quadratureType, - sampleResolution[2], + nz, selectedAllocatorID); axom::bump::views::dispatch_explicit_coordset(coordsetNode, [&](auto coordsetView) { @@ -332,6 +333,7 @@ void generateSamplingPositions(BlueprintState& bpState, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType) { + AXOM_ANNOTATE_SCOPE("generateSamplingPositions"); checkSampleResolution(bpState, sampleResolution, quadratureType); if(bpState.m_internal_node.has_path( diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp index 6cae0bdfbd..5e8fb533a9 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp @@ -447,6 +447,8 @@ void generateSamplingPositions(SamplingMFEMState& mfemState, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType) { + AXOM_ANNOTATE_SCOPE("generateSamplingPositions"); + checkSampleResolution(mfemState, sampleResolution, quadratureType); if(mfemState.m_inoutShapeQFuncs.Has("positions")) From e696f596ff282bc4b8db7d9bc1d3cc7a3959b072 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 22 May 2026 11:00:23 -0700 Subject: [PATCH 18/72] make style --- src/axom/core/numerics/quadrature.cpp | 21 +- src/axom/core/tests/numerics_quadrature.hpp | 6 +- src/axom/quest/IntersectionShaper.hpp | 10 +- src/axom/quest/SamplingShaper.cpp | 725 +++++++++--------- src/axom/quest/SamplingShaper.hpp | 69 +- src/axom/quest/Shaper.cpp | 17 +- src/axom/quest/Shaper.hpp | 6 +- .../quest/detail/shaping/InOutSampler.hpp | 10 +- .../detail/shaping/MappedZoneUtilities.hpp | 30 +- .../quest/detail/shaping/PrimitiveSampler.hpp | 5 +- .../detail/shaping/WindingNumberSampler.hpp | 5 +- .../shaping/shaping_helpers_blueprint.cpp | 150 ++-- .../shaping/shaping_helpers_blueprint.hpp | 42 +- .../detail/shaping/shaping_helpers_mfem.cpp | 106 +-- .../detail/shaping/shaping_helpers_mfem.hpp | 26 +- src/axom/quest/examples/shaping_driver.cpp | 75 +- .../tests/quest_blueprint_quadrature_mesh.cpp | 155 ++-- .../quest/tests/quest_sampling_shaper.cpp | 4 +- src/axom/quest/util/mesh_helpers.cpp | 12 +- 19 files changed, 687 insertions(+), 787 deletions(-) diff --git a/src/axom/core/numerics/quadrature.cpp b/src/axom/core/numerics/quadrature.cpp index 3939fb89fc..b419f487d9 100644 --- a/src/axom/core/numerics/quadrature.cpp +++ b/src/axom/core/numerics/quadrature.cpp @@ -318,8 +318,11 @@ QuadratureRule get_gauss_legendre(int npts, int allocatorID) // Store cached rules keyed by (npts, allocatorID). static axom::FlatMap rule_library(64); static std::mutex rule_library_mutex; - auto& storage = get_cached_rule_storage( - npts, allocatorID, rule_library, rule_library_mutex, compute_gauss_legendre_data); + auto& storage = get_cached_rule_storage(npts, + allocatorID, + rule_library, + rule_library_mutex, + compute_gauss_legendre_data); return QuadratureRule {storage.nodes.view(), storage.weights.view()}; } @@ -354,8 +357,11 @@ QuadratureRule get_open_uniform(int npts, int allocatorID) static axom::FlatMap rule_library(64); static std::mutex rule_library_mutex; - auto& storage = get_cached_rule_storage( - npts, allocatorID, rule_library, rule_library_mutex, compute_open_uniform_data); + auto& storage = get_cached_rule_storage(npts, + allocatorID, + rule_library, + rule_library_mutex, + compute_open_uniform_data); return QuadratureRule {storage.nodes.view(), storage.weights.view()}; } @@ -365,8 +371,11 @@ QuadratureRule get_closed_uniform(int npts, int allocatorID) static axom::FlatMap rule_library(64); static std::mutex rule_library_mutex; - auto& storage = get_cached_rule_storage( - npts, allocatorID, rule_library, rule_library_mutex, compute_closed_uniform_data); + auto& storage = get_cached_rule_storage(npts, + allocatorID, + rule_library, + rule_library_mutex, + compute_closed_uniform_data); return QuadratureRule {storage.nodes.view(), storage.weights.view()}; } diff --git a/src/axom/core/tests/numerics_quadrature.hpp b/src/axom/core/tests/numerics_quadrature.hpp index 696ef16347..b99632796b 100644 --- a/src/axom/core/tests/numerics_quadrature.hpp +++ b/src/axom/core/tests/numerics_quadrature.hpp @@ -246,12 +246,10 @@ TEST(numerics_quadrature, quadrature_type_dispatch) TEST(numerics_quadrature, open_uniform_exactness) { - check_polynomial_exactness( - [](int npts) { return axom::numerics::get_open_uniform(npts); }, 10); + check_polynomial_exactness([](int npts) { return axom::numerics::get_open_uniform(npts); }, 10); } TEST(numerics_quadrature, closed_uniform_exactness) { - check_polynomial_exactness( - [](int npts) { return axom::numerics::get_closed_uniform(npts); }, 10); + check_polynomial_exactness([](int npts) { return axom::numerics::get_closed_uniform(npts); }, 10); } diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 59e56449a9..c0742e5beb 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -2752,9 +2752,8 @@ class IntersectionShaper : public Shaper // conduit::Node meshNode; // m_group_ptr->createNativeLayout(m_internal_node); - const conduit::Node& topoNode = - m_bp_state->m_internal_node.fetch_existing("topologies") - .fetch_existing(m_bp_state->m_topology_name); + const conduit::Node& topoNode = m_bp_state->m_internal_node.fetch_existing("topologies") + .fetch_existing(m_bp_state->m_topology_name); const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); // Assume unstructured and hexahedral @@ -2826,9 +2825,8 @@ class IntersectionShaper : public Shaper // conduit::Node meshNode; // m_group_ptr->createNativeLayout(m_internal_node); - const conduit::Node& topoNode = - m_bp_state->m_internal_node.fetch_existing("topologies") - .fetch_existing(m_bp_state->m_topology_name); + const conduit::Node& topoNode = m_bp_state->m_internal_node.fetch_existing("topologies") + .fetch_existing(m_bp_state->m_topology_name); const conduit::Node& topoCoordsetNode = topoNode.fetch_existing("coordset"); const std::string coordsetName = topoCoordsetNode.as_string(); diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 1daf60ce37..84e37660d7 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -11,311 +11,311 @@ namespace axom namespace quest { - void SamplingShaper::setQuadratureType(axom::numerics::QuadratureType qtype) +void SamplingShaper::setQuadratureType(axom::numerics::QuadratureType qtype) +{ + if(m_bp_state != nullptr) { - if(m_bp_state != nullptr) + // For Blueprint, we rely on Axom quadrature types and not all are implementd yet. + if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) { - // For Blueprint, we rely on Axom quadrature types and not all are implementd yet. - if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) - { -std::cout << "Setting m_quadratureType = " << static_cast(qtype) << std::endl; - m_quadratureType = qtype; - } - else - { - SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); - } + std::cout << "Setting m_quadratureType = " << static_cast(qtype) << std::endl; + m_quadratureType = qtype; } - } - - void SamplingShaper::setSamplingResolution(int sampleRes) - { - SLIC_ERROR_IF(sampleRes < 1, "Invalid sample resolution"); - m_samplingResolution.clear(); - const auto dim = meshDimension(); - for(int d = 0; d < dim; d++) + else { - m_samplingResolution.push_back(sampleRes); + SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); } } +} - void SamplingShaper::setSamplingResolution(axom::ArrayView sampleRes) +void SamplingShaper::setSamplingResolution(int sampleRes) +{ + SLIC_ERROR_IF(sampleRes < 1, "Invalid sample resolution"); + m_samplingResolution.clear(); + const auto dim = meshDimension(); + for(int d = 0; d < dim; d++) { - const auto dim = meshDimension(); - SLIC_ERROR_IF(static_cast(dim) != sampleRes.size(), - "Number of sample resolutions does not match mesh dimension."); - m_samplingResolution.clear(); - for(int d = 0; d < dim; d++) - { - SLIC_ERROR_IF(sampleRes[d] < 1, "Invalid sample resolution"); - m_samplingResolution.push_back(sampleRes[d]); - } + m_samplingResolution.push_back(sampleRes); } +} - void SamplingShaper::initializeSamplingResolution() +void SamplingShaper::setSamplingResolution(axom::ArrayView sampleRes) +{ + const auto dim = meshDimension(); + SLIC_ERROR_IF(static_cast(dim) != sampleRes.size(), + "Number of sample resolutions does not match mesh dimension."); + m_samplingResolution.clear(); + for(int d = 0; d < dim; d++) { - // Initialize the default number of samples based on the mesh dimension. - const int dim = meshDimension(); - for(int d = 0; d < dim; d++) - { - m_samplingResolution.push_back(5); - } + SLIC_ERROR_IF(sampleRes[d] < 1, "Invalid sample resolution"); + m_samplingResolution.push_back(sampleRes[d]); } +} - bool SamplingShaper::verifyInputMeshImpl(std::string& whyBad) const +void SamplingShaper::initializeSamplingResolution() +{ + // Initialize the default number of samples based on the mesh dimension. + const int dim = meshDimension(); + for(int d = 0; d < dim; d++) { - bool rval = true; + m_samplingResolution.push_back(5); + } +} + +bool SamplingShaper::verifyInputMeshImpl(std::string& whyBad) const +{ + bool rval = true; #if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr) - { - rval = verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(whyBad); - } + if(m_bp_state != nullptr) + { + rval = verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(whyBad); + } #endif #if defined(AXOM_USE_MFEM) - if(getDC() != nullptr) - { - rval = verifyMFEMInputMesh(whyBad); - } + if(getDC() != nullptr) + { + rval = verifyMFEMInputMesh(whyBad); + } #endif - return rval; - } + return rval; +} #if defined(AXOM_USE_CONDUIT) - void SamplingShaper::saveBlueprintFile(const conduit::Node &n_mesh, const std::string &filename) const - { +void SamplingShaper::saveBlueprintFile(const conduit::Node& n_mesh, const std::string& filename) const +{ #ifdef CONDUIT_RELAY_MPI_ENABLED - conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, outputProtocol(), m_comm); + conduit::relay::mpi::io::blueprint::save_mesh(n_mesh, filename, outputProtocol(), m_comm); #else - conduit::relay::io::blueprint::save_mesh(n_mesh, filename, outputProtocol()); + conduit::relay::io::blueprint::save_mesh(n_mesh, filename, outputProtocol()); #endif - } +} #endif - void SamplingShaper::saveQuadraturePoints(const std::string& filename) const - { +void SamplingShaper::saveQuadraturePoints(const std::string& filename) const +{ #if defined(AXOM_USE_CONDUIT) - conduit::Node n_mesh; + conduit::Node n_mesh; - // Save the quadrature points from MFEM as a Blueprint file. -#if defined(AXOM_USE_MFEM) - if(m_mfem_state != nullptr) + // Save the quadrature points from MFEM as a Blueprint file. + #if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) + { + auto* positions = shapeQFuncs().Get("positions"); + if(positions == nullptr) { - auto* positions = shapeQFuncs().Get("positions"); - if(positions == nullptr) - { - SLIC_WARNING("No MFEM quadrature positions are available to save."); - return; - } - - const int dim = positions->GetSpace()->GetMesh()->Dimension(); - mfem::real_t* X = const_cast(positions->GetData()); - const int npts = positions->Size() / positions->GetVDim(); - const conduit::index_t stride = dim * sizeof(mfem::real_t); - n_mesh["coordsets/coords/type"] = "explicit"; - n_mesh["coordsets/coords/values/x"].set_external(X, npts, 0, stride); - n_mesh["coordsets/coords/values/y"].set_external(X, npts, sizeof(mfem::real_t), stride); - if(dim > 2) - { - n_mesh["coordsets/coords/values/z"].set_external(X, npts, 2 * sizeof(mfem::real_t), stride); - } - n_mesh["topologies/points/type"] = "unstructured"; - n_mesh["topologies/points/coordset"] = "coords"; - n_mesh["topologies/points/elements/shape"] = "point"; - std::vector tmp(npts); - std::iota(tmp.begin(), tmp.end(), 0); - n_mesh["topologies/points/elements/connectivity"].set(tmp); - n_mesh["topologies/points/elements/offsets"].set(tmp); - std::fill(tmp.begin(), tmp.end(), 1); - n_mesh["topologies/points/elements/sizes"].set(tmp); - - saveBlueprintFile(n_mesh, filename); - SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); + SLIC_WARNING("No MFEM quadrature positions are available to save."); return; } -#endif - // Save the Blueprint quadrature point mesh as a Blueprint file. - if(m_bp_state != nullptr) + const int dim = positions->GetSpace()->GetMesh()->Dimension(); + mfem::real_t* X = const_cast(positions->GetData()); + const int npts = positions->Size() / positions->GetVDim(); + const conduit::index_t stride = dim * sizeof(mfem::real_t); + n_mesh["coordsets/coords/type"] = "explicit"; + n_mesh["coordsets/coords/values/x"].set_external(X, npts, 0, stride); + n_mesh["coordsets/coords/values/y"].set_external(X, npts, sizeof(mfem::real_t), stride); + if(dim > 2) { - constexpr const char* quadName = "quadrature_points"; - const conduit::Node& bpMesh = m_bp_state->m_internal_node; + n_mesh["coordsets/coords/values/z"].set_external(X, npts, 2 * sizeof(mfem::real_t), stride); + } + n_mesh["topologies/points/type"] = "unstructured"; + n_mesh["topologies/points/coordset"] = "coords"; + n_mesh["topologies/points/elements/shape"] = "point"; + std::vector tmp(npts); + std::iota(tmp.begin(), tmp.end(), 0); + n_mesh["topologies/points/elements/connectivity"].set(tmp); + n_mesh["topologies/points/elements/offsets"].set(tmp); + std::fill(tmp.begin(), tmp.end(), 1); + n_mesh["topologies/points/elements/sizes"].set(tmp); + + saveBlueprintFile(n_mesh, filename); + SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); + return; + } + #endif - if(!bpMesh.has_path(axom::fmt::format("coordsets/{}", quadName)) || - !bpMesh.has_path(axom::fmt::format("topologies/{}", quadName))) - { - SLIC_WARNING("No Blueprint quadrature point mesh is available to save."); - return; - } + // Save the Blueprint quadrature point mesh as a Blueprint file. + if(m_bp_state != nullptr) + { + constexpr const char* quadName = "quadrature_points"; + const conduit::Node& bpMesh = m_bp_state->m_internal_node; + + if(!bpMesh.has_path(axom::fmt::format("coordsets/{}", quadName)) || + !bpMesh.has_path(axom::fmt::format("topologies/{}", quadName))) + { + SLIC_WARNING("No Blueprint quadrature point mesh is available to save."); + return; + } - n_mesh["coordsets"][quadName].update(bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", quadName))); - n_mesh["topologies"][quadName].update(bpMesh.fetch_existing(axom::fmt::format("topologies/{}", quadName))); + n_mesh["coordsets"][quadName].update( + bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", quadName))); + n_mesh["topologies"][quadName].update( + bpMesh.fetch_existing(axom::fmt::format("topologies/{}", quadName))); - if(bpMesh.has_path("fields")) + if(bpMesh.has_path("fields")) + { + const conduit::Node& fields = bpMesh.fetch_existing("fields"); + for(conduit::index_t i = 0; i < fields.number_of_children(); ++i) { - const conduit::Node& fields = bpMesh.fetch_existing("fields"); - for(conduit::index_t i = 0; i < fields.number_of_children(); ++i) + const conduit::Node& field = fields.child(i); + if(field.has_path("topology") && field.fetch_existing("topology").as_string() == quadName) { - const conduit::Node& field = fields.child(i); - if(field.has_path("topology") && field.fetch_existing("topology").as_string() == quadName) - { - n_mesh["fields"][field.name()].update(field); - } + n_mesh["fields"][field.name()].update(field); } } - - saveBlueprintFile(n_mesh, filename); - SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); - return; } - SLIC_WARNING("No mesh state is available for quadrature-point export."); + + saveBlueprintFile(n_mesh, filename); + SLIC_INFO_ROOT(axom::fmt::format("Saved quadrature point mesh to '{}'.", filename)); + return; + } + SLIC_WARNING("No mesh state is available for quadrature-point export."); #else - AXOM_UNUSED_VAR(filename); - SLIC_WARNING("Quadrature-point export requires Conduit Relay HDF5 support."); + AXOM_UNUSED_VAR(filename); + SLIC_WARNING("Quadrature-point export requires Conduit Relay HDF5 support."); #endif - } +} - void SamplingShaper::loadShape(const klee::Shape& shape) - { +void SamplingShaper::loadShape(const klee::Shape& shape) +{ #if defined(AXOM_USE_MFEM) - if(useWindingNumberSampler(shape)) - { - const std::string shapePath = - axom::utilities::filesystem::prefixRelativePath(shape.getGeometry().getPath(), m_prefixPath); - SLIC_INFO_ROOT("Reading file: " << shapePath << "..."); - // Read the MFEM file as curved polygon contours for winding number intersection. - quest::MFEMReader reader; - reader.setFileName(shapePath); - const int rc = reader.read(m_contours); - - SLIC_ERROR_IF(rc != quest::MFEMReader::READ_SUCCESS, - axom::fmt::format("Failed to read MFEM shape '{}' from file '{}'.", - shape.getName(), - shapePath)); - return; - } + if(useWindingNumberSampler(shape)) + { + const std::string shapePath = + axom::utilities::filesystem::prefixRelativePath(shape.getGeometry().getPath(), m_prefixPath); + SLIC_INFO_ROOT("Reading file: " << shapePath << "..."); + // Read the MFEM file as curved polygon contours for winding number intersection. + quest::MFEMReader reader; + reader.setFileName(shapePath); + const int rc = reader.read(m_contours); + + SLIC_ERROR_IF( + rc != quest::MFEMReader::READ_SUCCESS, + axom::fmt::format("Failed to read MFEM shape '{}' from file '{}'.", shape.getName(), shapePath)); + return; + } #else - SLIC_ERROR_IF(useWindingNumberSampler(shape), - "SamplingShaper winding-number sampling for MFEM shapes requires MFEM support."); + SLIC_ERROR_IF(useWindingNumberSampler(shape), + "SamplingShaper winding-number sampling for MFEM shapes requires MFEM support."); #endif - Shaper::loadShape(shape); - } + Shaper::loadShape(shape); +} void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const klee::Shape& shape) - { - AXOM_ANNOTATE_SCOPE("prepareShapeQuery"); +{ + AXOM_ANNOTATE_SCOPE("prepareShapeQuery"); - internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug - : slic::message::Warning); + internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug + : slic::message::Warning); - if(!shape.getGeometry().hasGeometry()) - { - return; - } + if(!shape.getGeometry().hasGeometry()) + { + return; + } - SLIC_INFO_ROOT(axom::fmt::format("{:-^80}", " Generating the spatial index ")); + SLIC_INFO_ROOT(axom::fmt::format("{:-^80}", " Generating the spatial index ")); - const auto& shapeName = shape.getName(); + const auto& shapeName = shape.getName(); - // Initialize the sampler based on shape format - // note: ignoring the global shapeDimension for now since it's causing problems - // reading c2c when the dimension is Three - AXOM_UNUSED_VAR(shapeDimension); - const auto format = this->shapeFormat(shape); - if(useWindingNumberSampler(shape)) - { - m_sampler = std::make_unique(shapeName, m_contours.view()); - } - else if(format == "c2c" || format == "mfem") - { - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - } - else if(format == "stl") - { - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - } - else if(format == "proe") + // Initialize the sampler based on shape format + // note: ignoring the global shapeDimension for now since it's causing problems + // reading c2c when the dimension is Three + AXOM_UNUSED_VAR(shapeDimension); + const auto format = this->shapeFormat(shape); + if(useWindingNumberSampler(shape)) + { + m_sampler = std::make_unique(shapeName, m_contours.view()); + } + else if(format == "c2c" || format == "mfem") + { + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + } + else if(format == "stl") + { + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + } + else if(format == "proe") + { + using Policy = runtime_policy::Policy; + switch(this->getExecutionPolicy()) { - using Policy = runtime_policy::Policy; - switch(this->getExecutionPolicy()) - { - case Policy::seq: - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - break; + case Policy::seq: + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + break; #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) - case Policy::omp: - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - break; + case Policy::omp: + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + break; #endif #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) - case Policy::cuda: - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - break; + case Policy::cuda: + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + break; #endif #if defined(AXOM_RUNTIME_POLICY_USE_HIP) - case Policy::hip: - m_sampler = std::make_unique(shapeName, m_surfaceMesh); - break; + case Policy::hip: + m_sampler = std::make_unique(shapeName, m_surfaceMesh); + break; #endif - default: - SLIC_ERROR("Unsupported execution policy for PrimitiveSampler3D"); - break; - } + default: + SLIC_ERROR("Unsupported execution policy for PrimitiveSampler3D"); + break; } + } - SLIC_ASSERT(hasValidSampler()); + SLIC_ASSERT(hasValidSampler()); - // Use visitor to initialize the sampler - std::visit( - [this](auto& sampler) { - using T = std::decay_t; - if constexpr(std::is_same_v) - { - // no op -- monostate - } - else if constexpr(is_wnsampler_v) - { - sampler->computeBounds(); - sampler->initSpatialIndex(this->m_vertexWeldThreshold); - } - else if constexpr(is_inoutsampler_v) - { - sampler->computeBounds(); - sampler->initSpatialIndex(this->m_vertexWeldThreshold); - } - else if constexpr(is_primitivesampler_v) - { - sampler->computeBounds(); - sampler->initSpatialIndex(); - } - }, - m_sampler); - - // Output some logging info and dump the mesh - if(this->isVerbose() && this->getRank() == 0) - { - if(m_surfaceMesh != nullptr) + // Use visitor to initialize the sampler + std::visit( + [this](auto& sampler) { + using T = std::decay_t; + if constexpr(std::is_same_v) + { + // no op -- monostate + } + else if constexpr(is_wnsampler_v) { - const int nVerts = m_surfaceMesh->getNumberOfNodes(); - const int nCells = m_surfaceMesh->getNumberOfCells(); - SLIC_INFO(axom::fmt::format("After welding, surface mesh has {} vertices and {} elements.", - nVerts, - nCells)); - mint::write_vtk(m_surfaceMesh.get(), - axom::fmt::format("melded_shape_mesh_{}.vtk", shapeName)); + sampler->computeBounds(); + sampler->initSpatialIndex(this->m_vertexWeldThreshold); } - else if(!m_contours.empty()) + else if constexpr(is_inoutsampler_v) { - SLIC_INFO(axom::fmt::format("Contours contain {} curved polygons.", m_contours.size())); + sampler->computeBounds(); + sampler->initSpatialIndex(this->m_vertexWeldThreshold); } + else if constexpr(is_primitivesampler_v) + { + sampler->computeBounds(); + sampler->initSpatialIndex(); + } + }, + m_sampler); + + // Output some logging info and dump the mesh + if(this->isVerbose() && this->getRank() == 0) + { + if(m_surfaceMesh != nullptr) + { + const int nVerts = m_surfaceMesh->getNumberOfNodes(); + const int nCells = m_surfaceMesh->getNumberOfCells(); + SLIC_INFO(axom::fmt::format("After welding, surface mesh has {} vertices and {} elements.", + nVerts, + nCells)); + mint::write_vtk(m_surfaceMesh.get(), axom::fmt::format("melded_shape_mesh_{}.vtk", shapeName)); + } + else if(!m_contours.empty()) + { + SLIC_INFO(axom::fmt::format("Contours contain {} curved polygons.", m_contours.size())); } } +} #if defined(AXOM_USE_MFEM) - /** +/** * \brief Import an initial set of material volume fractions before shaping * * \param [in] initialGridFuncions The input data as a map from material names to grid functions @@ -323,174 +323,169 @@ void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const kl * The imported grid functions are interpolated at quadrature points and registered * with the supplied names as material-based quadrature fields */ - void SamplingShaper::importInitialVolumeFractions(const std::map& initialGridFunctions) - { - internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug - : slic::message::Warning); +void SamplingShaper::importInitialVolumeFractions( + const std::map& initialGridFunctions) +{ + internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug + : slic::message::Warning); - auto& mfemState = samplingMFEMState(); - auto* mesh = mfemState.m_dc->GetMesh(); - // Sample the InOut field at the mesh quadrature points - if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) - { - shaping::generateSamplingPositions(mfemState, m_samplingResolution.view(), m_quadratureType); - } - auto* positionsQSpace = mfemState.m_inoutShapeQFuncs.Get("positions")->GetSpace(); + auto& mfemState = samplingMFEMState(); + auto* mesh = mfemState.m_dc->GetMesh(); + // Sample the InOut field at the mesh quadrature points + if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) + { + shaping::generateSamplingPositions(mfemState, m_samplingResolution.view(), m_quadratureType); + } + auto* positionsQSpace = mfemState.m_inoutShapeQFuncs.Get("positions")->GetSpace(); - // Interpolate grid functions at quadrature points & register material quad functions - // assume all elements have same integration rule - for(auto& entry : initialGridFunctions) - { - const auto& name = entry.first; - auto* gf = entry.second; + // Interpolate grid functions at quadrature points & register material quad functions + // assume all elements have same integration rule + for(auto& entry : initialGridFunctions) + { + const auto& name = entry.first; + auto* gf = entry.second; - SLIC_INFO_ROOT(axom::fmt::format("Importing volume fraction field for '{}' material", name)); + SLIC_INFO_ROOT(axom::fmt::format("Importing volume fraction field for '{}' material", name)); - if(gf == nullptr) - { - SLIC_WARNING( - axom::fmt::format("Skipping missing volume fraction field for material '{}'", name)); - continue; - } + if(gf == nullptr) + { + SLIC_WARNING( + axom::fmt::format("Skipping missing volume fraction field for material '{}'", name)); + continue; + } - auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); - const auto& ir = matQFunc->GetSpace()->GetIntRule(0); + auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); + const auto& ir = matQFunc->GetSpace()->GetIntRule(0); - if(shaping::usesAnisotropicCustomTensorQuadrature(*mesh, m_samplingResolution, m_quadratureType)) - { - // Avoid MFEM's tensor quadrature interpolation path only for - // anisotropic custom quad/hex rules. MFEM infers a single q1d from - // ir.GetNPoints(), which cannot represent per-direction sample counts - // such as 3 x 5 or 3 x 5 x 2. - mfem::Vector elemValues; - mfem::Vector qfuncValues; - for(int elem = 0; elem < mesh->GetNE(); ++elem) - { - gf->GetValues(elem, ir, elemValues); - matQFunc->GetValues(elem, qfuncValues); - qfuncValues = elemValues; - } - } - else + if(shaping::usesAnisotropicCustomTensorQuadrature(*mesh, m_samplingResolution, m_quadratureType)) + { + // Avoid MFEM's tensor quadrature interpolation path only for + // anisotropic custom quad/hex rules. MFEM infers a single q1d from + // ir.GetNPoints(), which cannot represent per-direction sample counts + // such as 3 x 5 or 3 x 5 x 2. + mfem::Vector elemValues; + mfem::Vector qfuncValues; + for(int elem = 0; elem < mesh->GetNE(); ++elem) { - const auto* interp = gf->FESpace()->GetQuadratureInterpolator(ir); - SLIC_ERROR_IF(interp == nullptr, - axom::fmt::format("Could not create a quadrature interpolator while " - "importing volume fractions for '{}'.", - name)); - interp->Values(*gf, *matQFunc); + gf->GetValues(elem, ir, elemValues); + matQFunc->GetValues(elem, qfuncValues); + qfuncValues = elemValues; } - - const auto matName = axom::fmt::format("mat_inout_{}", name); - materialQFuncs().Register(matName, matQFunc, true); } + else + { + const auto* interp = gf->FESpace()->GetQuadratureInterpolator(ir); + SLIC_ERROR_IF(interp == nullptr, + axom::fmt::format("Could not create a quadrature interpolator while " + "importing volume fractions for '{}'.", + name)); + interp->Values(*gf, *matQFunc); + } + + const auto matName = axom::fmt::format("mat_inout_{}", name); + materialQFuncs().Register(matName, matQFunc, true); } +} #endif - void SamplingShaper::printRegisteredFieldNames(const std::string& initialMessage) - { +void SamplingShaper::printRegisteredFieldNames(const std::string& initialMessage) +{ #if defined(AXOM_USE_MFEM) - if(m_mfem_state != nullptr) - { - shaping::printRegisteredFieldNames(samplingMFEMState(), - m_knownMaterials, - m_vfSampling, - initialMessage); - return; - } + if(m_mfem_state != nullptr) + { + shaping::printRegisteredFieldNames(samplingMFEMState(), + m_knownMaterials, + m_vfSampling, + initialMessage); + return; + } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr) - { - shaping::printRegisteredFieldNames(*m_bp_state, - m_knownMaterials, - m_vfSampling, - initialMessage); - return; - } -#endif - SLIC_INFO_ROOT(axom::fmt::format("SamplingShaper {} has no registered fields.", - initialMessage)); + if(m_bp_state != nullptr) + { + shaping::printRegisteredFieldNames(*m_bp_state, m_knownMaterials, m_vfSampling, initialMessage); + return; } +#endif + SLIC_INFO_ROOT(axom::fmt::format("SamplingShaper {} has no registered fields.", initialMessage)); +} - void SamplingShaper::saveResults(bool extra) +void SamplingShaper::saveResults(bool extra) +{ + Shaper::saveResults(extra); + if(extra) { - Shaper::saveResults(extra); - if(extra) - { - saveQuadraturePoints("shaping_quadrature"); - } + saveQuadraturePoints("shaping_quadrature"); } +} - void SamplingShaper::computeVolumeFractionsForMaterial(const std::string& matField) - { +void SamplingShaper::computeVolumeFractionsForMaterial(const std::string& matField) +{ #if defined(AXOM_USE_MFEM) - if(m_mfem_state != nullptr) - { - // NOTE: We pass the m_samplingResolution and m_quadratureType values to this - // version of the function so we can detect whether we have anisotropic - // sampling, which is handled differently. - shaping::computeVolumeFractionsForMaterial( - samplingMFEMState(), - matField, - m_volfracOrder, - m_samplingResolution, - m_quadratureType); - return; - } + if(m_mfem_state != nullptr) + { + // NOTE: We pass the m_samplingResolution and m_quadratureType values to this + // version of the function so we can detect whether we have anisotropic + // sampling, which is handled differently. + shaping::computeVolumeFractionsForMaterial(samplingMFEMState(), + matField, + m_volfracOrder, + m_samplingResolution, + m_quadratureType); + return; + } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr) - { - shaping::computeVolumeFractionsForMaterial(*m_bp_state, matField); - return; - } -#endif - SLIC_ERROR("No mesh state is available for SamplingShaper."); + if(m_bp_state != nullptr) + { + shaping::computeVolumeFractionsForMaterial(*m_bp_state, matField); + return; } +#endif + SLIC_ERROR("No mesh state is available for SamplingShaper."); +} - void SamplingShaper::adjustVolumeFractions() - { - AXOM_ANNOTATE_SCOPE("adjustVolumeFractions"); +void SamplingShaper::adjustVolumeFractions() +{ + AXOM_ANNOTATE_SCOPE("adjustVolumeFractions"); - internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug - : slic::message::Warning); + internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug + : slic::message::Warning); - for(const auto& materialName : m_knownMaterials) - { - const auto matName = axom::fmt::format("mat_inout_{}", materialName); - SLIC_INFO_ROOT( - axom::fmt::format("Generating volume fraction fields for '{}' material", matName)); + for(const auto& materialName : m_knownMaterials) + { + const auto matName = axom::fmt::format("mat_inout_{}", materialName); + SLIC_INFO_ROOT(axom::fmt::format("Generating volume fraction fields for '{}' material", matName)); - switch(m_vfSampling) - { - case shaping::VolFracSampling::SAMPLE_AT_QPTS: - this->computeVolumeFractionsForMaterial(matName); - break; - case shaping::VolFracSampling::SAMPLE_AT_DOFS: - break; - } + switch(m_vfSampling) + { + case shaping::VolFracSampling::SAMPLE_AT_QPTS: + this->computeVolumeFractionsForMaterial(matName); + break; + case shaping::VolFracSampling::SAMPLE_AT_DOFS: + break; } } +} - int SamplingShaper::meshDimension() const - { - const int InvalidDimension = -1; - int dim = InvalidDimension; +int SamplingShaper::meshDimension() const +{ + const int InvalidDimension = -1; + int dim = InvalidDimension; #if defined(AXOM_USE_MFEM) - if(m_mfem_state) - { - dim = m_mfem_state->meshDimension(); - } + if(m_mfem_state) + { + dim = m_mfem_state->meshDimension(); + } #endif #if defined(AXOM_USE_CONDUIT) - if(dim == InvalidDimension && m_bp_state) - { - dim = m_bp_state->meshDimension(); - } -#endif - return dim; + if(dim == InvalidDimension && m_bp_state) + { + dim = m_bp_state->meshDimension(); } +#endif + return dim; +} -} // end namespace quest -} // end namespace axom +} // end namespace quest +} // end namespace axom diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 64a5cda9d7..cb76b4d4e4 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -284,7 +284,7 @@ class SamplingShaper : public Shaper * \param n_mesh The Blueprint mesh to save. * \param filename The name of the file to save. */ - void saveBlueprintFile(const conduit::Node &n_mesh, const std::string &filename) const; + void saveBlueprintFile(const conduit::Node& n_mesh, const std::string& filename) const; #endif /*! @@ -304,7 +304,7 @@ class SamplingShaper : public Shaper return std::make_unique(); } - /// + /// void initializeSamplingMFEMState() { // Shaper constructs its MFEM state in the base constructor, so upgrade it @@ -351,10 +351,7 @@ class SamplingShaper : public Shaper } shaping::MFEMArrayCollection& arrays() { return samplingMFEMState().m_inoutArrays; } - const shaping::MFEMArrayCollection& arrays() const - { - return samplingMFEMState().m_inoutArrays; - } + const shaping::MFEMArrayCollection& arrays() const { return samplingMFEMState().m_inoutArrays; } #endif bool hasValidSampler() const { return !std::holds_alternative(m_sampler); } @@ -408,7 +405,6 @@ class SamplingShaper : public Shaper /// Initializes the spatial index for shaping void prepareShapeQuery(klee::Dimensions shapeDimension, const klee::Shape& shape) override; - void runShapeQuery(const klee::Shape& shape) override { AXOM_ANNOTATE_SCOPE("runShapeQuery"); @@ -487,7 +483,8 @@ class SamplingShaper : public Shaper * The imported grid functions are interpolated at quadrature points and registered * with the supplied names as material-based quadrature fields */ - void importInitialVolumeFractions(const std::map& initialGridFunctions); + void importInitialVolumeFractions( + const std::map& initialGridFunctions); #endif /*! @@ -529,25 +526,21 @@ class SamplingShaper : public Shaper case 2: if(meshDim == 2) { - sampler->template sampleInOutField<2, 2>(meshState, - m_projector22); + sampler->template sampleInOutField<2, 2>(meshState, m_projector22); } else if(meshDim == 3) { - sampler->template sampleInOutField<3, 2>(meshState, - m_projector32); + sampler->template sampleInOutField<3, 2>(meshState, m_projector32); } break; case 3: if(meshDim == 2) { - sampler->template sampleInOutField<2, 3>(meshState, - m_projector23); + sampler->template sampleInOutField<2, 3>(meshState, m_projector23); } else if(meshDim == 3) { - sampler->template sampleInOutField<3, 3>(meshState, - m_projector33); + sampler->template sampleInOutField<3, 3>(meshState, m_projector33); } break; } @@ -613,7 +606,7 @@ class SamplingShaper : public Shaper template void runShapeQueryImpl(shaping::WindingNumberSampler* sampler) { - #if defined(AXOM_USE_MFEM) +#if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { runShapeQueryImplSampler(sampler, samplingMFEMState()); @@ -641,33 +634,31 @@ class SamplingShaper : public Shaper shaping::generateSamplingPositions(meshState, m_samplingResolution.view(), m_quadratureType); } - // Sample the InOut field at the mesh quadrature points - switch(m_vfSampling) - { - case shaping::VolFracSampling::SAMPLE_AT_QPTS: - switch(DIM) + // Sample the InOut field at the mesh quadrature points + switch(m_vfSampling) { - case 2: - SLIC_ERROR("Not implemented yet!"); - break; - case 3: - if(meshDim == 2) - { - sampler->template sampleInOutField<2, 3>(meshState, - m_projector23); - } - else if(meshDim == 3) + case shaping::VolFracSampling::SAMPLE_AT_QPTS: + switch(DIM) { - sampler->template sampleInOutField<3, 3>(meshState, - m_projector33); + case 2: + SLIC_ERROR("Not implemented yet!"); + break; + case 3: + if(meshDim == 2) + { + sampler->template sampleInOutField<2, 3>(meshState, m_projector23); + } + else if(meshDim == 3) + { + sampler->template sampleInOutField<3, 3>(meshState, m_projector33); + } + break; } break; + case shaping::VolFracSampling::SAMPLE_AT_DOFS: + SLIC_ERROR("Not implemented yet!"); + break; } - break; - case shaping::VolFracSampling::SAMPLE_AT_DOFS: - SLIC_ERROR("Not implemented yet!"); - break; - } }; #if defined(AXOM_USE_MFEM) diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 11227a3c88..315731daab 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -133,9 +133,9 @@ Shaper::Shaper(RuntimePolicy execPolicy, , m_mfem_state() #endif , m_bp_state() -#if defined(AXOM_USE_MPI) + #if defined(AXOM_USE_MPI) , m_comm(MPI_COMM_WORLD) -#endif + #endif { AXOM_ANNOTATE_SCOPE("Shaper::Shaper_Node"); @@ -266,10 +266,7 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, double percentError, do revolvedVolume = discreteShape.getRevolvedVolume(); } -bool Shaper::verifyInputMesh(std::string& whyBad) const -{ - return verifyInputMeshImpl(whyBad); -} +bool Shaper::verifyInputMesh(std::string& whyBad) const { return verifyInputMeshImpl(whyBad); } #if defined(AXOM_USE_CONDUIT) std::string Shaper::resolveBlueprintTopologyName(const sidre::Group* bpMesh, @@ -279,8 +276,7 @@ std::string Shaper::resolveBlueprintTopologyName(const sidre::Group* bpMesh, auto* topologiesGrp = bpMesh->getGroup("topologies"); SLIC_ERROR_IF(topologiesGrp == nullptr, "Blueprint mesh is missing a 'topologies' group."); - const std::string topologyName = - topo.empty() ? topologiesGrp->getGroupName(0) : topo; + const std::string topologyName = topo.empty() ? topologiesGrp->getGroupName(0) : topo; SLIC_ERROR_IF(topologyName == sidre::InvalidName, "Blueprint mesh does not contain any topology groups."); SLIC_ERROR_IF(!topologiesGrp->hasGroup(topologyName), @@ -460,7 +456,10 @@ void Shaper::saveResults(bool AXOM_UNUSED_PARAM(extra)) { const std::string filename("shaping"); #if defined(CONDUIT_RELAY_MPI_ENABLED) - conduit::relay::mpi::io::blueprint::save_mesh(m_bp_state->m_internal_node, filename, outputProtocol(), m_comm); + conduit::relay::mpi::io::blueprint::save_mesh(m_bp_state->m_internal_node, + filename, + outputProtocol(), + m_comm); #else conduit::relay::io::blueprint::save_mesh(m_bp_state->m_internal_node, filename, outputProtocol()); #endif diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index 8530b4f79d..a1ebf73389 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -266,14 +266,12 @@ class Shaper /*! * \brief Selects the Blueprint topology name to use and verifies it exists. */ - std::string resolveBlueprintTopologyName(const sidre::Group* bpMesh, - const std::string& topo) const; + std::string resolveBlueprintTopologyName(const sidre::Group* bpMesh, const std::string& topo) const; /*! * \brief Selects the Blueprint topology name to use and verifies it exists. */ - std::string resolveBlueprintTopologyName(const conduit::Node& bpMesh, - const std::string& topo) const; + std::string resolveBlueprintTopologyName(const conduit::Node& bpMesh, const std::string& topo) const; /*! * \brief Rebuilds the internal Conduit view and cached cell count from the diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index 8cb1b906bf..fe71c4aac5 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -121,10 +121,7 @@ class InOutSampler const InOutOctreeType* octree = m_octree; auto checkInside = [=](const PointType& pt) -> bool { return octree->within(pt); }; - shaping::sampleInOutField(m_shapeName, - mfemState, - checkInside, - projector); + shaping::sampleInOutField(m_shapeName, mfemState, checkInside, projector); } /*! @@ -185,10 +182,7 @@ class InOutSampler const InOutOctreeType* octree = m_octree; auto checkInside = [=](const PointType& pt) -> bool { return octree->within(pt); }; - shaping::sampleInOutField(m_shapeName, - bpState, - checkInside, - projector); + shaping::sampleInOutField(m_shapeName, bpState, checkInside, projector); } template diff --git a/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp b/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp index 358628e987..607ef7593c 100644 --- a/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp +++ b/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp @@ -43,11 +43,8 @@ namespace detail * \return The mapped physical-space point. */ template -AXOM_HOST_DEVICE primal::Point mapToPhysicalPoint( - const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v) +AXOM_HOST_DEVICE primal::Point +mapToPhysicalPoint(const ShapeType& zone, const CoordsetView& coordsetView, double u, double v) { using PointType = primal::Point; const auto p0 = coordsetView[zone.getId(0)]; @@ -84,12 +81,8 @@ AXOM_HOST_DEVICE primal::Point mapToPhysic * \return The mapped physical-space point. */ template -AXOM_HOST_DEVICE primal::Point mapToPhysicalPoint( - const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v, - double w) +AXOM_HOST_DEVICE primal::Point +mapToPhysicalPoint(const ShapeType& zone, const CoordsetView& coordsetView, double u, double v, double w) { using PointType = primal::Point; const auto p0 = coordsetView[zone.getId(0)]; @@ -170,8 +163,7 @@ AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d]; } - return axom::utilities::abs( - axom::numerics::determinant(dxdu[0], dxdv[0], dxdu[1], dxdv[1])); + return axom::utilities::abs(axom::numerics::determinant(dxdu[0], dxdv[0], dxdu[1], dxdv[1])); } /*! @@ -245,12 +237,12 @@ AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, VectorType dxdw; for(int d = 0; d < 3; ++d) { - dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d] + du4 * p4[d] + - du5 * p5[d] + du6 * p6[d] + du7 * p7[d]; - dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d] + dv4 * p4[d] + - dv5 * p5[d] + dv6 * p6[d] + dv7 * p7[d]; - dxdw[d] = dw0 * p0[d] + dw1 * p1[d] + dw2 * p2[d] + dw3 * p3[d] + dw4 * p4[d] + - dw5 * p5[d] + dw6 * p6[d] + dw7 * p7[d]; + dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d] + du4 * p4[d] + du5 * p5[d] + + du6 * p6[d] + du7 * p7[d]; + dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d] + dv4 * p4[d] + dv5 * p5[d] + + dv6 * p6[d] + dv7 * p7[d]; + dxdw[d] = dw0 * p0[d] + dw1 * p1[d] + dw2 * p2[d] + dw3 * p3[d] + dw4 * p4[d] + dw5 * p5[d] + + dw6 * p6[d] + dw7 * p7[d]; } return axom::utilities::abs(VectorType::scalar_triple_product(dxdu, dxdv, dxdw)); diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index a25469080a..44c5be5de8 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -305,10 +305,7 @@ class PrimitiveSampler PointProjector projector = {}) { auto checkInside = [](const primal::Point&) -> bool { return false; }; - shaping::sampleInOutField(m_shapeName, - bpState, - checkInside, - projector); + shaping::sampleInOutField(m_shapeName, bpState, checkInside, projector); } template diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index 1b933c887f..0c63be388f 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -322,10 +322,7 @@ class WindingNumberSampler } return inside; }; - shaping::sampleInOutField(m_shapeName, - bpState, - checkInside, - projector); + shaping::sampleInOutField(m_shapeName, bpState, checkInside, projector); } template diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 3e598b0091..027c24ff6c 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -9,12 +9,12 @@ #if defined(AXOM_USE_CONDUIT) -#include "axom/bump/views/dispatch_topology.hpp" -#include "axom/bump/views/dispatch_unstructured_topology.hpp" + #include "axom/bump/views/dispatch_topology.hpp" + #include "axom/bump/views/dispatch_unstructured_topology.hpp" -#include "conduit_blueprint_mesh.hpp" + #include "conduit_blueprint_mesh.hpp" -#include + #include namespace axom { @@ -30,19 +30,17 @@ constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; -constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = - "quadraturePhysicalWeights"; +constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; -numerics::QuadratureRule getBlueprintQuadratureRule( - axom::numerics::QuadratureType quadratureType, - int npts, - int allocatorID) +numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureType quadratureType, + int npts, + int allocatorID) { SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); - SLIC_ERROR_IF(!axom::numerics::is_supported_quadrature_type(quadratureType), - axom::fmt::format( - "Quadrature type {} is not yet supported for Blueprint quadrature meshes.", - static_cast(quadratureType))); + SLIC_ERROR_IF( + !axom::numerics::is_supported_quadrature_type(quadratureType), + axom::fmt::format("Quadrature type {} is not yet supported for Blueprint quadrature meshes.", + static_cast(quadratureType))); return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); } @@ -74,8 +72,7 @@ std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) SLIC_ERROR("Structured Blueprint topology is missing recognizable element dims."); } - SLIC_ERROR( - axom::fmt::format("Blueprint topology type '{}' is missing 'elements/shape'.", topoType)); + SLIC_ERROR(axom::fmt::format("Blueprint topology type '{}' is missing 'elements/shape'.", topoType)); return ""; } @@ -90,15 +87,13 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, conduit::Node& meshNode) { namespace views = axom::bump::views; - constexpr int SupportedShapes = - views::select_shapes(views::Quad_ShapeID, views::Hex_ShapeID); + constexpr int SupportedShapes = views::select_shapes(views::Quad_ShapeID, views::Hex_ShapeID); views::dispatch_topology( topoNode, [&](const auto&, auto topoView) { - GenerateQuadratureMesh generator( - topoView, - coordsetView); + GenerateQuadratureMesh generator(topoView, + coordsetView); generator.setAllocatorID(allocatorID); generator.execute(topoNode, coordsetNode, @@ -143,8 +138,7 @@ void printRegisteredFieldNames(const BlueprintState& bpState, std::vector names; if(bpState.m_internal_node.has_path("fields")) { - const conduit::Node& fieldsNode = - bpState.m_internal_node.fetch_existing("fields"); + const conduit::Node& fieldsNode = bpState.m_internal_node.fetch_existing("fields"); for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) { const std::string name = fieldsNode.child(i).name(); @@ -161,8 +155,7 @@ void printRegisteredFieldNames(const BlueprintState& bpState, std::vector names; if(bpState.m_internal_node.has_path("fields")) { - const conduit::Node& fieldsNode = - bpState.m_internal_node.fetch_existing("fields"); + const conduit::Node& fieldsNode = bpState.m_internal_node.fetch_existing("fields"); for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) { const std::string name = fieldsNode.child(i).name(); @@ -177,16 +170,13 @@ void printRegisteredFieldNames(const BlueprintState& bpState, return names; }; - const std::vector topologyNames = - bpState.m_internal_node.has_path("topologies") + const std::vector topologyNames = bpState.m_internal_node.has_path("topologies") ? extractChildren(bpState.m_internal_node.fetch_existing("topologies")) : std::vector {}; - const std::vector coordsetNames = - bpState.m_internal_node.has_path("coordsets") + const std::vector coordsetNames = bpState.m_internal_node.has_path("coordsets") ? extractChildren(bpState.m_internal_node.fetch_existing("coordsets")) : std::vector {}; - const std::vector fieldNames = - bpState.m_internal_node.has_path("fields") + const std::vector fieldNames = bpState.m_internal_node.has_path("fields") ? extractChildren(bpState.m_internal_node.fetch_existing("fields")) : std::vector {}; @@ -228,56 +218,47 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, const conduit::Node& topoNode = bpMeshNode.fetch_existing("topologies").fetch_existing(topologyName); const std::string topoType = topoNode.fetch_existing("type").as_string(); - SLIC_ERROR_IF(topoType != "unstructured" && topoType != "structured", - axom::fmt::format( - "Unsupported Blueprint topology type '{}' for quadrature mesh generation.", - topoType)); + SLIC_ERROR_IF( + topoType != "unstructured" && topoType != "structured", + axom::fmt::format("Unsupported Blueprint topology type '{}' for quadrature mesh generation.", + topoType)); const std::string shape = shaping::getBlueprintCellShape(topoNode); - SLIC_ERROR_IF(shape != "quad" && shape != "hex", - axom::fmt::format( - "Unsupported Blueprint element shape '{}' for quadrature mesh generation.", - shape)); + SLIC_ERROR_IF( + shape != "quad" && shape != "hex", + axom::fmt::format("Unsupported Blueprint element shape '{}' for quadrature mesh generation.", + shape)); const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); const conduit::Node& coordsetNode = bpMeshNode.fetch_existing("coordsets").fetch_existing(coordsetName); const std::string coordsetType = coordsetNode.fetch_existing("type").as_string(); - SLIC_ERROR_IF(coordsetType != "explicit", - axom::fmt::format( - "Unsupported Blueprint coordset type '{}' for quadrature mesh generation.", - coordsetType)); + SLIC_ERROR_IF( + coordsetType != "explicit", + axom::fmt::format("Unsupported Blueprint coordset type '{}' for quadrature mesh generation.", + coordsetType)); int selectedAllocatorID = allocatorID; if(!axom::execution_space::usesAllocId(selectedAllocatorID) && !axom::execution_space::usesAllocId(selectedAllocatorID) -#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && !axom::execution_space::usesAllocId(selectedAllocatorID) -#endif -#if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #endif + #if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && !axom::execution_space::usesAllocId(selectedAllocatorID) -#endif + #endif ) { selectedAllocatorID = axom::execution_space::allocatorID(); } - auto ruleX = getBlueprintQuadratureRule( - quadratureType, - sampleResolution[0], - selectedAllocatorID); - auto ruleY = getBlueprintQuadratureRule( - quadratureType, - sampleResolution[1], - selectedAllocatorID); + auto ruleX = getBlueprintQuadratureRule(quadratureType, sampleResolution[0], selectedAllocatorID); + auto ruleY = getBlueprintQuadratureRule(quadratureType, sampleResolution[1], selectedAllocatorID); const int nz = (sampleResolution.size() > 2) ? sampleResolution[2] : 1; - auto ruleZ = getBlueprintQuadratureRule( - quadratureType, - nz, - selectedAllocatorID); + auto ruleZ = getBlueprintQuadratureRule(quadratureType, nz, selectedAllocatorID); axom::bump::views::dispatch_explicit_coordset(coordsetNode, [&](auto coordsetView) { -#if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) if(axom::execution_space::usesAllocId(selectedAllocatorID)) { buildBlueprintQuadratureMesh(topoNode, @@ -290,8 +271,8 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, bpMeshNode); return; } -#endif -#if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #endif + #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) if(axom::execution_space::usesAllocId(selectedAllocatorID)) { buildBlueprintQuadratureMesh(topoNode, @@ -304,7 +285,7 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, bpMeshNode); return; } -#endif + #endif if(axom::execution_space::usesAllocId(selectedAllocatorID)) { buildBlueprintQuadratureMesh(topoNode, @@ -336,8 +317,7 @@ void generateSamplingPositions(BlueprintState& bpState, AXOM_ANNOTATE_SCOPE("generateSamplingPositions"); checkSampleResolution(bpState, sampleResolution, quadratureType); - if(bpState.m_internal_node.has_path( - axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) + if(bpState.m_internal_node.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) { return; } @@ -349,26 +329,24 @@ void generateSamplingPositions(BlueprintState& bpState, quadratureType); } -void computeVolumeFractionsForMaterial(BlueprintState& bpState, - const std::string& matField) +void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField) { AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); conduit::Node* inout = bpState.getMaterialFunction(matField); - SLIC_ERROR_IF(inout == nullptr, - axom::fmt::format( - "Missing Blueprint material field '{}' for volume fraction projection.", - matField)); + SLIC_ERROR_IF( + inout == nullptr, + axom::fmt::format("Missing Blueprint material field '{}' for volume fraction projection.", + matField)); conduit::Node& bpMeshNode = bpState.m_internal_node; SLIC_ERROR_IF(!bpMeshNode.has_path("fields/originalElements/values"), "Missing Blueprint originalElements field for volume fraction projection."); - SLIC_ERROR_IF( - !bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") && - !bpMeshNode.has_path("fields/quadratureWeights/values"), - "Missing Blueprint quadrature weight field for volume fraction projection."); + SLIC_ERROR_IF(!bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") && + !bpMeshNode.has_path("fields/quadratureWeights/values"), + "Missing Blueprint quadrature weight field for volume fraction projection."); const conduit::Node& topoNode = bpMeshNode.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); @@ -420,25 +398,23 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, for(axom::IndexType zoneIdx = 0; zoneIdx < vfValues.size(); ++zoneIdx) { - SLIC_ERROR_IF(axom::utilities::isNearlyEqual(totalWeightsView[zoneIdx], 0.0), - axom::fmt::format( - "Blueprint quadrature weights sum to zero in zone {} during volume fraction projection.", - zoneIdx)); + SLIC_ERROR_IF( + axom::utilities::isNearlyEqual(totalWeightsView[zoneIdx], 0.0), + axom::fmt::format( + "Blueprint quadrature weights sum to zero in zone {} during volume fraction projection.", + zoneIdx)); vfValues[zoneIdx] /= totalWeightsView[zoneIdx]; } } -void replaceMaterial(conduit::Node* shapeNode, - conduit::Node* materialNode, - bool shapeReplacesMaterial) +void replaceMaterial(conduit::Node* shapeNode, conduit::Node* materialNode, bool shapeReplacesMaterial) { SLIC_ASSERT(shapeNode != nullptr); SLIC_ASSERT(materialNode != nullptr); namespace utils = axom::bump::utilities; auto shapeValues = utils::make_array_view(shapeNode->fetch_existing("values")); - auto materialValues = - utils::make_array_view(materialNode->fetch_existing("values")); + auto materialValues = utils::make_array_view(materialNode->fetch_existing("values")); SLIC_ASSERT(shapeValues.size() == materialValues.size()); @@ -463,10 +439,8 @@ void copyShapeIntoMaterial(const conduit::Node* shapeNode, SLIC_ASSERT(materialNode != nullptr); namespace utils = axom::bump::utilities; - const auto shapeValues = - utils::make_array_view(shapeNode->fetch_existing("values")); - auto materialValues = - utils::make_array_view(materialNode->fetch_existing("values")); + const auto shapeValues = utils::make_array_view(shapeNode->fetch_existing("values")); + auto materialValues = utils::make_array_view(materialNode->fetch_existing("values")); SLIC_ASSERT(shapeValues.size() == materialValues.size()); diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 40d32b5b48..604a3400ac 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -11,15 +11,15 @@ #if defined(AXOM_USE_CONDUIT) -#include "axom/bump/utilities/conduit_memory.hpp" -#include "axom/bump/views/dispatch_coordset.hpp" -#include "axom/fmt.hpp" + #include "axom/bump/utilities/conduit_memory.hpp" + #include "axom/bump/views/dispatch_coordset.hpp" + #include "axom/fmt.hpp" -#include "conduit_node.hpp" + #include "conduit_node.hpp" -#include -#include -#include + #include + #include + #include namespace axom { @@ -64,14 +64,12 @@ struct BlueprintState conduit::Node* getShapeFunction(const std::string& name) { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] : nullptr; } const conduit::Node* getShapeFunction(const std::string& name) const { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] : nullptr; } void deleteShapeFunction(const std::string& name) @@ -88,30 +86,27 @@ struct BlueprintState conduit::Node* getMaterialFunction(const std::string& name) { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] : nullptr; } const conduit::Node* getMaterialFunction(const std::string& name) const { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] - : nullptr; + return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] : nullptr; } conduit::Node* createMaterialFunction(const std::string& name) { constexpr const char* quadratureTopologyName = "quadrature_points"; - SLIC_ERROR_IF(!m_internal_node.has_path("coordsets/quadrature_points/values"), - std::string("Cannot create material function '") + name + - "' without quadrature points."); + SLIC_ERROR_IF( + !m_internal_node.has_path("coordsets/quadrature_points/values"), + std::string("Cannot create material function '") + name + "' without quadrature points."); conduit::Node& fieldNode = m_internal_node["fields/" + name]; fieldNode.reset(); fieldNode["association"] = "element"; fieldNode["topology"] = quadratureTopologyName; - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); + const auto conduitAllocatorId = axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); conduit::Node& valuesNode = fieldNode["values"]; valuesNode.set_allocator(conduitAllocatorId); @@ -135,9 +130,7 @@ void printRegisteredFieldNames(const BlueprintState& bpState, VolFracSampling vfSampling, const std::string& initialMessage); -void replaceMaterial(conduit::Node* shapeNode, - conduit::Node* materialNode, - bool shouldReplace); +void replaceMaterial(conduit::Node* shapeNode, conduit::Node* materialNode, bool shouldReplace); void copyShapeIntoMaterial(const conduit::Node* shapeNode, conduit::Node* materialNode, @@ -194,7 +187,8 @@ void sampleInOutField(const std::string& shapeName, axom::utilities::Timer timer(true); axom::IndexType numQueryPoints = 0; axom::bump::views::dispatch_explicit_coordset( - bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)], [&](auto coordsetView) { + bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)], + [&](auto coordsetView) { using CoordsetView = typename std::decay::type; SLIC_ERROR_IF(CoordsetView::dimension() != FromDim, diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp index 5e8fb533a9..ebb6dfefb2 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp @@ -8,8 +8,8 @@ #if defined(AXOM_USE_MFEM) -#include -#include + #include + #include namespace axom { @@ -55,8 +55,7 @@ bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, case mfem::Geometry::SQUARE: return sampleResolution[0] != sampleResolution[1]; case mfem::Geometry::CUBE: - return sampleResolution[0] != sampleResolution[1] || - sampleResolution[0] != sampleResolution[2]; + return sampleResolution[0] != sampleResolution[1] || sampleResolution[0] != sampleResolution[2]; default: return false; } @@ -84,8 +83,7 @@ int to_mfem_quadrature_type(axom::numerics::QuadratureType quadratureType) return mfem::Quadrature1D::ClosedGL; } - SLIC_ERROR( - axom::fmt::format("Invalid quadrature type {}.", static_cast(quadratureType))); + SLIC_ERROR(axom::fmt::format("Invalid quadrature type {}.", static_cast(quadratureType))); return mfem::Quadrature1D::Invalid; } @@ -261,10 +259,9 @@ mfem::QuadratureSpace* makeDefaultQuadratureSpace(mfem::Mesh* mesh, int sampleRe return new mfem::QuadratureSpace(mesh, sampleOrder); } -mfem::QuadratureSpace* makeCustomQuadratureSpace( - mfem::Mesh* mesh, - axom::ArrayView sampleRes, - axom::numerics::QuadratureType quadratureType) +mfem::QuadratureSpace* makeCustomQuadratureSpace(mfem::Mesh* mesh, + axom::ArrayView sampleRes, + axom::numerics::QuadratureType quadratureType) { SLIC_ASSERT(mesh != nullptr); const int NE = mesh->GetNE(); @@ -283,10 +280,7 @@ mfem::QuadratureSpace* makeCustomQuadratureSpace( for(int d = 0; d < dim; d++) { SLIC_ERROR_IF(sampleRes[d] < 1, - axom::fmt::format( - "Invalid sample value {} for dimension {}.", - sampleRes[d], - d)); + axom::fmt::format("Invalid sample value {} for dimension {}.", sampleRes[d], d)); switch(quadratureType) { case axom::numerics::QuadratureType::GaussLegendre: @@ -309,9 +303,7 @@ mfem::QuadratureSpace* makeCustomQuadratureSpace( break; case axom::numerics::QuadratureType::Invalid: default: - SLIC_ERROR(axom::fmt::format( - "Invalid quadrature type {}.", - static_cast(quadratureType))); + SLIC_ERROR(axom::fmt::format("Invalid quadrature type {}.", static_cast(quadratureType))); break; } } @@ -348,10 +340,7 @@ void assembleVolumeFractionRHS(const mfem::FiniteElementSpace& fes, const int NE = fes.GetNE(); for(int elem = 0; elem < NE; ++elem) { - rhs.AssembleRHSElementVect( - *fes.GetFE(elem), - *fes.GetElementTransformation(elem), - elemVec); + rhs.AssembleRHSElementVect(*fes.GetFE(elem), *fes.GetElementTransformation(elem), elemVec); fes.GetElementVDofs(elem, elemVDofs); b.AddElementVector(elemVDofs, elemVec); } @@ -404,8 +393,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, if(!usesAnisotropicCustomTensorQuadrature(*mesh, sampleResolution, quadratureType)) { - const auto* geomFactors = - mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); + const auto* geomFactors = mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); geomFactors->X.HostRead(); for(int i = 0; i < NE; ++i) @@ -492,11 +480,7 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, case 2: return axom::fmt::format(" ({} * {})", sampleRes[0], sampleRes[1]); case 3: - return axom::fmt::format( - " ({} * {} * {})", - sampleRes[0], - sampleRes[1], - sampleRes[2]); + return axom::fmt::format(" ({} * {} * {})", sampleRes[0], sampleRes[1], sampleRes[2]); default: return std::string(); } @@ -510,18 +494,12 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, sampleOrder, sampleSZ)); - SLIC_INFO_ROOT(axom::fmt::format(axom::utilities::locale(), - "Mesh has dim {} and {:L} elements", - dim, - NE)); + SLIC_INFO_ROOT( + axom::fmt::format(axom::utilities::locale(), "Mesh has dim {} and {:L} elements", dim, NE)); const auto vf_name = axom::fmt::format("vol_frac_{}", matField.substr(10)); - mfem::GridFunction* vf = getOrAllocateL2GridFunction( - dc, - vf_name, - volfracOrder, - dim, - mfem::BasisType::Positive); + mfem::GridFunction* vf = + getOrAllocateL2GridFunction(dc, vf_name, volfracOrder, dim, mfem::BasisType::Positive); const mfem::FiniteElementSpace* fes = vf->FESpace(); const int dofs = fes->GetTypicalFE()->GetDof(); @@ -543,10 +521,7 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, mfem::ConstantCoefficient one_coef(1.0); mfem::MassIntegrator mass_integrator(one_coef, &sampleIR); - if(usesAnisotropicCustomTensorQuadrature( - *fes->GetMesh(), - sampleResolution, - quadratureType)) + if(usesAnisotropicCustomTensorQuadrature(*fes->GetMesh(), sampleResolution, quadratureType)) { mfem::DenseMatrix elemMat; mass_mat->HostWrite(); @@ -581,8 +556,7 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, mfem::Array* mass_mat_pivots {nullptr}; const std::string minv_name = "shaping_mass_matrix_inv"; const std::string pivots_name = "shaping_mass_matrix_pivots"; - if(mfemState.m_inoutTensors.Has(minv_name) && - mfemState.m_inoutArrays.Has(pivots_name)) + if(mfemState.m_inoutTensors.Has(minv_name) && mfemState.m_inoutArrays.Has(pivots_name)) { mass_mat_inv = mfemState.m_inoutTensors.Get(minv_name); mass_mat_pivots = mfemState.m_inoutArrays.Get(pivots_name); @@ -614,10 +588,7 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, shaping_scratch_buffer = new mfem::DenseTensor(dofs, dofs, NE); shaping_scratch_buffer->HostWrite(); (*shaping_scratch_buffer) = 0.; - mfemState.m_inoutTensors.Register( - scratch_buffer_name, - shaping_scratch_buffer, - true); + mfemState.m_inoutTensors.Register(scratch_buffer_name, shaping_scratch_buffer, true); } axom::utilities::Timer timer(true); @@ -636,10 +607,7 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, *fes, *inout, sampleIR, - usesAnisotropicCustomTensorQuadrature( - *fes->GetMesh(), - sampleResolution, - quadratureType), + usesAnisotropicCustomTensorQuadrature(*fes->GetMesh(), sampleResolution, quadratureType), b); } inout->HostReadWrite(); @@ -664,18 +632,11 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, auto m_d = mfem::Reshape(mass_mat->HostReadWrite(), dofs, dofs, NE); auto b_d = mfem::Reshape(b.HostReadWrite(), dofs, NE); auto vf_d = mfem::Reshape(vf->HostReadWrite(), dofs, NE); - auto fct_mat_d = - mfem::Reshape(shaping_scratch_buffer->HostReadWrite(), dofs, dofs, NE); + auto fct_mat_d = mfem::Reshape(shaping_scratch_buffer->HostReadWrite(), dofs, dofs, NE); AXOM_ANNOTATE_BEGIN("fct project"); axom::for_all(0, NE, [=](int i) { - FCT_correct(&m_d(0, 0, i), - dofs, - &b_d(0, i), - minY, - maxY, - &vf_d(0, i), - &fct_mat_d(0, 0, i)); + FCT_correct(&m_d(0, 0, i), dofs, &b_d(0, i), minY, maxY, &vf_d(0, i), &fct_mat_d(0, 0, i)); }); AXOM_ANNOTATE_END("fct project"); } @@ -755,14 +716,12 @@ void FCT_correct(const double* M, const double y_avg = sum_m / sum_ML; -#ifdef AXOM_DEBUG + #ifdef AXOM_DEBUG constexpr double EPS = 1e-12; - SLIC_WARNING_IF(!(y_min < y_avg + EPS && y_avg < y_max + EPS), - axom::fmt::format("Average ({}) is out of bounds [{},{}]: ", - y_avg, - y_min - EPS, - y_max + EPS)); -#endif + SLIC_WARNING_IF( + !(y_min < y_avg + EPS && y_avg < y_max + EPS), + axom::fmt::format("Average ({}) is out of bounds [{},{}]: ", y_avg, y_min - EPS, y_max + EPS)); + #endif double sum_beta = 0.; for(int i = 0; i < s; ++i) @@ -837,15 +796,15 @@ void FCT_correct(const double* M, { double fij = fct_mat[i + j * s]; - const double aij = fij >= 0.0 ? axom::utilities::min(gp[i], gm[j]) - : axom::utilities::min(gm[i], gp[j]); + const double aij = + fij >= 0.0 ? axom::utilities::min(gp[i], gm[j]) : axom::utilities::min(gm[i], gp[j]); fij *= aij; xy[i] += fij / ML[i]; xy[j] -= fij / ML[j]; } } -#ifdef AXOM_DEBUG + #ifdef AXOM_DEBUG for(int i = 0; i < s; ++i) { SLIC_WARNING_IF(!(y_min < xy[i] + EPS && xy[i] < y_max + EPS), @@ -855,7 +814,7 @@ void FCT_correct(const double* M, y_min - EPS, y_max + EPS)); } -#endif + #endif } void computeVolumeFractionsIdentity(mfem::DataCollection* dc, @@ -868,8 +827,7 @@ void computeVolumeFractionsIdentity(mfem::DataCollection* dc, const int dim = mesh->Dimension(); const int NE = mesh->GetNE(); - std::cout << axom::fmt::format("Mesh has dim {} and {} elements", dim, NE) - << std::endl; + std::cout << axom::fmt::format("Mesh has dim {} and {} elements", dim, NE) << std::endl; auto* fec = new mfem::L2_FECollection(order, dim, mfem::BasisType::Positive); auto* fes = new mfem::FiniteElementSpace(mesh, fec); diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp index 07fd41a875..b998aa6283 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp @@ -11,14 +11,14 @@ #if defined(AXOM_USE_MFEM) -#include "axom/fmt.hpp" + #include "axom/fmt.hpp" -#include "mfem.hpp" -#include "mfem/linalg/dtensor.hpp" + #include "mfem.hpp" + #include "mfem/linalg/dtensor.hpp" -#include -#include -#include + #include + #include + #include namespace axom { @@ -88,8 +88,7 @@ struct SamplingMFEMState : public MFEMState { auto* positions = m_inoutShapeQFuncs.Get("positions"); SLIC_ERROR_IF(positions == nullptr, - std::string("Cannot create material function '") + name + - "' without positions."); + std::string("Cannot create material function '") + name + "' without positions."); auto* qfunc = new mfem::QuadratureFunction(positions->GetSpace(), 1); qfunc->HostWrite(); @@ -238,20 +237,15 @@ void computeVolumeFractionsBaseline(const std::string& shapeName, } const auto volFracName = axom::fmt::format("vol_frac_{}", shapeName); - mfem::GridFunction* volFrac = shaping::getOrAllocateL2GridFunction( - dc, - volFracName, - outputOrder, - dim, - mfem::BasisType::Positive); + mfem::GridFunction* volFrac = + shaping::getOrAllocateL2GridFunction(dc, volFracName, outputOrder, dim, mfem::BasisType::Positive); const mfem::FiniteElementSpace* fes = volFrac->FESpace(); auto* fe = fes->GetFE(0); auto& ir = fe->GetNodes(); const int nq = ir.GetNPoints(); - const auto* geomFactors = - mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); + const auto* geomFactors = mesh->GetGeometricFactors(ir, mfem::GeometricFactors::COORDINATES); mfem::DenseTensor pos_coef(dim, nq, NE); for(int i = 0; i < NE; ++i) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 402c9625ac..e14cef0643 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -185,7 +185,7 @@ struct Input } // Handle conversion to parallel mfem mesh -#if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) + #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) { int* partitioning = nullptr; int part_method = 0; @@ -194,7 +194,7 @@ struct Input delete mesh; mesh = pmesh; } -#endif + #endif return mesh; } @@ -217,10 +217,11 @@ struct Input auto res = axom::NumericArray(boxResolution.data()); auto bbox = BBox2D(Pt2D(boxMins.data()), Pt2D(boxMaxs.data())); - SLIC_INFO_ROOT(axom::fmt::format("Creating inline Blueprint box mesh of resolution {} and " - "bounding box {}", - res, - bbox)); + SLIC_INFO_ROOT( + axom::fmt::format("Creating inline Blueprint box mesh of resolution {} and " + "bounding box {}", + res, + bbox)); if(blueprintTopologyType == BlueprintTopologyType::Structured) { @@ -228,12 +229,7 @@ struct Input } else { - quest::util::make_unstructured_blueprint_box_mesh_2d(meshGrp, - bbox, - res, - "mesh", - "coords", - policy); + quest::util::make_unstructured_blueprint_box_mesh_2d(meshGrp, bbox, res, "mesh", "coords", policy); } } break; @@ -244,10 +240,11 @@ struct Input auto res = axom::NumericArray(boxResolution.data()); auto bbox = BBox3D(Pt3D(boxMins.data()), Pt3D(boxMaxs.data())); - SLIC_INFO_ROOT(axom::fmt::format("Creating inline Blueprint box mesh of resolution {} and " - "bounding box {}", - res, - bbox)); + SLIC_INFO_ROOT( + axom::fmt::format("Creating inline Blueprint box mesh of resolution {} and " + "bounding box {}", + res, + bbox)); if(blueprintTopologyType == BlueprintTopologyType::Structured) { @@ -255,12 +252,7 @@ struct Input } else { - quest::util::make_unstructured_blueprint_box_mesh_3d(meshGrp, - bbox, - res, - "mesh", - "coords", - policy); + quest::util::make_unstructured_blueprint_box_mesh_3d(meshGrp, bbox, res, "mesh", "coords", policy); } } break; @@ -273,7 +265,7 @@ struct Input } #endif - #if defined(AXOM_USE_MFEM) +#if defined(AXOM_USE_MFEM) std::unique_ptr loadComputationalMesh() { constexpr bool dc_owns_data = true; @@ -291,7 +283,7 @@ struct Input return dc; } - #endif +#endif std::string getDCMeshName() const { @@ -360,11 +352,12 @@ 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. \n" - "Alternatively, use the `inline_mesh` or `inline_mesh_blueprint` subcommands.") - ->check(axom::CLI::ExistingFile); + auto* mesh_file = + app.add_option("-m,--mesh-file", meshFile) + ->description( + "Path to computational mesh. \n" + "Alternatively, use the `inline_mesh` or `inline_mesh_blueprint` subcommands.") + ->check(axom::CLI::ExistingFile); auto* inline_mesh_subcommand = app.add_subcommand("inline_mesh") ->description("Options for setting up a simple inline mesh") @@ -399,7 +392,8 @@ struct Input app.add_subcommand("inline_mesh_blueprint") ->description("Options for setting up a simple inline Blueprint mesh") ->fallthrough(); - inline_mesh_blueprint_subcommand->callback([this]() { inlineMeshKind = InlineMeshKind::Blueprint; }); + inline_mesh_blueprint_subcommand->callback( + [this]() { inlineMeshKind = InlineMeshKind::Blueprint; }); inline_mesh_blueprint_subcommand->add_option("--min", boxMins) ->description("Min bounds for box mesh (x,y[,z])") @@ -528,14 +522,14 @@ void printMeshInfo(mfem::Mesh* mesh, const std::string& prefixMessage = "") namespace primal = axom::primal; int myRank = 0; -#ifdef AXOM_USE_MPI + #ifdef AXOM_USE_MPI MPI_Comm_rank(MPI_COMM_WORLD, &myRank); -#endif + #endif int numElements = mesh->GetNE(); mfem::Vector mins, maxs; -#ifdef MFEM_USE_MPI + #ifdef MFEM_USE_MPI auto* pmesh = dynamic_cast(mesh); if(pmesh != nullptr) { @@ -544,7 +538,7 @@ void printMeshInfo(mfem::Mesh* mesh, const std::string& prefixMessage = "") myRank = pmesh->GetMyRank(); } else -#endif + #endif { mesh->GetBoundingBox(mins, maxs); } @@ -733,7 +727,8 @@ int main(int argc, char** argv) shapingDC.SetMesh(shapingMesh); printMeshInfo(shapingMesh, "After loading"); #else - SLIC_ERROR_ROOT("MFEM-backed meshes in shaping_driver require Axom to be configured with MFEM."); + SLIC_ERROR_ROOT( + "MFEM-backed meshes in shaping_driver require Axom to be configured with MFEM."); #endif } AXOM_ANNOTATE_END("load mesh"); @@ -857,7 +852,7 @@ int main(int argc, char** argv) } else #endif - if(params.usesInlineBlueprintMesh()) + if(params.usesInlineBlueprintMesh()) { meshDim = params.boxDim; } @@ -891,8 +886,9 @@ int main(int argc, char** argv) AXOM_ANNOTATE_SCOPE("import initial volume fractions"); if(params.usesInlineBlueprintMesh()) { - SLIC_ERROR_IF(!params.backgroundMaterial.empty(), - "Background material import is not yet supported for inline Blueprint sampling meshes."); + SLIC_ERROR_IF( + !params.backgroundMaterial.empty(), + "Background material import is not yet supported for inline Blueprint sampling meshes."); } else { @@ -992,7 +988,8 @@ int main(int argc, char** argv) using axom::utilities::string::startsWith; if(params.usesInlineBlueprintMesh()) { - SLIC_INFO("Volume summaries are not yet implemented for Blueprint-backed shaping in this driver."); + SLIC_INFO( + "Volume summaries are not yet implemented for Blueprint-backed shaping in this driver."); } #if defined(AXOM_USE_MFEM) else if(shaper->getDC() != nullptr) diff --git a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp index dfd867396b..50dc3a3b8f 100644 --- a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp +++ b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp @@ -96,8 +96,9 @@ void setNodeValues(conduit::Node& node, axom::ArrayView void runSamplingShaper(BlueprintSamplingShaperForTest& shaper, const axom::klee::ShapeSet& shapeSet) { auto getShapeDim = [](const auto& shape) { - static std::map formatDim {{"c2c", axom::klee::Dimensions::Two}, - {"stl", axom::klee::Dimensions::Three}}; + static std::map formatDim { + {"c2c", axom::klee::Dimensions::Two}, + {"stl", axom::klee::Dimensions::Three}}; const auto& shapeDim = shape.getGeometry().getInputDimensions(); const auto& formatStr = shape.getGeometry().getFormat(); @@ -118,11 +119,12 @@ void runSamplingShaper(BlueprintSamplingShaperForTest& shaper, const axom::klee: } double computeStructuredMaterialMeasure(const conduit::Node& mesh, - const std::string& vfFieldName, - double cellMeasure) + const std::string& vfFieldName, + double cellMeasure) { namespace utils = axom::bump::utilities; - const auto values = utils::make_array_view(mesh.fetch_existing("fields").fetch_existing(vfFieldName).fetch_existing("values")); + const auto values = utils::make_array_view( + mesh.fetch_existing("fields").fetch_existing(vfFieldName).fetch_existing("values")); double total = 0.; for(axom::IndexType i = 0; i < values.size(); ++i) { @@ -216,11 +218,12 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) conduit::Node mesh = makeQuadMesh(); int sampleResolution[] = {2, 3}; - axom::quest::shaping::generateQuadraturePointMesh(mesh, - "mesh", - axom::execution_space::allocatorID(), - axom::ArrayView{sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); + axom::quest::shaping::generateQuadraturePointMesh( + mesh, + "mesh", + axom::execution_space::allocatorID(), + axom::ArrayView {sampleResolution, 2}, + axom::numerics::QuadratureType::ClosedUniform); conduit::Node info; EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); @@ -233,10 +236,10 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) namespace utils = axom::bump::utilities; const auto connView = utils::make_array_view( mesh["topologies/quadrature_points/elements/connectivity"]); - const auto sizesView = utils::make_array_view( - mesh["topologies/quadrature_points/elements/sizes"]); - const auto offsetsView = utils::make_array_view( - mesh["topologies/quadrature_points/elements/offsets"]); + const auto sizesView = + utils::make_array_view(mesh["topologies/quadrature_points/elements/sizes"]); + const auto offsetsView = + utils::make_array_view(mesh["topologies/quadrature_points/elements/offsets"]); const auto originalElementsView = utils::make_array_view(mesh["fields/originalElements/values"]); const auto quadratureWeightsView = @@ -250,10 +253,12 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) const axom::Array expectedSizes {{1, 1, 1, 1, 1, 1}}; const axom::Array expectedOffsets {{0, 1, 2, 3, 4, 5}}; const axom::Array expectedOriginalElements {{0, 0, 0, 0, 0, 0}}; - const axom::Array expectedWeights {{1. / 12., 1. / 12., 1. / 3., 1. / 3., 1. / 12., 1. / 12.}}; + const axom::Array expectedWeights { + {1. / 12., 1. / 12., 1. / 3., 1. / 3., 1. / 12., 1. / 12.}}; axom::bump::views::dispatch_explicit_coordset( - mesh["coordsets/quadrature_points"], [&](auto coordsetView) { + mesh["coordsets/quadrature_points"], + [&](auto coordsetView) { for(axom::IndexType i = 0; i < expectedX.size(); ++i) { EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-12); @@ -276,11 +281,12 @@ TEST(quest_blueprint_quadrature_mesh, generate_open_uniform_hex_mesh) conduit::Node mesh = makeHexMesh(); int sampleResolution[3] = {2, 1, 2}; - axom::quest::shaping::generateQuadraturePointMesh(mesh, - "mesh", - axom::execution_space::allocatorID(), - axom::ArrayView{sampleResolution, 3}, - axom::numerics::QuadratureType::OpenUniform); + axom::quest::shaping::generateQuadraturePointMesh( + mesh, + "mesh", + axom::execution_space::allocatorID(), + axom::ArrayView {sampleResolution, 3}, + axom::numerics::QuadratureType::OpenUniform); conduit::Node info; EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); @@ -305,7 +311,8 @@ TEST(quest_blueprint_quadrature_mesh, generate_open_uniform_hex_mesh) const axom::Array expectedWeights {{0.25, 0.25, 0.25, 0.25}}; axom::bump::views::dispatch_explicit_coordset( - mesh["coordsets/quadrature_points"], [&](auto coordsetView) { + mesh["coordsets/quadrature_points"], + [&](auto coordsetView) { for(axom::IndexType i = 0; i < expectedX.size(); ++i) { EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-6); @@ -326,11 +333,12 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_structured_quad_me conduit::Node mesh = makeStructuredQuadMesh(); int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateQuadraturePointMesh(mesh, - "mesh", - axom::execution_space::allocatorID(), - axom::ArrayView{sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); + axom::quest::shaping::generateQuadraturePointMesh( + mesh, + "mesh", + axom::execution_space::allocatorID(), + axom::ArrayView {sampleResolution, 2}, + axom::numerics::QuadratureType::ClosedUniform); conduit::Node info; EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); @@ -347,7 +355,8 @@ TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_structured_quad_me const axom::Array expectedOriginalElements {{0, 0, 0, 0}}; axom::bump::views::dispatch_explicit_coordset( - mesh["coordsets/quadrature_points"], [&](auto coordsetView) { + mesh["coordsets/quadrature_points"], + [&](auto coordsetView) { for(axom::IndexType i = 0; i < expectedX.size(); ++i) { EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-12); @@ -363,23 +372,21 @@ TEST(quest_blueprint_quadrature_mesh, mapped_zone_helper_computes_distorted_quad double lowerFactor = -1.; double upperFactor = -1.; - axom::bump::views::dispatch_explicit_coordset( - mesh["coordsets/coords"], [&](auto coordsetView) { - axom::bump::views::dispatch_unstructured_topology( - mesh["topologies/mesh"], [&](const auto&, auto topoView) { - const auto zone = topoView.zone(0); - lowerFactor = - axom::quest::shaping::detail::computePhysicalMeasureFactor(zone, - coordsetView, - 1. / 3., - 1. / 3.); - upperFactor = - axom::quest::shaping::detail::computePhysicalMeasureFactor(zone, - coordsetView, - 1. / 3., - 2. / 3.); - }); - }); + axom::bump::views::dispatch_explicit_coordset(mesh["coordsets/coords"], [&](auto coordsetView) { + axom::bump::views::dispatch_unstructured_topology( + mesh["topologies/mesh"], + [&](const auto&, auto topoView) { + const auto zone = topoView.zone(0); + lowerFactor = axom::quest::shaping::detail::computePhysicalMeasureFactor(zone, + coordsetView, + 1. / 3., + 1. / 3.); + upperFactor = axom::quest::shaping::detail::computePhysicalMeasureFactor(zone, + coordsetView, + 1. / 3., + 2. / 3.); + }); + }); EXPECT_NEAR(lowerFactor, 5. / 3., 1e-12); EXPECT_NEAR(upperFactor, 4. / 3., 1e-12); @@ -395,20 +402,17 @@ TEST(quest_blueprint_quadrature_mesh, state_wrapper_generation_is_idempotent) bpState.m_internal_node = mesh; int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateSamplingPositions( - bpState, - axom::ArrayView{sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); + axom::quest::shaping::generateSamplingPositions(bpState, + axom::ArrayView {sampleResolution, 2}, + axom::numerics::QuadratureType::ClosedUniform); ASSERT_TRUE(bpState.m_internal_node.has_path("fields/originalElements/values")); conduit::Node savedOriginalElements; - savedOriginalElements.set_external( - bpState.m_internal_node["fields/originalElements/values"]); + savedOriginalElements.set_external(bpState.m_internal_node["fields/originalElements/values"]); - axom::quest::shaping::generateSamplingPositions( - bpState, - axom::ArrayView{sampleResolution, 2}, - axom::numerics::QuadratureType::OpenUniform); + axom::quest::shaping::generateSamplingPositions(bpState, + axom::ArrayView {sampleResolution, 2}, + axom::numerics::QuadratureType::OpenUniform); EXPECT_TRUE(bpState.m_internal_node.has_path("topologies/quadrature_points")); @@ -431,10 +435,9 @@ TEST(quest_blueprint_quadrature_mesh, blueprint_state_field_helpers_support_repl bpState.m_internal_node = mesh; int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateSamplingPositions( - bpState, - axom::ArrayView{sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); + axom::quest::shaping::generateSamplingPositions(bpState, + axom::ArrayView {sampleResolution, 2}, + axom::numerics::QuadratureType::ClosedUniform); conduit::Node& shapeField = bpState.m_internal_node["fields/inout_shape"]; shapeField["association"] = "element"; @@ -478,10 +481,9 @@ TEST(quest_blueprint_quadrature_mesh, compute_volume_fractions_for_material_from bpState.m_internal_node = mesh; int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateSamplingPositions( - bpState, - axom::ArrayView{sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); + axom::quest::shaping::generateSamplingPositions(bpState, + axom::ArrayView {sampleResolution, 2}, + axom::numerics::QuadratureType::ClosedUniform); conduit::Node* materialField = bpState.createMaterialFunction("mat_inout_test"); ASSERT_NE(materialField, nullptr); @@ -511,10 +513,9 @@ TEST(quest_blueprint_quadrature_mesh, bpState.m_internal_node = mesh; int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateSamplingPositions( - bpState, - axom::ArrayView{sampleResolution, 2}, - axom::numerics::QuadratureType::OpenUniform); + axom::quest::shaping::generateSamplingPositions(bpState, + axom::ArrayView {sampleResolution, 2}, + axom::numerics::QuadratureType::OpenUniform); conduit::Node* materialField = bpState.createMaterialFunction("mat_inout_test"); ASSERT_NE(materialField, nullptr); @@ -623,7 +624,11 @@ TEST(quest_blueprint_quadrature_mesh, blueprint_shapers_support_nondefault_topol std::string whyBad; EXPECT_TRUE(samplingShaper.verifyInputMesh(whyBad)) << whyBad; - BlueprintIntersectionShaperForTest intersectionShaper(policy, allocatorId, shapeSet, mesh, "cells"); + BlueprintIntersectionShaperForTest intersectionShaper(policy, + allocatorId, + shapeSet, + mesh, + "cells"); whyBad.clear(); EXPECT_TRUE(intersectionShaper.verifyInputMesh(whyBad)) << whyBad; EXPECT_EQ(intersectionShaper.blueprintMeshDimension(), 2); @@ -641,7 +646,12 @@ TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_quad_blu const axom::primal::BoundingBox bbox {{-2., -2.}, {2., 2.}}; const axom::NumericArray resolution {64, 64}; - axom::quest::util::make_structured_blueprint_box_mesh_2d(meshGroup, bbox, resolution, "mesh", "coords", policy); + axom::quest::util::make_structured_blueprint_box_mesh_2d(meshGroup, + bbox, + resolution, + "mesh", + "coords", + policy); axom::utilities::filesystem::TempFile contourFile(testname, ".contour"); contourFile.write(unit_circle_contour); @@ -691,7 +701,12 @@ TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_hex_blue const axom::primal::BoundingBox bbox {{-2., -2., -2.}, {2., 2., 2.}}; const axom::NumericArray resolution {8, 8, 8}; - axom::quest::util::make_structured_blueprint_box_mesh_3d(meshGroup, bbox, resolution, "mesh", "coords", policy); + axom::quest::util::make_structured_blueprint_box_mesh_3d(meshGroup, + bbox, + resolution, + "mesh", + "coords", + policy); const std::string tetPath = axom::fmt::format("{}/quest/tetrahedron.stl", AXOM_DATA_DIR); const std::string shapeYaml = axom::fmt::format(R"( diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index fb791bce5d..5f4b65adb3 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2599,7 +2599,7 @@ TEST_F(CurvedSampleTester2D, generate_sampling_positions_is_idempotent) int sampleRes[] = {3, 2}; quest::shaping::generateSamplingPositions(mfemState, - axom::ArrayView{sampleRes, 2}, + axom::ArrayView {sampleRes, 2}, axom::numerics::QuadratureType::OpenUniform); auto* positions = mfemState.m_inoutShapeQFuncs.Get("positions"); @@ -2609,7 +2609,7 @@ TEST_F(CurvedSampleTester2D, generate_sampling_positions_is_idempotent) const int initialNumPoints = qspace->GetElementIntRule(0).GetNPoints(); quest::shaping::generateSamplingPositions(mfemState, - axom::ArrayView{sampleRes, 2}, + axom::ArrayView {sampleRes, 2}, axom::numerics::QuadratureType::ClosedUniform); EXPECT_EQ(mfemState.m_inoutShapeQFuncs.Get("positions"), positions); diff --git a/src/axom/quest/util/mesh_helpers.cpp b/src/axom/quest/util/mesh_helpers.cpp index 6f0d73b390..b234bb0420 100644 --- a/src/axom/quest/util/mesh_helpers.cpp +++ b/src/axom/quest/util/mesh_helpers.cpp @@ -343,9 +343,9 @@ void convert_blueprint_structured_explicit_to_unstructured_3d_impl(axom::sidre:: axom::sidre::View* ugTopoTypeView = ugTopoGrp == topoGrp ? ugTopoGrp->getView("type") : ugTopoGrp->createView("type"); ugTopoTypeView->setString("unstructured"); - axom::sidre::View* shapeView = - ugTopoGrp->hasView("elements/shape") ? ugTopoGrp->getView("elements/shape") - : ugTopoGrp->createView("elements/shape"); + axom::sidre::View* shapeView = ugTopoGrp->hasView("elements/shape") + ? ugTopoGrp->getView("elements/shape") + : ugTopoGrp->createView("elements/shape"); SLIC_ASSERT(shapeView != nullptr); shapeView->setString("hex"); @@ -469,9 +469,9 @@ void convert_blueprint_structured_explicit_to_unstructured_2d_impl(axom::sidre:: axom::sidre::View* topoTypeView = topoGrp->getView("type"); SLIC_ASSERT(std::string(topoTypeView->getString()) == "structured"); topoTypeView->setString("unstructured"); - axom::sidre::View* shapeView = - topoGrp->hasView("elements/shape") ? topoGrp->getView("elements/shape") - : topoGrp->createView("elements/shape"); + axom::sidre::View* shapeView = topoGrp->hasView("elements/shape") + ? topoGrp->getView("elements/shape") + : topoGrp->createView("elements/shape"); SLIC_ASSERT(shapeView != nullptr); shapeView->setString("quad"); From c2c250ee6471ec6ca0a9f6b1907e5f7ac8f99d2d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 22 May 2026 11:23:35 -0700 Subject: [PATCH 19/72] Set volfracOrder to 1 for Blueprint --- src/axom/quest/SamplingShaper.cpp | 17 ++++++++++++++--- src/axom/quest/SamplingShaper.hpp | 24 ++++++++++++++++-------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 84e37660d7..9fe908bc18 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -15,10 +15,9 @@ void SamplingShaper::setQuadratureType(axom::numerics::QuadratureType qtype) { if(m_bp_state != nullptr) { - // For Blueprint, we rely on Axom quadrature types and not all are implementd yet. - if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) + // For Blueprint, we rely on Axom quadrature types and not all are implemented yet. + if(axom::numerics::is_supported_quadrature_type(qtype)) { - std::cout << "Setting m_quadratureType = " << static_cast(qtype) << std::endl; m_quadratureType = qtype; } else @@ -52,6 +51,18 @@ void SamplingShaper::setSamplingResolution(axom::ArrayView sampleRes) } } +void SamplingShaper::setVolumeFractionOrder(int volfracOrder) +{ +#if defined(AXOM_USE_CONDUIT) + if(m_bp_state != nullptr) + { + SLIC_INFO("setVolumeFractionOrder is ignored for Blueprint meshes."); + return; + } +#endif + m_volfracOrder = axom::utilities::max(1, volfracOrder); +} + void SamplingShaper::initializeSamplingResolution() { // Initialize the default number of samples based on the mesh dimension. diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index cb76b4d4e4..ebe4544dc0 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -165,6 +165,7 @@ class SamplingShaper : public Shaper : Shaper(execPolicy, allocatorId, shapeSet, bpMesh, topo) { initializeSamplingResolution(); + m_volfracOrder = 1; } /// Blueprint-compatible constructor @@ -176,6 +177,7 @@ class SamplingShaper : public Shaper : Shaper(execPolicy, allocatorId, shapeSet, bpNode, topo) { initializeSamplingResolution(); + m_volfracOrder = 1; } #endif @@ -231,10 +233,16 @@ class SamplingShaper : public Shaper */ void setSamplingResolution(axom::ArrayView sampleRes); - // Deprecated backward compatibility method + /// Deprecated backward compatibility method [[deprecated]] void setQuadratureOrder(int order) { setSamplingResolution(order); } - void setVolumeFractionOrder(int volfracOrder) { m_volfracOrder = volfracOrder; } + /*! + * \brief Set the order for the output volume fractions. This function has no + * effect for Blueprint meshes. + * + * \param volfracOrder The order for the output volume fractions. + */ + void setVolumeFractionOrder(int volfracOrder); /// Registers a function to project from 2D input points to 2D query points void setPointProjector22(shaping::PointProjector<2, 2> projector) { m_projector22 = projector; } @@ -509,7 +517,7 @@ class SamplingShaper : public Shaper // Handles 2D or 3D shaping for compatible samplers, based on the template and associated parameter template - void runShapeQueryImplSampler(SamplerType* sampler, MeshState& meshState) + void runShapeQueryImplSampler(MeshState& meshState, SamplerType* sampler) { // Sample the InOut field at the mesh quadrature points if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) @@ -588,35 +596,35 @@ class SamplingShaper : public Shaper #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - runShapeQueryImplSampler(sampler, samplingMFEMState()); + runShapeQueryImplSampler(samplingMFEMState(), sampler); return; } #endif #if defined(AXOM_USE_CONDUIT) if(m_bp_state != nullptr) { - runShapeQueryImplSampler(sampler, *m_bp_state); + runShapeQueryImplSampler(*m_bp_state, sampler); return; } #endif SLIC_ERROR("No mesh state is available for SamplingShaper."); } - // Handles 2D or 3D shaping for InOutSampler, based on the template and associated parameter + // Handles 2D or 3D shaping for WindingNumberSampler, based on the template and associated parameter template void runShapeQueryImpl(shaping::WindingNumberSampler* sampler) { #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - runShapeQueryImplSampler(sampler, samplingMFEMState()); + runShapeQueryImplSampler(samplingMFEMState(), sampler); return; } #endif #if defined(AXOM_USE_CONDUIT) if(m_bp_state != nullptr) { - runShapeQueryImplSampler(sampler, *m_bp_state); + runShapeQueryImplSampler(*m_bp_state, sampler); return; } #endif From c8c20fcc50a3cf77f770039ad5a413f5ac2bb312 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 26 May 2026 11:34:15 -0700 Subject: [PATCH 20/72] Small refactor --- src/axom/quest/SamplingShaper.cpp | 4 +-- src/axom/quest/SamplingShaper.hpp | 28 ++----------------- .../detail/shaping/shaping_helpers_mfem.hpp | 25 +++++++++++++++++ 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 9fe908bc18..15b830863b 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -114,7 +114,7 @@ void SamplingShaper::saveQuadraturePoints(const std::string& filename) const #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - auto* positions = shapeQFuncs().Get("positions"); + auto* positions = samplingMFEMState().shapeQFuncs().Get("positions"); if(positions == nullptr) { SLIC_WARNING("No MFEM quadrature positions are available to save."); @@ -394,7 +394,7 @@ void SamplingShaper::importInitialVolumeFractions( } const auto matName = axom::fmt::format("mat_inout_{}", name); - materialQFuncs().Register(matName, matQFunc, true); + samplingMFEMState().materialQFuncs().Register(matName, matQFunc, true); } } #endif diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index ebe4544dc0..2aa8745ceb 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -264,12 +264,12 @@ class SamplingShaper : public Shaper /// Returns a pointer to the quadrature function associated with shape \a name if it exists, else nullptr mfem::QuadratureFunction* getShapeQFunction(const std::string& name) const { - return shapeQFuncs().Get(name); + return samplingMFEMState().shapeQFuncs().Get(name); } /// Returns a pointer to the quadrature function associated with material \a name if it exists, else nullptr mfem::QuadratureFunction* getMaterialQFunction(const std::string& name) const { - return materialQFuncs().Get(name); + return samplingMFEMState().materialQFuncs().Get(name); } #endif protected: @@ -336,30 +336,6 @@ class SamplingShaper : public Shaper SLIC_ASSERT(m_mfem_state != nullptr); return static_cast(*m_mfem_state); } - - shaping::QFunctionCollection& shapeQFuncs() { return samplingMFEMState().m_inoutShapeQFuncs; } - const shaping::QFunctionCollection& shapeQFuncs() const - { - return samplingMFEMState().m_inoutShapeQFuncs; - } - - shaping::QFunctionCollection& materialQFuncs() - { - return samplingMFEMState().m_inoutMaterialQFuncs; - } - const shaping::QFunctionCollection& materialQFuncs() const - { - return samplingMFEMState().m_inoutMaterialQFuncs; - } - - shaping::DenseTensorCollection& tensors() { return samplingMFEMState().m_inoutTensors; } - const shaping::DenseTensorCollection& tensors() const - { - return samplingMFEMState().m_inoutTensors; - } - - shaping::MFEMArrayCollection& arrays() { return samplingMFEMState().m_inoutArrays; } - const shaping::MFEMArrayCollection& arrays() const { return samplingMFEMState().m_inoutArrays; } #endif bool hasValidSampler() const { return !std::holds_alternative(m_sampler); } diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp index b998aa6283..01bcf5356c 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp @@ -97,6 +97,31 @@ struct SamplingMFEMState : public MFEMState return qfunc; } + + QFunctionCollection& shapeQFuncs() { return m_inoutShapeQFuncs; } + const QFunctionCollection& shapeQFuncs() const + { + return m_inoutShapeQFuncs; + } + + QFunctionCollection& materialQFuncs() + { + return m_inoutMaterialQFuncs; + } + const QFunctionCollection& materialQFuncs() const + { + return m_inoutMaterialQFuncs; + } + + DenseTensorCollection& tensors() { return m_inoutTensors; } + const DenseTensorCollection& tensors() const + { + return m_inoutTensors; + } + + MFEMArrayCollection& arrays() { return m_inoutArrays; } + const MFEMArrayCollection& arrays() const { return m_inoutArrays; } + QFunctionCollection m_inoutShapeQFuncs; QFunctionCollection m_inoutMaterialQFuncs; DenseTensorCollection m_inoutTensors; From 14d7e301065e3adabe3cfcd17ffa0b546552397e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 26 May 2026 15:23:32 -0700 Subject: [PATCH 21/72] Restoring / adding comments. --- src/axom/quest/SamplingShaper.hpp | 12 +- .../shaping/shaping_helpers_blueprint.hpp | 91 +++++++++++- .../detail/shaping/shaping_helpers_mfem.hpp | 134 +++++++++++++++++- 3 files changed, 231 insertions(+), 6 deletions(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 2aa8745ceb..26005fe071 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -57,7 +57,7 @@ namespace axom { namespace quest { -/// \brief Concrete class for sample based shaping +/// \brief Concrete class for sample based shaping on MFEM or Blueprint meshes. class SamplingShaper : public Shaper { public: @@ -312,7 +312,7 @@ class SamplingShaper : public Shaper return std::make_unique(); } - /// + /// Finish initializing the MFEM state. void initializeSamplingMFEMState() { // Shaper constructs its MFEM state in the base constructor, so upgrade it @@ -325,12 +325,14 @@ class SamplingShaper : public Shaper m_mfem_state = std::move(samplingState); } + /// Get a reference to the MFEM state as a SamplingMFEMState. shaping::SamplingMFEMState& samplingMFEMState() { SLIC_ASSERT(m_mfem_state != nullptr); return static_cast(*m_mfem_state); } + /// Get a reference to the MFEM state as a SamplingMFEMState. const shaping::SamplingMFEMState& samplingMFEMState() const { SLIC_ASSERT(m_mfem_state != nullptr); @@ -662,6 +664,12 @@ class SamplingShaper : public Shaper SLIC_ERROR("No mesh state is available for SamplingShaper."); } + /*! + * \brief Apply replacement rules using for the supplied shape, adjusting functions in \a meshState. + * + * \param meshState The object that contains the mesh and fields. + * \param shape The shape being considered. + */ template void applyReplacementRulesImpl(MeshState& meshState, const klee::Shape& shape) { diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 604a3400ac..58fda1a8ef 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -27,9 +27,16 @@ namespace quest { namespace shaping { - +/*! + * \brief Return the cell shape for a Blueprint topology. + * + * \param topoNode The Blueprint topology being queried. + * + * \return A string containing the cell shape for the topology. + */ std::string getBlueprintCellShape(const conduit::Node& topoNode); +/// A class that contains Blueprint mesh and field state for SamplingShaper class. struct BlueprintState { virtual ~BlueprintState() = default; @@ -125,31 +132,113 @@ struct BlueprintState } }; +/*! + * \brief Print the registered field names in the \a bpState. + * + * \param bpState The Blueprint state. + * \param knownMaterials A set of known material names. + * \param vfSampling The type of volume fraction sampling being performed. + * \param initialMessage A string to prepend to the printed message. + */ + void printRegisteredFieldNames(const BlueprintState& bpState, const std::set& knownMaterials, VolFracSampling vfSampling, const std::string& initialMessage); +/*! + * Utility function to zero out inout quadrature points for a material replaced by a shape + * + * Each location in space can only be covered by one material. + * When \a shouldReplace is true, we clear all values in \a materialQFunc + * that are set in \a shapeQFunc. When it is false, we do the opposite. + * + * \param shapeNode The node that contains the shape function. + * \param materialNode The node that contains the material function. + * \param shouldReplace Flag for whether the shape replaces the material + * or whether the material remains and we should zero out the shape sample (when false) + */ void replaceMaterial(conduit::Node* shapeNode, conduit::Node* materialNode, bool shouldReplace); +/*! + * \brief Utility function to copy inout quadrature point values from \a shapeNode to \a materialNode + * + * \param shapeNode The inout samples field for the current shape + * \param materialNode The inout samples field for the material we're writing into + * \param reuseExisting When a value is not set in \a shapeNode, should we retain existing values + * from \a materialNode or overwrite them based on \a shapeNode. The default is to retain values + */ void copyShapeIntoMaterial(const conduit::Node* shapeNode, conduit::Node* materialNode, bool reuseExisting = true); +/*! + * \brief Create a copy of the supplied field. + * + * \param node A pointer to the field to clone. + * + * \return A pointer to a new copy of the supplied field. + */ conduit::Node* cloneInOutFunction(const conduit::Node* node); +/*! + * \brief Generate sampling positions within each zone based on element quadrature, creating a new topology. + * + * \param bpMeshNode The node that will contain the new quadrature point mesh topology. + * \param topologyName The name of the new topology to create. + * \param allocatorID The allocator Id to use for allocating memory. + * \param sampleResolution The number of samples in each dimension. + * \param quadratureType The quadrature type that determines the sample locations. + */ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, const std::string& topologyName, int allocatorID, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType); +/*! + * \brief Generates sampling positions within each zone based on element quadrature. + * + * \param bpState The Blueprint state. + * \param sampleResolution The number of samples in each dimension. + * \param quadratureType The quadrature type that determines the sample locations. + * + * \note The sample points are stored as a new quadrature_points topology. + */ void generateSamplingPositions(BlueprintState& bpState, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType); +/*! + * \brief Create volume fractions for a material using the existing material field + * (mat_inout_{matField}) to make the new field (vol_fract_{matField}). + * + * \param bpState The Blueprint state that contains the mesh and functions. + * \param matField The name of the material field. + */ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField); +/*! + * \brief Samples the inout field over the indexed geometry, possibly using a + * callback function to project the input points (from the computational mesh) + * to query points on the spatial index + * + * \tparam FromDim The dimension of points from the input mesh + * \tparam ToDim The dimension of points on the indexed shape + * \tparam InsideFunc A function that takes a point and returns a bool indicating whether the + * point is inside or outside of relevant shapes. + * + * \param [in] shapeName The name of the shape used in making data array names. + * \param [in] mfemState The data collection containing the mesh, associated query points + * and a collection of quadrature functions for the shape and material + * inout samples. + * \param [in] checkInside The function that determines whether a point is inside. + * \param [in] projector A callback function to apply to points from the input mesh + * before querying them on the spatial index + * + * \note A projector callback must be supplied when \a FromDim is not equal + * to \a ToDim. + */ template void sampleInOutField(const std::string& shapeName, shaping::BlueprintState& bpState, diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp index 01bcf5356c..14bbd6adae 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp @@ -33,6 +33,7 @@ using QFunctionCollection = mfem::NamedFieldsMap; using DenseTensorCollection = mfem::NamedFieldsMap; using MFEMArrayCollection = mfem::NamedFieldsMap>; +/// Base class that contains MFEM state for Shaper classes. struct MFEMState { virtual ~MFEMState() = default; @@ -42,6 +43,7 @@ struct MFEMState sidre::MFEMSidreDataCollection* m_dc {nullptr}; }; +/// Derived class that contains additional state for SamplingShaper class. struct SamplingMFEMState : public MFEMState { ~SamplingMFEMState() override @@ -128,36 +130,104 @@ struct SamplingMFEMState : public MFEMState MFEMArrayCollection m_inoutArrays; }; +/*! + * \brief Print the registered field names in the \a mfemState. + * + * \param mfemState The MFEM state. + * \param knownMaterials A set of known material names. + * \param vfSampling The type of volume fraction sampling being performed. + * \param initialMessage A string to prepend to the printed message. + */ void printRegisteredFieldNames(const SamplingMFEMState& mfemState, const std::set& knownMaterials, VolFracSampling vfSampling, const std::string& initialMessage); +/*! + * \brief Utility function to either return a grid function from the DataCollection \a dc, + * or to allocate the grud function through the dc, ensuring the memory doesn't leak + * + * \return A pointer to the (allocated) grid function. nullptr if it cannot be allocated + */ mfem::GridFunction* getOrAllocateL2GridFunction(mfem::DataCollection* dc, const std::string& gf_name, int order, int dim, const int basis); +/*! + * Utility function to zero out inout quadrature points for a material replaced by a shape + * + * Each location in space can only be covered by one material. + * When \a shouldReplace is true, we clear all values in \a materialQFunc + * that are set in \a shapeQFunc. When it is false, we do the opposite. + * + * \param shapeQFunc The inout quadrature function for the shape samples + * \param materialQFunc The inout quadrature function for the material samples + * \param shouldReplace Flag for whether the shape replaces the material + * or whether the material remains and we should zero out the shape sample (when false) + */ void replaceMaterial(mfem::QuadratureFunction* shapeQFunc, mfem::QuadratureFunction* materialQFunc, bool shouldReplace); +/*! + * \brief Utility function to copy inout quadrature point values from \a shapeQFunc to \a materialQFunc + * + * \param shapeQFunc The inout samples for the current shape + * \param materialQFunc The inout samples for the material we're writing into + * \param reuseExisting When a value is not set in \a shapeQFunc, should we retain existing values + * from \a materialQFunc or overwrite them based on \a shapeQFunc. The default is to retain values + */ void copyShapeIntoMaterial(const mfem::QuadratureFunction* shapeQFunc, mfem::QuadratureFunction* materialQFunc, bool reuseExisting = true); +/*! + * \brief Create a copy of the supplied function. + * + * \param qfunc A pointer to the function to clone. + * + * \return A pointer to a new copy of the supplied function. + */ mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfunc); +/*! + * \brief Generates sampling positions within each zone based on element quadrature. + * + * \param mesh The MFEM mesh. + * \param inoutQFuncs A function collection in which to place the position function. + * \param sampleResolution The number of samples in each dimension. + * \param quadratureType The quadrature type that determines the sample locations. + */ void generatePositionsQFunction(mfem::Mesh* mesh, QFunctionCollection& inoutQFuncs, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType); +/*! + * \brief Generates sampling positions within each zone based on element quadrature. + * + * \param bpState The Blueprint state. + * \param sampleResolution The number of samples in each dimension. + * \param quadratureType The quadrature type that determines the sample locations. + * + * \note The sample points are stored as a function corresponding to the mesh positions + */ void generateSamplingPositions(SamplingMFEMState& mfemState, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType); +/*! + * \brief Create volume fractions for a material using the existing material field + * (mat_inout_{matField}) to make the new field (vol_fract_{matField}). + * + * \param mfemState The MFEM state that contains the mesh and functions. + * \param matField The name of the material field. + * \param volfracOrder The order of the volume fraction function to create. + * \param sampleResolution The number of samples in each mesh dimension. + * \param quadratureType The quadrature type that determines the sample point locations. + */ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, const std::string& matField, int volfracOrder, @@ -168,10 +238,42 @@ void computeVolumeFractionsIdentity(mfem::DataCollection* dc, mfem::QuadratureFunction* inout, const std::string& name); +/*! + * \brief Examines the sample resolution and quadrature rule to decide whether the + * requested quadrature is a custom-tensor / anisotropic. Some algorithms for + * MFEM must take special paths in this case. + * + * \param mesh The MFEM mesh. + * \param sampleResolution The number of samples in each mesh dimension. + * \param quadratureType The quadrature type that determines the sample point locations. + * + * \return True if the quadrature is custom / anisotropic; false otherwise. + */ bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType); +/*! + * \brief Samples the inout field over the indexed geometry, possibly using a + * callback function to project the input points (from the computational mesh) + * to query points on the spatial index + * + * \tparam FromDim The dimension of points from the input mesh + * \tparam ToDim The dimension of points on the indexed shape + * \tparam InsideFunc A function that takes a point and returns a bool indicating whether the + * point is inside or outside of relevant shapes. + * + * \param [in] shapeName The name of the shape used in making data array names. + * \param [in] mfemState The data collection containing the mesh, associated query points + * and a collection of quadrature functions for the shape and material + * inout samples. + * \param [in] checkInside The function that determines whether a point is inside. + * \param [in] projector A callback function to apply to points from the input mesh + * before querying them on the spatial index + * + * \note A projector callback must be supplied when \a FromDim is not equal + * to \a ToDim. + */ template void sampleInOutField(const std::string shapeName, shaping::SamplingMFEMState& mfemState, @@ -239,6 +341,26 @@ void sampleInOutField(const std::string shapeName, static_cast(numQueryPoints / timer.elapsed()))); } +/*! + * \brief Called when sampling shapes at dofs. + * + * \tparam FromDim The dimension of points from the input mesh + * \tparam ToDim The dimension of points on the indexed shape + * \tparam InsideFunc A function that takes a point and returns a bool indicating whether the + * point is inside or outside of relevant shapes. + * + * \param [in] shapeName The name of the shape used in making data array names. + * \param [in] mfemState The data collection containing the mesh, associated query points + * and a collection of quadrature functions for the shape and material + * inout samples. + * \param [in] outputOrder The order of the volume fraction function. + * \param [in] checkInside The function that determines whether a point is inside. + * \param [in] projector A callback function to apply to points from the input mesh + * before querying them on the spatial index + * + * \note A projector callback must be supplied when \a FromDim is not equal + * to \a ToDim. + */ template void computeVolumeFractionsBaseline(const std::string& shapeName, shaping::SamplingMFEMState& mfemState, @@ -318,13 +440,19 @@ void computeVolumeFractionsBaseline(const std::string& shapeName, } } +/** + * Implements flux-corrected transport (FCT) to correct the solution obtained + * when converting from inout samples (ones and zeros) to a grid function + * on the degrees of freedom such that the volume fractions are doubles + * between 0 and 1 ( \a y_min and \a y_max ) + */ void FCT_correct(const double* M, const int s, const double* m, - const double y_min, - const double y_max, + const double y_min, // 0 + const double y_max, // 1 double* xy, - double* fct_mat); + double* fct_mat); // scratch buffer } // end namespace shaping } // end namespace quest From 16885a422f965742b877240b3d8357d030a9e889 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 26 May 2026 17:52:01 -0700 Subject: [PATCH 22/72] Support importing Blueprint volume fraction field for void material. --- src/axom/quest/SamplingShaper.cpp | 80 +++++-------------- src/axom/quest/SamplingShaper.hpp | 11 +++ src/axom/quest/Shaper.hpp | 4 + .../shaping/shaping_helpers_blueprint.cpp | 58 ++++++++++++++ .../shaping/shaping_helpers_blueprint.hpp | 15 ++++ .../detail/shaping/shaping_helpers_mfem.cpp | 56 +++++++++++++ .../detail/shaping/shaping_helpers_mfem.hpp | 37 ++++----- src/axom/quest/examples/shaping_driver.cpp | 43 +++++++++- 8 files changed, 225 insertions(+), 79 deletions(-) diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 15b830863b..f85d373ec0 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -326,76 +326,40 @@ void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const kl } #if defined(AXOM_USE_MFEM) -/** - * \brief Import an initial set of material volume fractions before shaping - * - * \param [in] initialGridFuncions The input data as a map from material names to grid functions - * - * The imported grid functions are interpolated at quadrature points and registered - * with the supplied names as material-based quadrature fields - */ +void SamplingShaper::importInitialVolumeFractions( + const std::map& initialVolumeFractions) +{ + internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug + : slic::message::Warning); + SLIC_ERROR_IF(m_bp_state == nullptr, "This method requires Blueprint inputs."); + // Generate the quadrature points. + if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) + { + shaping::generateSamplingPositions(*m_bp_state, m_samplingResolution.view(), m_quadratureType); + } + shaping::importInitialVolumeFractions(*m_bp_state, initialVolumeFractions); +} +#endif + +#if defined(AXOM_USE_MFEM) void SamplingShaper::importInitialVolumeFractions( const std::map& initialGridFunctions) { internal::ScopedLogLevelChanger logLevelChanger(this->isVerbose() ? slic::message::Debug : slic::message::Warning); + SLIC_ERROR_IF(m_mfem_state == nullptr, "This method requires MFEM inputs."); + auto& mfemState = samplingMFEMState(); auto* mesh = mfemState.m_dc->GetMesh(); - // Sample the InOut field at the mesh quadrature points + // Generate the quadrature points. if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) { shaping::generateSamplingPositions(mfemState, m_samplingResolution.view(), m_quadratureType); } - auto* positionsQSpace = mfemState.m_inoutShapeQFuncs.Get("positions")->GetSpace(); - - // Interpolate grid functions at quadrature points & register material quad functions - // assume all elements have same integration rule - for(auto& entry : initialGridFunctions) - { - const auto& name = entry.first; - auto* gf = entry.second; - - SLIC_INFO_ROOT(axom::fmt::format("Importing volume fraction field for '{}' material", name)); - - if(gf == nullptr) - { - SLIC_WARNING( - axom::fmt::format("Skipping missing volume fraction field for material '{}'", name)); - continue; - } - - auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); - const auto& ir = matQFunc->GetSpace()->GetIntRule(0); - - if(shaping::usesAnisotropicCustomTensorQuadrature(*mesh, m_samplingResolution, m_quadratureType)) - { - // Avoid MFEM's tensor quadrature interpolation path only for - // anisotropic custom quad/hex rules. MFEM infers a single q1d from - // ir.GetNPoints(), which cannot represent per-direction sample counts - // such as 3 x 5 or 3 x 5 x 2. - mfem::Vector elemValues; - mfem::Vector qfuncValues; - for(int elem = 0; elem < mesh->GetNE(); ++elem) - { - gf->GetValues(elem, ir, elemValues); - matQFunc->GetValues(elem, qfuncValues); - qfuncValues = elemValues; - } - } - else - { - const auto* interp = gf->FESpace()->GetQuadratureInterpolator(ir); - SLIC_ERROR_IF(interp == nullptr, - axom::fmt::format("Could not create a quadrature interpolator while " - "importing volume fractions for '{}'.", - name)); - interp->Values(*gf, *matQFunc); - } - - const auto matName = axom::fmt::format("mat_inout_{}", name); - samplingMFEMState().materialQFuncs().Register(matName, matQFunc, true); - } + const bool anisotropic = + shaping::usesAnisotropicCustomTensorQuadrature(*mesh, m_samplingResolution, m_quadratureType); + shaping::importInitialVolumeFractions(mfemState, initialGridFunctions, anisotropic); } #endif diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 26005fe071..fe51a58c8a 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -460,6 +460,17 @@ class SamplingShaper : public Shaper ///@} public: +#if defined(AXOM_USE_CONDUIT) + /** + * \brief Import an initial set of material volume fractions before shaping + * + * \param [in] initialVolumeFractions The input data as a map from material names to fields + * + * The imported fields are interpolated at quadrature points and registered + * with the supplied names as material-based quadrature fields + */ + void importInitialVolumeFractions(const std::map& initialVolumeFractions); +#endif #if defined(AXOM_USE_MFEM) /** * \brief Import an initial set of material volume fractions before shaping diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index a1ebf73389..d859ff885c 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -137,6 +137,10 @@ class Shaper #endif #if defined(AXOM_USE_CONDUIT) + conduit::Node* getBlueprintMeshNode() + { + return m_bp_state != nullptr ? &m_bp_state->m_internal_node : nullptr; + } const conduit::Node* getBlueprintMeshNode() const { return m_bp_state != nullptr ? &m_bp_state->m_internal_node : nullptr; diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 027c24ff6c..cc09f8b53c 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -329,6 +329,64 @@ void generateSamplingPositions(BlueprintState& bpState, quadratureType); } +void importInitialVolumeFractions(BlueprintState& bpState, + const std::map& initialVolumeFractions) +{ + conduit::Node& n_mesh = bpState.getBlueprintMeshNode(); + const std::string quadName("quadrature_points"); + const conduit::Node& n_quad_points = n_mesh.fetch_existing("coordsets/" + quadName); + const auto totalQuadPoints = conduit::blueprint::mesh::coordset::length(n_quad_points); + + // Get the topology we want to sample. + const conduit::Node& n_topo = bpState.getBlueprintTopologyNode(); + const auto totalZones = conduit::blueprint::mesh::topology::length(n_topo); + + const auto samplesPerZone = totalQuadPoints / totalZones; + + for(auto& entry : initialVolumeFractions) + { + const auto& name = entry.first; + auto* field_ptr = entry.second; + + SLIC_INFO_ROOT(axom::fmt::format("Importing volume fraction field for '{}' material", name)); + + if(field_ptr == nullptr) + { + SLIC_WARNING( + axom::fmt::format("Skipping missing volume fraction field for material '{}'", name)); + continue; + } + + // Get the source field. + const auto srcPath = axom::fmt::format("fields/vol_frac_{}", name); + conduit::Node& n_src_field = n_mesh.fetch_existing(srcPath); + SLIC_ERROR_IF(n_src_field.fetch_existing("association").as_string() != "element", + "The imported field must have element association."); + const auto src_values = n_src_field["values"].as_double_accessor(); + + // Make the new quadrature field. + const auto destPath = axom::fmt::format("fields/mat_inout_{}", name); + conduit::Node& n_dest_field = n_mesh.fetch(destPath); + n_dest_field["topology"] = quadName; + n_dest_field["association"] = "element"; + conduit::Node& n_dest_values = n_dest_field["values"]; + n_dest_values.set(conduit::DataType::float64(totalQuadPoints)); + double* dptr = n_dest_values.as_double_ptr(); + + // Copy the source field into the dest field. We just copy samplesPerZone values + // from the source into the dest since each block of samplesPerZone points in + // the quadrature mesh corresponds to a zone in the source mesh. + for(conduit::index_t i = 0; i < totalZones; i++) + { + const auto src_value = src_values[i]; + for(conduit::index_t c = 0; c < samplesPerZone; c++) + { + *dptr++ = src_value; + } + } + } +} + void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::string& matField) { AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 58fda1a8ef..4a7c95736e 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -17,6 +17,7 @@ #include "conduit_node.hpp" + #include #include #include #include @@ -64,6 +65,8 @@ struct BlueprintState return -1; } + conduit::Node& getBlueprintMeshNode() { return m_internal_node; } + const conduit::Node& getBlueprintTopologyNode() const { return m_internal_node.fetch_existing("topologies").fetch_existing(m_topology_name); @@ -209,6 +212,18 @@ void generateSamplingPositions(BlueprintState& bpState, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType); +/*! + * \brief Import initial volume fractions from the map into the quadrature + * "mat_inout_" fields in \a bpState. + * + * \param bpState The Blueprint state. + * \param initialVolumeFractions A map of initial volume fraction fields used to + * initialize mat_inout fields over the quadrature + * points. + */ +void importInitialVolumeFractions(BlueprintState& bpState, + const std::map& initialVolumeFractions); + /*! * \brief Create volume fractions for a material using the existing material field * (mat_inout_{matField}) to make the new field (vol_fract_{matField}). diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp index ebb6dfefb2..62dcfc5776 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp @@ -450,6 +450,62 @@ void generateSamplingPositions(SamplingMFEMState& mfemState, quadratureType); } +void importInitialVolumeFractions(SamplingMFEMState& mfemState, + const std::map& initialGridFunctions, + bool anisotropic) +{ + auto* positionsQSpace = mfemState.shapeQFuncs().Get("positions")->GetSpace(); + auto* mesh = mfemState.m_dc->GetMesh(); + + // Interpolate grid functions at quadrature points & register material quad functions + // assume all elements have same integration rule + for(auto& entry : initialGridFunctions) + { + const auto& name = entry.first; + auto* gf = entry.second; + + SLIC_INFO_ROOT(axom::fmt::format("Importing volume fraction field for '{}' material", name)); + + if(gf == nullptr) + { + SLIC_WARNING( + axom::fmt::format("Skipping missing volume fraction field for material '{}'", name)); + continue; + } + + auto* matQFunc = new mfem::QuadratureFunction(*positionsQSpace); + const auto& ir = matQFunc->GetSpace()->GetIntRule(0); + + if(anisotropic) + { + // Avoid MFEM's tensor quadrature interpolation path only for + // anisotropic custom quad/hex rules. MFEM infers a single q1d from + // ir.GetNPoints(), which cannot represent per-direction sample counts + // such as 3 x 5 or 3 x 5 x 2. + mfem::Vector elemValues; + mfem::Vector qfuncValues; + for(int elem = 0; elem < mesh->GetNE(); ++elem) + { + gf->GetValues(elem, ir, elemValues); + matQFunc->GetValues(elem, qfuncValues); + qfuncValues = elemValues; + } + } + else + { + const auto* interp = gf->FESpace()->GetQuadratureInterpolator(ir); + SLIC_ERROR_IF(interp == nullptr, + axom::fmt::format("Could not create a quadrature interpolator while " + "importing volume fractions for '{}'.", + name)); + interp->Values(*gf, *matQFunc); + } + + const auto matName = axom::fmt::format("mat_inout_{}", name); + mfemState.materialQFuncs().Register(matName, matQFunc, true); + } +} + void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, const std::string& matField, int volfracOrder, diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp index 14bbd6adae..1e042817bf 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp @@ -99,27 +99,14 @@ struct SamplingMFEMState : public MFEMState return qfunc; } - QFunctionCollection& shapeQFuncs() { return m_inoutShapeQFuncs; } - const QFunctionCollection& shapeQFuncs() const - { - return m_inoutShapeQFuncs; - } + const QFunctionCollection& shapeQFuncs() const { return m_inoutShapeQFuncs; } - QFunctionCollection& materialQFuncs() - { - return m_inoutMaterialQFuncs; - } - const QFunctionCollection& materialQFuncs() const - { - return m_inoutMaterialQFuncs; - } + QFunctionCollection& materialQFuncs() { return m_inoutMaterialQFuncs; } + const QFunctionCollection& materialQFuncs() const { return m_inoutMaterialQFuncs; } DenseTensorCollection& tensors() { return m_inoutTensors; } - const DenseTensorCollection& tensors() const - { - return m_inoutTensors; - } + const DenseTensorCollection& tensors() const { return m_inoutTensors; } MFEMArrayCollection& arrays() { return m_inoutArrays; } const MFEMArrayCollection& arrays() const { return m_inoutArrays; } @@ -208,7 +195,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, /*! * \brief Generates sampling positions within each zone based on element quadrature. * - * \param bpState The Blueprint state. + * \param mfemState The MFEM state. * \param sampleResolution The number of samples in each dimension. * \param quadratureType The quadrature type that determines the sample locations. * @@ -218,6 +205,20 @@ void generateSamplingPositions(SamplingMFEMState& mfemState, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType); +/*! + * \brief Import initial volume fractions from the map into the quadrature + * "mat_inout_" fields in \a mfemState. + * + * \param mfemState The MFEM state. + * \param initialVolumeFractions A map of initial volume fraction fields used to + * initialize mat_inout fields over the quadrature + * points. + * \param anisotropic Whether the quadrature points are anisotropic. + */ +void importInitialVolumeFractions(SamplingMFEMState& mfemState, + const std::map& initialVolumeFractions, + bool anisotropic); + /*! * \brief Create volume fractions for a material using the existing material field * (mat_inout_{matField}) to make the new field (vol_fract_{matField}). diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index e14cef0643..c21f5d82d5 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -265,6 +265,20 @@ struct Input } #endif + int numberOfBoxMeshElements() const + { + switch(boxDim) + { + case 3: + return boxResolution[0] * boxResolution[1] * boxResolution[2]; + break; + case 2: + return boxResolution[0] * boxResolution[1]; + break; + } + return 0; + } + #if defined(AXOM_USE_MFEM) std::unique_ptr loadComputationalMesh() { @@ -886,9 +900,32 @@ int main(int argc, char** argv) AXOM_ANNOTATE_SCOPE("import initial volume fractions"); if(params.usesInlineBlueprintMesh()) { - SLIC_ERROR_IF( - !params.backgroundMaterial.empty(), - "Background material import is not yet supported for inline Blueprint sampling meshes."); +#if defined(AXOM_USE_CONDUIT) + // 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 auto num_elements = params.numberOfBoxMeshElements(); + conduit::Node* n_mesh = shaper->getBlueprintMeshNode(); + conduit::Node& n_field = n_mesh->fetch("fields/" + name); + n_field["topology"] = "topology"; + n_field["association"] = "element"; + n_field["values"].set(conduit::DataType::float64(num_elements)); + conduit::float64_array values = n_field["values"].value(); + for(conduit::index_t i = 0; i < num_elements; i++) + { + values[i] = 1.; + } + + std::map initial_grid_functions; + initial_grid_functions[material] = &n_field; + + // Project provided volume fraction grid functions as quadrature point data + samplingShaper->importInitialVolumeFractions(initial_grid_functions); + } +#endif } else { From df653839c62b4b2fe437bdb2bbd785dd43669606 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 26 May 2026 18:07:59 -0700 Subject: [PATCH 23/72] Added a conduit-only sampling shaper test that does not require MFEM. --- src/axom/quest/examples/CMakeLists.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 886b41260c..4569b5ec59 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -368,6 +368,22 @@ if(AXOM_HAS_MFEM_WITH_MPI AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) PASS_REGULAR_EXPRESSION "Volume of material 'air' is 182,?227,?963") endif() endif() +# Blueprint-only shaping test +if(CONDUIT_FOUND AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE AND AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) + set(_nranks 1) + set(_testname quest_shaping_driver_ex_sampling_stl_spheres) + axom_add_test(NAME ${_testname} + COMMAND quest_shaping_driver_ex + -i ${shaping_data_dir}/spheres.yaml + --verbose + --method sampling + --sampling inout + --background-material void + inline_mesh_blueprint --min -5 -5 -5 --max 5 5 5 --res 10 10 10 -d 3 + NUM_MPI_TASKS ${_nranks}) + set_tests_properties(${_testname} PROPERTIES + PASS_REGULAR_EXPRESSION "Volume fraction fields: vol_frac_void, vol_frac_steel") +endif() # Distributed closest point example ------------------------------------------- if(AXOM_ENABLE_MPI AND AXOM_ENABLE_SIDRE AND HDF5_FOUND) From 7f0002865f4d16a47be643d5c18a8e9e09ba8322 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 27 May 2026 09:53:06 -0700 Subject: [PATCH 24/72] Added a note to the RELEASE-NOTES.md file. --- RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 1053530907..085b8312af 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -36,6 +36,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ removed in a future version of Axom. - Core: Adds Durand-Kerner polynomial solver which returns the complex roots of a univariate polynomial - Core: Adds `axom::Array::pop_back` for API compatibility with `std::vector` +- Quest: Enhanced `SamplingShaper` so it can operate on Blueprint quad/hex meshes. ### Removed From 830282045a710336f024707e214240a2de79f60b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 27 May 2026 14:53:28 -0700 Subject: [PATCH 25/72] Added axom::bump::ComputeMeasure and used it to print material volumes in shaping driver for Blueprint meshes. --- src/axom/bump/CMakeLists.txt | 1 + src/axom/bump/ComputeMeasure.hpp | 111 +++++++++++++++ src/axom/bump/PrimalAdaptor.hpp | 3 + src/axom/quest/SamplingShaper.cpp | 2 +- src/axom/quest/Shaper.hpp | 4 + src/axom/quest/examples/CMakeLists.txt | 11 +- src/axom/quest/examples/shaping_driver.cpp | 153 ++++++++++++++++++--- 7 files changed, 259 insertions(+), 26 deletions(-) create mode 100644 src/axom/bump/ComputeMeasure.hpp diff --git a/src/axom/bump/CMakeLists.txt b/src/axom/bump/CMakeLists.txt index d897086be7..3748251f63 100644 --- a/src/axom/bump/CMakeLists.txt +++ b/src/axom/bump/CMakeLists.txt @@ -65,6 +65,7 @@ set(bump_headers views/UnstructuredTopologySingleShapeView.hpp views/view_traits.hpp BlendData.hpp + ComputeMeasure.hpp CoordsetBlender.hpp CoordsetExtents.hpp CoordsetSlicer.hpp diff --git a/src/axom/bump/ComputeMeasure.hpp b/src/axom/bump/ComputeMeasure.hpp new file mode 100644 index 0000000000..840585e567 --- /dev/null +++ b/src/axom/bump/ComputeMeasure.hpp @@ -0,0 +1,111 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#ifndef AXOM_BUMP_COMPUTE_MEASURE_HPP_ +#define AXOM_BUMP_COMPUTE_MEASURE_HPP_ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/slic.hpp" + +#include "axom/sidre/core/ConduitMemory.hpp" + +#include + +#include + +namespace axom +{ +namespace bump +{ + +/*! + * \brief This class computes volume for 3D and area for 2D and stores the values in a field. + * + * \tparam ExecSpace The execution space where the compute happens. + * \tparam Adaptor A PrimalAdaptor. + */ +template +class ComputeMeasure +{ +public: + /*! + * \brief Constructor. + * + * \param adaptor The adaptor object to use to compute the measure. + */ + ComputeMeasure(Adaptor &adaptor) : m_adaptor(adaptor), + m_allocator_id(axom::execution_space::allocatorID()) + { + } + + /*! + * \brief Set the allocator id to use when allocating memory. + * + * \param allocator_id The allocator id to use when allocating memory. + */ + void setAllocatorID(int allocator_id) + { + SLIC_ERROR_IF(!axom::isValidAllocatorID(allocator_id), "Invalid allocator id."); + SLIC_ERROR_IF(!axom::execution_space::usesAllocId(allocator_id), + "Allocator id is not compatible with execution space."); + m_allocator_id = allocator_id; + } + + /*! + * \brief Get the allocator id to use when allocating memory. + * + * \return The allocator id to use when allocating memory. + */ + int getAllocatorID() const { return m_allocator_id; } + + /*! + * \brief Compute the area or volume (depending on shape dimension) and store + * it in the field. + * + * \param topoName The topology name for the field. + * \param n_field The node that will contain the new field. + */ + void execute(const std::string &topoName, conduit::Node &n_field) + { + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(getAllocatorID()); + + n_field["topology"] = topoName; + n_field["association"] = "element"; + conduit::Node &n_values = n_field["values"]; + n_values.set_allocator(conduitAllocatorId); + n_values.set(conduit::DataType::float64(m_adaptor.numberOfZones())); + auto valuesView = bump::utilities::make_array_view(n_values); + + // Use the Adaptor on device to compute area or volume. + const Adaptor deviceAdaptor(m_adaptor); + axom::for_all(deviceAdaptor.numberOfZones(), AXOM_LAMBDA(axom::IndexType zoneIndex) + { + const auto shape = deviceAdaptor.getShape(zoneIndex); + + double value = 0.; + if constexpr (Adaptor::dimension() == 3) + { + value = shape.volume(); + } + else if constexpr (Adaptor::dimension() == 2) + { + value = shape.area(); + } + + valuesView[zoneIndex] = value; + }); + } + +private: + Adaptor m_adaptor; + int m_allocator_id; +}; + +} // end namespace bump +} // end namespace axom + +#endif diff --git a/src/axom/bump/PrimalAdaptor.hpp b/src/axom/bump/PrimalAdaptor.hpp index 9c1a2bc222..290b87d9d4 100644 --- a/src/axom/bump/PrimalAdaptor.hpp +++ b/src/axom/bump/PrimalAdaptor.hpp @@ -326,6 +326,9 @@ struct PrimalAdaptor typename AdaptPolyhedron::PolyhedralRepresentation; using BoundingBox = axom::primal::BoundingBox; + /// Return the dimension of the shape + static constexpr int dimension() { return CoordsetView::dimension(); } + /*! * \brief Constructor * diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index f85d373ec0..16b75f8555 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -325,7 +325,7 @@ void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const kl } } -#if defined(AXOM_USE_MFEM) +#if defined(AXOM_USE_CONDUIT) void SamplingShaper::importInitialVolumeFractions( const std::map& initialVolumeFractions) { diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index d859ff885c..b609a63226 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -137,6 +137,10 @@ class Shaper #endif #if defined(AXOM_USE_CONDUIT) + shaping::BlueprintState *getBlueprintState() + { + return m_bp_state.get(); + } conduit::Node* getBlueprintMeshNode() { return m_bp_state != nullptr ? &m_bp_state->m_internal_node : nullptr; diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 4569b5ec59..74220f3d57 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -371,7 +371,7 @@ endif() # Blueprint-only shaping test if(CONDUIT_FOUND AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE AND AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) set(_nranks 1) - set(_testname quest_shaping_driver_ex_sampling_stl_spheres) + set(_testname quest_shaping_driver_ex_sampling_blueprint_3D) axom_add_test(NAME ${_testname} COMMAND quest_shaping_driver_ex -i ${shaping_data_dir}/spheres.yaml @@ -379,10 +379,15 @@ if(CONDUIT_FOUND AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE AND AXOM_ENABLE_TEST --method sampling --sampling inout --background-material void - inline_mesh_blueprint --min -5 -5 -5 --max 5 5 5 --res 10 10 10 -d 3 + inline_mesh_blueprint --min -6 -6 -6 --max 6 6 6 --res 16 16 16 -d 3 + --sampling-resolution 5 5 5 + --quadrature-type gausslegendre NUM_MPI_TASKS ${_nranks}) + # bbox volume: 12^3 = 1728; sphere(r=5): ~523.6; sphere(r=2): 33.5 + # expected analytic volume when fully resolved: ~1237.9 + # NOTE: the answer depends on the quadrature type. This answer is for gausslegendre. set_tests_properties(${_testname} PROPERTIES - PASS_REGULAR_EXPRESSION "Volume fraction fields: vol_frac_void, vol_frac_steel") + PASS_REGULAR_EXPRESSION "Volume of material 'void' is 1,?239.") endif() # Distributed closest point example ------------------------------------------- diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index c21f5d82d5..0c3b941e96 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -12,6 +12,7 @@ // Axom includes #include "axom/config.hpp" #include "axom/core.hpp" +#include "axom/bump.hpp" #include "axom/slic.hpp" #include "axom/primal.hpp" #include "axom/sidre.hpp" @@ -92,6 +93,14 @@ struct Projector23 }; } // namespace +//------------------------------------------------------------------------------ +#if defined(AXOM_USE_CONDUIT) +void printSummaryBlueprint(axom::quest::SamplingShaper *); +#endif +#if defined(AXOM_USE_MFEM) +void printSummaryMFEM(axom::quest::Shaper *); +#endif + //------------------------------------------------------------------------------ /// Struct to help choose our shaping method: sampling or intersection for now @@ -1025,32 +1034,17 @@ int main(int argc, char** argv) using axom::utilities::string::startsWith; if(params.usesInlineBlueprintMesh()) { - SLIC_INFO( - "Volume summaries are not yet implemented for Blueprint-backed shaping in this driver."); +#if defined(AXOM_USE_CONDUIT) + if(auto* samplingShaper = dynamic_cast(shaper)) + { + printSummaryBlueprint(samplingShaper); + } +#endif } #if defined(AXOM_USE_MFEM) else if(shaper->getDC() != nullptr) { - 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)); - } - } + printSummaryMFEM(shaper); } #endif AXOM_ANNOTATE_END("adjust"); @@ -1086,3 +1080,118 @@ int main(int argc, char** argv) return 0; } + +void printVolume(const std::string mat_name, double volume) +{ + SLIC_INFO(axom::fmt::format(axom::utilities::locale(), + "Volume of material '{}' is {:.6Lf}", + mat_name, + volume)); +} + +#if defined(AXOM_USE_CONDUIT) +/*! + * \brief Print the summary information for Blueprint meshes. + * + * \param shaper The shaper that was in use for shaping. + * + * \note At present, only compute volumes for the SamplingShaper. + */ +void printSummaryBlueprint(axom::quest::SamplingShaper *shaper) +{ + AXOM_ANNOTATE_SCOPE("printSummaryBlueprint"); + using ExecSpace = axom::SEQ_EXEC; + + // Make sure there is a fields node. If there isn't then we do not need to do any work. + auto *bpState = shaper->getBlueprintState(); + conduit::Node &n_mesh = bpState->getBlueprintMeshNode(); + if(!n_mesh.has_path("fields")) + { + return; + } + + const conduit::Node &n_topo = bpState->getBlueprintTopologyNode(); + conduit::Node &n_fields = n_mesh.fetch_existing("fields"); + + // Compute the measure field. + namespace views = axom::bump::views; + const conduit::Node *n_coordset = conduit::blueprint::mesh::utils::find_reference_node(n_topo, "coordset"); + SLIC_ERROR_IF(n_coordset == nullptr, "Coordset could not be found."); + views::dispatch_coordset(*n_coordset, [&](auto coordsetView) + { + using CoordsetView = decltype(coordsetView); + + // Only compute over quads or hexes, depending on the dimension. + constexpr int selected_dimensions = views::select_dimensions(CoordsetView::dimension()); + constexpr int selected_shapes = (CoordsetView::dimension() == 2) ? (1 << views::Quad_ShapeID) : (1 << views::Hex_ShapeID); + views::dispatch_topology(n_topo, [&](const std::string &AXOM_UNUSED_PARAM(shape), auto topologyView) + { + using TopologyView = decltype(topologyView); + using ShapeAdaptor = axom::bump::PrimalAdaptor; + + ShapeAdaptor adaptor(topologyView, coordsetView); + axom::bump::ComputeMeasure m(adaptor); + m.execute("mesh", n_fields["measure"]); + }); + }); + + // Get the measure field. + if(!n_fields.has_path("measure")) + { + SLIC_INFO(axom::fmt::format("Could not find measure field.")); + return; + } + const auto measure = axom::bump::utilities::make_array_view(n_fields.fetch_existing("measure/values")); + + // Compute the volumes for all of the "vol_frac_" fields. + for(conduit::index_t i = 0; i < n_fields.number_of_children(); i++) + { + conduit::Node &n_field = n_fields[i]; + const std::string name = n_field.name(); + if(axom::utilities::string::startsWith(name, "vol_frac_")) + { + const auto mat_name = name.substr(9); + const auto values = axom::bump::utilities::make_array_view(n_field.fetch_existing("values")); + + SLIC_ERROR_IF(values.size() != measure.size(), "Incompatible sizes"); + const auto n = values.size(); + double sum = 0.; + for(axom::IndexType j = 0; j < n; j++) + { + sum += values[j] * measure[j]; + } + const double volume = shaper->allReduceSum(sum); + + printVolume(mat_name, volume); + } + } +} +#endif + +#if defined(AXOM_USE_MFEM) +/*! + * \brief Print the summary information for MFEM meshes. + * + * \param shaper The shaper that was in use for shaping. + */ +void printSummaryMFEM(axom::quest::Shaper *shaper) +{ + for(auto& kv : shaper->getDC()->GetFieldMap()) + { + if(axom::utilities::string::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); + + printVolume(mat_name, volume); + } + } +} +#endif From b565553cd71a49e05efe19add46c312e26d14a75 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 27 May 2026 15:26:09 -0700 Subject: [PATCH 26/72] make style --- src/axom/bump/ComputeMeasure.hpp | 41 ++++++++--------- src/axom/quest/Shaper.hpp | 5 +-- src/axom/quest/examples/shaping_driver.cpp | 52 ++++++++++++---------- 3 files changed, 50 insertions(+), 48 deletions(-) diff --git a/src/axom/bump/ComputeMeasure.hpp b/src/axom/bump/ComputeMeasure.hpp index 840585e567..07480433f0 100644 --- a/src/axom/bump/ComputeMeasure.hpp +++ b/src/axom/bump/ComputeMeasure.hpp @@ -36,10 +36,10 @@ class ComputeMeasure * * \param adaptor The adaptor object to use to compute the measure. */ - ComputeMeasure(Adaptor &adaptor) : m_adaptor(adaptor), - m_allocator_id(axom::execution_space::allocatorID()) - { - } + ComputeMeasure(Adaptor &adaptor) + : m_adaptor(adaptor) + , m_allocator_id(axom::execution_space::allocatorID()) + { } /*! * \brief Set the allocator id to use when allocating memory. @@ -82,22 +82,23 @@ class ComputeMeasure // Use the Adaptor on device to compute area or volume. const Adaptor deviceAdaptor(m_adaptor); - axom::for_all(deviceAdaptor.numberOfZones(), AXOM_LAMBDA(axom::IndexType zoneIndex) - { - const auto shape = deviceAdaptor.getShape(zoneIndex); - - double value = 0.; - if constexpr (Adaptor::dimension() == 3) - { - value = shape.volume(); - } - else if constexpr (Adaptor::dimension() == 2) - { - value = shape.area(); - } - - valuesView[zoneIndex] = value; - }); + axom::for_all( + deviceAdaptor.numberOfZones(), + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto shape = deviceAdaptor.getShape(zoneIndex); + + double value = 0.; + if constexpr(Adaptor::dimension() == 3) + { + value = shape.volume(); + } + else if constexpr(Adaptor::dimension() == 2) + { + value = shape.area(); + } + + valuesView[zoneIndex] = value; + }); } private: diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index b609a63226..c4d2f85bb2 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -137,10 +137,7 @@ class Shaper #endif #if defined(AXOM_USE_CONDUIT) - shaping::BlueprintState *getBlueprintState() - { - return m_bp_state.get(); - } + shaping::BlueprintState* getBlueprintState() { return m_bp_state.get(); } conduit::Node* getBlueprintMeshNode() { return m_bp_state != nullptr ? &m_bp_state->m_internal_node : nullptr; diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 0c3b941e96..c51cdf297c 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -95,10 +95,10 @@ struct Projector23 //------------------------------------------------------------------------------ #if defined(AXOM_USE_CONDUIT) -void printSummaryBlueprint(axom::quest::SamplingShaper *); +void printSummaryBlueprint(axom::quest::SamplingShaper*); #endif #if defined(AXOM_USE_MFEM) -void printSummaryMFEM(axom::quest::Shaper *); +void printSummaryMFEM(axom::quest::Shaper*); #endif //------------------------------------------------------------------------------ @@ -1097,42 +1097,44 @@ void printVolume(const std::string mat_name, double volume) * * \note At present, only compute volumes for the SamplingShaper. */ -void printSummaryBlueprint(axom::quest::SamplingShaper *shaper) +void printSummaryBlueprint(axom::quest::SamplingShaper* shaper) { AXOM_ANNOTATE_SCOPE("printSummaryBlueprint"); using ExecSpace = axom::SEQ_EXEC; // Make sure there is a fields node. If there isn't then we do not need to do any work. - auto *bpState = shaper->getBlueprintState(); - conduit::Node &n_mesh = bpState->getBlueprintMeshNode(); + auto* bpState = shaper->getBlueprintState(); + conduit::Node& n_mesh = bpState->getBlueprintMeshNode(); if(!n_mesh.has_path("fields")) { return; } - const conduit::Node &n_topo = bpState->getBlueprintTopologyNode(); - conduit::Node &n_fields = n_mesh.fetch_existing("fields"); + const conduit::Node& n_topo = bpState->getBlueprintTopologyNode(); + conduit::Node& n_fields = n_mesh.fetch_existing("fields"); // Compute the measure field. namespace views = axom::bump::views; - const conduit::Node *n_coordset = conduit::blueprint::mesh::utils::find_reference_node(n_topo, "coordset"); + const conduit::Node* n_coordset = + conduit::blueprint::mesh::utils::find_reference_node(n_topo, "coordset"); SLIC_ERROR_IF(n_coordset == nullptr, "Coordset could not be found."); - views::dispatch_coordset(*n_coordset, [&](auto coordsetView) - { + views::dispatch_coordset(*n_coordset, [&](auto coordsetView) { using CoordsetView = decltype(coordsetView); // Only compute over quads or hexes, depending on the dimension. constexpr int selected_dimensions = views::select_dimensions(CoordsetView::dimension()); - constexpr int selected_shapes = (CoordsetView::dimension() == 2) ? (1 << views::Quad_ShapeID) : (1 << views::Hex_ShapeID); - views::dispatch_topology(n_topo, [&](const std::string &AXOM_UNUSED_PARAM(shape), auto topologyView) - { - using TopologyView = decltype(topologyView); - using ShapeAdaptor = axom::bump::PrimalAdaptor; - - ShapeAdaptor adaptor(topologyView, coordsetView); - axom::bump::ComputeMeasure m(adaptor); - m.execute("mesh", n_fields["measure"]); - }); + constexpr int selected_shapes = + (CoordsetView::dimension() == 2) ? (1 << views::Quad_ShapeID) : (1 << views::Hex_ShapeID); + views::dispatch_topology( + n_topo, + [&](const std::string& AXOM_UNUSED_PARAM(shape), auto topologyView) { + using TopologyView = decltype(topologyView); + using ShapeAdaptor = axom::bump::PrimalAdaptor; + + ShapeAdaptor adaptor(topologyView, coordsetView); + axom::bump::ComputeMeasure m(adaptor); + m.execute("mesh", n_fields["measure"]); + }); }); // Get the measure field. @@ -1141,17 +1143,19 @@ void printSummaryBlueprint(axom::quest::SamplingShaper *shaper) SLIC_INFO(axom::fmt::format("Could not find measure field.")); return; } - const auto measure = axom::bump::utilities::make_array_view(n_fields.fetch_existing("measure/values")); + const auto measure = + axom::bump::utilities::make_array_view(n_fields.fetch_existing("measure/values")); // Compute the volumes for all of the "vol_frac_" fields. for(conduit::index_t i = 0; i < n_fields.number_of_children(); i++) { - conduit::Node &n_field = n_fields[i]; + conduit::Node& n_field = n_fields[i]; const std::string name = n_field.name(); if(axom::utilities::string::startsWith(name, "vol_frac_")) { const auto mat_name = name.substr(9); - const auto values = axom::bump::utilities::make_array_view(n_field.fetch_existing("values")); + const auto values = + axom::bump::utilities::make_array_view(n_field.fetch_existing("values")); SLIC_ERROR_IF(values.size() != measure.size(), "Incompatible sizes"); const auto n = values.size(); @@ -1174,7 +1178,7 @@ void printSummaryBlueprint(axom::quest::SamplingShaper *shaper) * * \param shaper The shaper that was in use for shaping. */ -void printSummaryMFEM(axom::quest::Shaper *shaper) +void printSummaryMFEM(axom::quest::Shaper* shaper) { for(auto& kv : shaper->getDC()->GetFieldMap()) { From a3916c30281829880f86e538908c79b518ea19e9 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 27 May 2026 17:17:54 -0700 Subject: [PATCH 27/72] Adjust cmake logic --- src/axom/quest/examples/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 74220f3d57..f11d3fa2b2 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -144,13 +144,13 @@ if (CONDUIT_FOUND AND UMPIRE_FOUND) endif() # Shaping example ------------------------------------------------------------- -set(shaping_dependencies ) +set(shaping_driver_dependencies ${quest_example_depends}) if(AXOM_ENABLE_MPI AND MFEM_FOUND AND MFEM_USE_MPI) set(AXOM_HAS_MFEM_WITH_MPI TRUE) - list(APPEND shaping_dependencies mfem) + list(APPEND shaping_driver_dependencies mfem) endif() if(CONDUIT_FOUND) - list(APPEND shaping_dependencies conduit) + list(APPEND shaping_driver_dependencies conduit) endif() if((AXOM_HAS_MFEM_WITH_MPI OR CONDUIT_FOUND) AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) @@ -158,7 +158,7 @@ if((AXOM_HAS_MFEM_WITH_MPI OR CONDUIT_FOUND) AND AXOM_ENABLE_SIDRE AND AXOM_ENAB NAME quest_shaping_driver_ex SOURCES shaping_driver.cpp OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} - DEPENDS_ON ${quest_example_depends} ${shaping_dependencies} + DEPENDS_ON ${shaping_driver_dependencies} FOLDER axom/quest/examples ) endif() From 41672985d7f1ccb122dc2606559efd5e6994466b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 27 May 2026 18:32:12 -0700 Subject: [PATCH 28/72] Compilation fixes for CUDA. --- .../detail/shaping/GenerateQuadratureMesh.hpp | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp b/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp index a7b0af264e..a18a60b380 100644 --- a/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp +++ b/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp @@ -56,6 +56,13 @@ class GenerateQuadratureMesh using CoordsetType = typename CoordsetView::value_type; using PointType = primal::Point; + /// Struct for capturing views. + struct ViewPackage + { + TopologyView topologyView; + CoordsetView coordsetView; + }; + /*! * \brief Constructs the generator from a topology and coordset view. * @@ -100,7 +107,7 @@ class GenerateQuadratureMesh * \param [in] ruleZ The quadrature rule in the third logical direction. * \param [in,out] n_output The Blueprint mesh tree to augment. */ - void execute(const conduit::Node& n_topology, + void execute(const conduit::Node& AXOM_UNUSED_PARAM(n_topology), const conduit::Node& n_coordset, const std::string& outputTopologyName, const std::string& outputCoordsetName, @@ -188,13 +195,13 @@ class GenerateQuadratureMesh n_physicalWeightValues.set(conduit::DataType::float64(numPoints)); auto physicalQuadratureWeights = utils::make_array_view(n_physicalWeightValues); - const TopologyView deviceTopoView(m_topologyView); - const CoordsetView deviceCoordsetView(m_coordsetView); + // Package these views into a struct to help with device access. + const ViewPackage deviceViews {m_topologyView, m_coordsetView}; axom::for_all( numZones, AXOM_LAMBDA(IndexType zoneIndex) { - const auto zone = deviceTopoView.zone(zoneIndex); + const auto zone = deviceViews.topologyView.zone(zoneIndex); IndexType pointIndex = zoneIndex * static_cast(npts); for(int kz = 0; kz < (dim == 3 ? ruleZ.getNumPoints() : 1); ++kz) @@ -214,15 +221,15 @@ class GenerateQuadratureMesh double physicalMeasure = 0.; if constexpr(CoordsetView::dimension() == 2) { - pt = detail::mapToPhysicalPoint(zone, deviceCoordsetView, xi, eta); + pt = detail::mapToPhysicalPoint(zone, deviceViews.coordsetView, xi, eta); physicalMeasure = - detail::computePhysicalMeasureFactor(zone, deviceCoordsetView, xi, eta); + detail::computePhysicalMeasureFactor(zone, deviceViews.coordsetView, xi, eta); } else { - pt = detail::mapToPhysicalPoint(zone, deviceCoordsetView, xi, eta, zeta); + pt = detail::mapToPhysicalPoint(zone, deviceViews.coordsetView, xi, eta, zeta); physicalMeasure = - detail::computePhysicalMeasureFactor(zone, deviceCoordsetView, xi, eta, zeta); + detail::computePhysicalMeasureFactor(zone, deviceViews.coordsetView, xi, eta, zeta); } // Retain both the reference-space tensor-product weights and the @@ -243,11 +250,12 @@ class GenerateQuadratureMesh } } }); - - AXOM_UNUSED_VAR(n_topology); } +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) private: +#endif TopologyView m_topologyView; CoordsetView m_coordsetView; int m_allocator_id; From 735784df32b1c922cb12bf71892ec34f4cb7ab23 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 28 May 2026 06:30:16 +0000 Subject: [PATCH 29/72] Require bump in some conditional compilation. --- src/axom/quest/CMakeLists.txt | 19 ++++---- src/axom/quest/SamplingShaper.cpp | 12 +++--- src/axom/quest/SamplingShaper.hpp | 19 ++++---- .../quest/detail/shaping/InOutSampler.hpp | 2 +- .../quest/detail/shaping/PrimitiveSampler.hpp | 2 +- .../detail/shaping/WindingNumberSampler.hpp | 2 +- .../shaping/shaping_helpers_blueprint.cpp | 43 +++++++++++-------- .../shaping/shaping_helpers_blueprint.hpp | 12 +++++- src/axom/quest/examples/CMakeLists.txt | 2 +- 9 files changed, 65 insertions(+), 48 deletions(-) diff --git a/src/axom/quest/CMakeLists.txt b/src/axom/quest/CMakeLists.txt index 184ea7184d..171d46edcf 100644 --- a/src/axom/quest/CMakeLists.txt +++ b/src/axom/quest/CMakeLists.txt @@ -151,17 +151,10 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE) list(APPEND quest_headers Shaper.hpp DiscreteShape.hpp IntersectionShaper.hpp - SamplingShaper.hpp - detail/shaping/shaping_helpers.hpp - detail/shaping/InOutSampler.hpp - detail/shaping/PrimitiveSampler.hpp - detail/shaping/WindingNumberSampler.hpp - ) + detail/shaping/shaping_helpers.hpp) list(APPEND quest_sources Shaper.cpp DiscreteShape.cpp - SamplingShaper.cpp - detail/shaping/shaping_helpers.cpp - ) + detail/shaping/shaping_helpers.cpp) if(MFEM_FOUND) list(APPEND quest_headers detail/shaping/shaping_helpers_mfem.hpp) list(APPEND quest_sources detail/shaping/shaping_helpers_mfem.cpp) @@ -173,6 +166,14 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE) list(APPEND quest_depends_on klee) endif() + if(MFEM_FOUND OR (CONDUIT_FOUND AND AXOM_ENABLE_BUMP)) + list(APPEND quest_headers SamplingShaper.hpp + detail/shaping/InOutSampler.hpp + detail/shaping/PrimitiveSampler.hpp + detail/shaping/WindingNumberSampler.hpp) + list(APPEND quest_sources SamplingShaper.cpp) + endif() + # Geometry clipping requires Conduit, Sidre, and RAJA. # (TetMeshClipper additionally requires the Bump component.) if(CONDUIT_FOUND AND RAJA_FOUND) diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 9653b3de05..382ef0adc9 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -14,7 +14,7 @@ namespace quest { bool rval = true; -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) if(m_bp_state != nullptr) { rval = verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(whyBad); @@ -31,7 +31,7 @@ namespace quest return rval; } -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) void SamplingShaper::saveBlueprintFile(const conduit::Node &n_mesh, const std::string &filename) const { #ifdef CONDUIT_RELAY_MPI_ENABLED @@ -44,7 +44,7 @@ namespace quest void SamplingShaper::saveQuadraturePoints(const std::string& filename) const { -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) conduit::Node n_mesh; // Save the quadrature points from MFEM as a Blueprint file. @@ -347,7 +347,7 @@ void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const kl return; } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) if(m_bp_state != nullptr) { shaping::printRegisteredFieldNames(*m_bp_state, @@ -387,7 +387,7 @@ void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const kl return; } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) if(m_bp_state != nullptr) { shaping::computeVolumeFractionsForMaterial(*m_bp_state, matField); @@ -431,7 +431,7 @@ void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const kl dim = m_mfem_state->meshDimension(); } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) if(dim == InvalidDimension && m_bp_state) { dim = m_bp_state->meshDimension(); diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 1cf76548ce..5a1a9b2bd8 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -20,8 +20,9 @@ #include "axom/mint.hpp" #include "axom/klee.hpp" -#if (!defined(AXOM_USE_MFEM) && !defined(AXOM_USE_CONDUIT)) || !defined(AXOM_USE_SIDRE) - #error SamplingShaper requires Axom to be configured with Sidre and either MFEM or Conduit +#if (!defined(AXOM_USE_MFEM) && !(defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP))) || \ + !defined(AXOM_USE_SIDRE) + #error SamplingShaper requires Axom to be configured with Sidre and either MFEM or Conduit+Bump #endif #include "axom/quest/Shaper.hpp" @@ -37,7 +38,7 @@ #include "mfem/linalg/dtensor.hpp" #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) #include "conduit/conduit_relay_io.hpp" #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED #ifdef CONDUIT_RELAY_MPI_ENABLED @@ -155,7 +156,7 @@ class SamplingShaper : public Shaper } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) /// Sidre-compatible constructor SamplingShaper(RuntimePolicy execPolicy, int allocatorId, @@ -315,7 +316,7 @@ class SamplingShaper : public Shaper */ bool verifyInputMeshImpl(std::string& whyBad) const override; -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) /*! * \brief Save a Blueprint file. * @@ -487,7 +488,7 @@ class SamplingShaper : public Shaper return; } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) if(m_bp_state != nullptr) { applyReplacementRulesImpl(*m_bp_state, shape); @@ -637,7 +638,7 @@ class SamplingShaper : public Shaper return; } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) if(m_bp_state != nullptr) { runShapeQueryImplSampler(sampler, *m_bp_state); @@ -658,7 +659,7 @@ class SamplingShaper : public Shaper return; } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) if(m_bp_state != nullptr) { runShapeQueryImplSampler(sampler, *m_bp_state); @@ -715,7 +716,7 @@ class SamplingShaper : public Shaper return; } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) if(m_bp_state != nullptr) { runImpl(*m_bp_state); diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index 8cb1b906bf..12bcf530f8 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -176,7 +176,7 @@ class InOutSampler } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) template std::enable_if_t sampleInOutField(shaping::BlueprintState& bpState, PointProjector projector = {}) diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index a25469080a..b72c58a25a 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -299,7 +299,7 @@ class PrimitiveSampler } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) template void sampleInOutField(shaping::BlueprintState& bpState, PointProjector projector = {}) diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index 1b933c887f..174191ef79 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -307,7 +307,7 @@ class WindingNumberSampler } #endif -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) template std::enable_if_t sampleInOutField(shaping::BlueprintState& bpState, PointProjector projector = {}) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 32aff5d607..4e48bad702 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -5,15 +5,17 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "shaping_helpers_blueprint.hpp" -#include "GenerateQuadratureMesh.hpp" #if defined(AXOM_USE_CONDUIT) -#include "axom/bump/views/dispatch_topology.hpp" -#include "axom/bump/views/dispatch_unstructured_topology.hpp" - #include "conduit_blueprint_mesh.hpp" +#if defined(AXOM_USE_BUMP) + #include "GenerateQuadratureMesh.hpp" + #include "axom/bump/views/dispatch_topology.hpp" + #include "axom/bump/views/dispatch_unstructured_topology.hpp" +#endif + #include namespace axom @@ -33,20 +35,6 @@ constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; -numerics::QuadratureRule getBlueprintQuadratureRule( - axom::numerics::QuadratureType quadratureType, - int npts, - int allocatorID) -{ - SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); - SLIC_ERROR_IF(!axom::numerics::is_supported_quadrature_type(quadratureType), - axom::fmt::format( - "Quadrature type {} is not yet supported for Blueprint quadrature meshes.", - static_cast(quadratureType))); - - return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); -} - std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) { const std::string topoType = topoNode.fetch_existing("type").as_string(); @@ -79,6 +67,21 @@ std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) return ""; } +#if defined(AXOM_USE_BUMP) +numerics::QuadratureRule getBlueprintQuadratureRule( + axom::numerics::QuadratureType quadratureType, + int npts, + int allocatorID) +{ + SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); + SLIC_ERROR_IF(!axom::numerics::is_supported_quadrature_type(quadratureType), + axom::fmt::format( + "Quadrature type {} is not yet supported for Blueprint quadrature meshes.", + static_cast(quadratureType))); + + return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); +} + template void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, const conduit::Node& coordsetNode, @@ -113,6 +116,7 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, meshNode); }); } +#endif } // namespace @@ -121,6 +125,7 @@ std::string getBlueprintCellShape(const conduit::Node& topoNode) return getBlueprintCellShapeImpl(topoNode); } +#if defined(AXOM_USE_BUMP) void printRegisteredFieldNames(const BlueprintState& bpState, const std::set& knownMaterials, VolFracSampling AXOM_UNUSED_PARAM(vfSampling), @@ -490,6 +495,8 @@ conduit::Node* cloneInOutFunction(const conduit::Node* node) return new conduit::Node(*node); } +#endif // defined(AXOM_USE_BUMP) + } // end namespace shaping } // end namespace quest } // end namespace axom diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 40d32b5b48..0f75ecfe67 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -11,10 +11,13 @@ #if defined(AXOM_USE_CONDUIT) -#include "axom/bump/utilities/conduit_memory.hpp" -#include "axom/bump/views/dispatch_coordset.hpp" #include "axom/fmt.hpp" +#if defined(AXOM_USE_BUMP) + #include "axom/bump/utilities/conduit_memory.hpp" + #include "axom/bump/views/dispatch_coordset.hpp" +#endif + #include "conduit_node.hpp" #include @@ -98,6 +101,7 @@ struct BlueprintState : nullptr; } +#if defined(AXOM_USE_BUMP) conduit::Node* createMaterialFunction(const std::string& name) { constexpr const char* quadratureTopologyName = "quadrature_points"; @@ -128,8 +132,10 @@ struct BlueprintState return &fieldNode; } +#endif }; +#if defined(AXOM_USE_BUMP) void printRegisteredFieldNames(const BlueprintState& bpState, const std::set& knownMaterials, VolFracSampling vfSampling, @@ -229,6 +235,8 @@ void sampleInOutField(const std::string& shapeName, static_cast(numQueryPoints / timer.elapsed()))); } +#endif // defined(AXOM_USE_BUMP) + } // end namespace shaping } // end namespace quest } // end namespace axom diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 886b41260c..5bd7b040ca 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -153,7 +153,7 @@ if(CONDUIT_FOUND) list(APPEND shaping_dependencies conduit) endif() -if((AXOM_HAS_MFEM_WITH_MPI OR CONDUIT_FOUND) AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) +if((AXOM_HAS_MFEM_WITH_MPI OR (CONDUIT_FOUND AND AXOM_ENABLE_BUMP)) AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) axom_add_executable( NAME quest_shaping_driver_ex SOURCES shaping_driver.cpp From fca954a37630cb8280b7b562d6942dbc4f8d8269 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 28 May 2026 07:33:19 +0000 Subject: [PATCH 30/72] Screen out incompatible coordsetView/topologyView combinations. --- .../shaping/shaping_helpers_blueprint.cpp | 4 +-- .../shaping/shaping_helpers_blueprint.hpp | 33 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 7ac0f0a812..d525ed6c25 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -89,9 +89,9 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, conduit::Node& meshNode) { namespace views = axom::bump::views; - constexpr int SupportedShapes = views::select_shapes(views::Quad_ShapeID, views::Hex_ShapeID); + constexpr int SupportedShapes = (CoordsetView::dimension() == 2) ? views::select_shapes(views::Quad_ShapeID) : views::select_shapes(views::Hex_ShapeID); - views::dispatch_topology( + views::dispatch_topology( topoNode, [&](const auto&, auto topoView) { GenerateQuadratureMesh generator(topoView, diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index f7291a2e56..fe7118fa1b 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -300,26 +300,25 @@ void sampleInOutField(const std::string& shapeName, [&](auto coordsetView) { using CoordsetView = typename std::decay::type; - SLIC_ERROR_IF(CoordsetView::dimension() != FromDim, - axom::fmt::format("Expected {}D quadrature point coordset, got {}D.", - FromDim, - CoordsetView::dimension())); - - numQueryPoints = coordsetView.size(); - valuesNode.set(conduit::DataType::float64(numQueryPoints)); - auto inoutValues = utils::make_array_view(valuesNode); - - for(axom::IndexType i = 0; i < numQueryPoints; ++i) + // Limit to handling coordsets whose dimensions match FromDim. + if constexpr (CoordsetView::dimension() == FromDim) { - FromPoint fromPt; - const auto coordsetPoint = coordsetView[i]; - for(int d = 0; d < FromDim; ++d) + numQueryPoints = coordsetView.size(); + valuesNode.set(conduit::DataType::float64(numQueryPoints)); + auto inoutValues = utils::make_array_view(valuesNode); + + for(axom::IndexType i = 0; i < numQueryPoints; ++i) { - fromPt[d] = coordsetPoint[d]; + FromPoint fromPt; + const auto coordsetPoint = coordsetView[i]; + for(int d = 0; d < FromDim; ++d) + { + fromPt[d] = coordsetPoint[d]; + } + + const ToPoint queryPt = projector ? projector(fromPt) : ToPoint(fromPt.data()); + inoutValues[i] = checkInside(queryPt) ? 1. : 0.; } - - const ToPoint queryPt = projector ? projector(fromPt) : ToPoint(fromPt.data()); - inoutValues[i] = checkInside(queryPt) ? 1. : 0.; } }); timer.stop(); From d282f4c4b5615f0421163d99f4b661cb9bb54fc4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 28 May 2026 11:20:58 -0700 Subject: [PATCH 31/72] Include files based on AXOM_USE_CONDUIT only --- src/axom/quest/SamplingShaper.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 04d5ef84ba..b1d7344cc7 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -38,7 +38,7 @@ #include "mfem/linalg/dtensor.hpp" #endif -#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) +#if defined(AXOM_USE_CONDUIT) #include "conduit/conduit_relay_io.hpp" #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED #ifdef CONDUIT_RELAY_MPI_ENABLED From 8a3241c0eec66972266bed6fb8b06fd7b6cbf683 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 8 Jun 2026 12:59:35 -0700 Subject: [PATCH 32/72] Fix some ifdefs --- src/axom/quest/examples/shaping_driver.cpp | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index c51cdf297c..c305ad00a0 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -12,7 +12,9 @@ // Axom includes #include "axom/config.hpp" #include "axom/core.hpp" -#include "axom/bump.hpp" +#ifdef AXOM_USE_BUMP + #include "axom/bump.hpp" +#endif #include "axom/slic.hpp" #include "axom/primal.hpp" #include "axom/sidre.hpp" @@ -23,8 +25,8 @@ #include "axom/CLI11.hpp" // NOTE: The shaping driver requires Axom to be configured with conduit or mfem. -#if !defined(AXOM_USE_MFEM) && !defined(AXOM_USE_CONDUIT) - #error Shaping functionality requires Axom to be configured with Conduit or MFEM +#if !defined(AXOM_USE_MFEM) && !(defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP)) + #error Shaping functionality requires Axom to be configured with MFEM or Conduit+Bump #endif #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED @@ -94,7 +96,7 @@ struct Projector23 } // namespace //------------------------------------------------------------------------------ -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) void printSummaryBlueprint(axom::quest::SamplingShaper*); #endif #if defined(AXOM_USE_MFEM) @@ -766,14 +768,15 @@ int main(int argc, char** argv) case ShapingMethod::Sampling: if(params.usesInlineBlueprintMesh()) { -#if defined(AXOM_USE_CONDUIT) + // NOTE: The SamplingShaper requires Conduit + Bump for Blueprint support. +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) shaper = new quest::SamplingShaper(params.policy, axom::policyToDefaultAllocatorID(params.policy), params.shapeSet, originalBlueprintMeshGroup, "mesh"); #else - SLIC_ERROR_ROOT("inline_mesh_blueprint requires Axom to be configured with Conduit."); + SLIC_ERROR_ROOT("inline_mesh_blueprint requires Axom to be configured with Conduit and Bump."); #endif } else @@ -789,6 +792,7 @@ int main(int argc, char** argv) case ShapingMethod::Intersection: if(params.usesInlineBlueprintMesh()) { + // NOTE: The IntersectionShaper requires Conduit for Blueprint support. #if defined(AXOM_USE_CONDUIT) shaper = new quest::IntersectionShaper(params.policy, axom::policyToDefaultAllocatorID(params.policy), @@ -909,7 +913,8 @@ int main(int argc, char** argv) AXOM_ANNOTATE_SCOPE("import initial volume fractions"); if(params.usesInlineBlueprintMesh()) { -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) + // Generate a background material (w/ volume fractions set to 1) if user provided a name if(!params.backgroundMaterial.empty()) { @@ -1034,7 +1039,7 @@ int main(int argc, char** argv) using axom::utilities::string::startsWith; if(params.usesInlineBlueprintMesh()) { -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) if(auto* samplingShaper = dynamic_cast(shaper)) { printSummaryBlueprint(samplingShaper); @@ -1089,7 +1094,7 @@ void printVolume(const std::string mat_name, double volume) volume)); } -#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) /*! * \brief Print the summary information for Blueprint meshes. * From 49fbe35beb1542d70bbd98d36dfa628bbf0c9ab8 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 8 Jun 2026 15:31:20 -0700 Subject: [PATCH 33/72] Moved some files. --- src/axom/bump/CMakeLists.txt | 1 + .../GenerateQuadratureMesh.hpp | 240 +++++++++--- src/axom/bump/tests/CMakeLists.txt | 1 + .../tests/bump_blueprint_quadrature_mesh.cpp | 356 ++++++++++++++++++ .../detail/shaping/MappedZoneUtilities.hpp | 275 -------------- .../shaping/shaping_helpers_blueprint.cpp | 7 +- .../tests/quest_blueprint_quadrature_mesh.cpp | 203 +--------- 7 files changed, 549 insertions(+), 534 deletions(-) rename src/axom/{quest/detail/shaping => bump}/GenerateQuadratureMesh.hpp (57%) create mode 100644 src/axom/bump/tests/bump_blueprint_quadrature_mesh.cpp delete mode 100644 src/axom/quest/detail/shaping/MappedZoneUtilities.hpp diff --git a/src/axom/bump/CMakeLists.txt b/src/axom/bump/CMakeLists.txt index 3748251f63..00cc3b1f87 100644 --- a/src/axom/bump/CMakeLists.txt +++ b/src/axom/bump/CMakeLists.txt @@ -74,6 +74,7 @@ set(bump_headers ExtrudeMesh.hpp FieldBlender.hpp FieldSlicer.hpp + GenerateQuadratureMesh.hpp HashNaming.hpp IndexingPolicies.hpp MakePointMesh.hpp diff --git a/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp b/src/axom/bump/GenerateQuadratureMesh.hpp similarity index 57% rename from src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp rename to src/axom/bump/GenerateQuadratureMesh.hpp index a18a60b380..a2456e85d0 100644 --- a/src/axom/quest/detail/shaping/GenerateQuadratureMesh.hpp +++ b/src/axom/bump/GenerateQuadratureMesh.hpp @@ -4,17 +4,17 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_GENERATE_QUADRATURE_MESH_HPP_ -#define AXOM_QUEST_GENERATE_QUADRATURE_MESH_HPP_ +#ifndef AXOM_BUMP_GENERATE_QUADRATURE_MESH_HPP_ +#define AXOM_BUMP_GENERATE_QUADRATURE_MESH_HPP_ #include "axom/config.hpp" #if defined(AXOM_USE_CONDUIT) - #include "MappedZoneUtilities.hpp" #include "axom/bump/utilities/blueprint_utilities.hpp" #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/core.hpp" + #include "axom/core/numerics/Determinants.hpp" #include "axom/core/numerics/quadrature.hpp" #include "axom/primal.hpp" #include "axom/sidre/core/ConduitMemory.hpp" @@ -33,22 +33,184 @@ namespace axom { -namespace quest +namespace bump { -namespace shaping +namespace detail { -/*! - * \brief Generates a Blueprint point mesh of quadrature samples over an input - * topology view. - * - * The generated mesh stores one point element per sampled quadrature point and - * publishes fields that map those points back to their source zones. - * - * \tparam ExecSpace The execution space used to populate the generated data. - * \tparam TopologyView The bump topology view type. - * \tparam CoordsetView The bump coordset view type. - */ +template +AXOM_HOST_DEVICE primal::Point +mapToPhysicalPoint(const ShapeType& zone, const CoordsetView& coordsetView, double u, double v) +{ + using PointType = primal::Point; + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + + const double n0 = (1.0 - u) * (1.0 - v); + const double n1 = u * (1.0 - v); + const double n2 = u * v; + const double n3 = (1.0 - u) * v; + + PointType pt; + for(int d = 0; d < 2; ++d) + { + pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d]; + } + return pt; +} + +template +AXOM_HOST_DEVICE primal::Point +mapToPhysicalPoint(const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v, + double w) +{ + using PointType = primal::Point; + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + const auto p4 = coordsetView[zone.getId(4)]; + const auto p5 = coordsetView[zone.getId(5)]; + const auto p6 = coordsetView[zone.getId(6)]; + const auto p7 = coordsetView[zone.getId(7)]; + + const double a = 1.0 - u; + const double b = 1.0 - v; + const double c = 1.0 - w; + + const double n0 = a * b * c; + const double n1 = u * b * c; + const double n2 = u * v * c; + const double n3 = a * v * c; + const double n4 = a * b * w; + const double n5 = u * b * w; + const double n6 = u * v * w; + const double n7 = a * v * w; + + PointType pt; + for(int d = 0; d < 3; ++d) + { + pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d] + n4 * p4[d] + n5 * p5[d] + + n6 * p6[d] + n7 * p7[d]; + } + return pt; +} + +template +AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v) +{ + using VectorType = primal::Vector; + + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + + const double du0 = -(1.0 - v); + const double du1 = (1.0 - v); + const double du2 = v; + const double du3 = -v; + + const double dv0 = -(1.0 - u); + const double dv1 = -u; + const double dv2 = u; + const double dv3 = 1.0 - u; + + VectorType dxdu; + VectorType dxdv; + for(int d = 0; d < 2; ++d) + { + dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d]; + dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d]; + } + + return axom::utilities::abs(axom::numerics::determinant(dxdu[0], dxdv[0], dxdu[1], dxdv[1])); +} + +template +AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v, + double w) +{ + using VectorType = primal::Vector; + + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + const auto p4 = coordsetView[zone.getId(4)]; + const auto p5 = coordsetView[zone.getId(5)]; + const auto p6 = coordsetView[zone.getId(6)]; + const auto p7 = coordsetView[zone.getId(7)]; + + const double a = 1.0 - u; + const double b = 1.0 - v; + const double c = 1.0 - w; + + const double du0 = -b * c; + const double du1 = b * c; + const double du2 = v * c; + const double du3 = -v * c; + const double du4 = -b * w; + const double du5 = b * w; + const double du6 = v * w; + const double du7 = -v * w; + + const double dv0 = -a * c; + const double dv1 = -u * c; + const double dv2 = u * c; + const double dv3 = a * c; + const double dv4 = -a * w; + const double dv5 = -u * w; + const double dv6 = u * w; + const double dv7 = a * w; + + const double dw0 = -a * b; + const double dw1 = -u * b; + const double dw2 = -u * v; + const double dw3 = -a * v; + const double dw4 = a * b; + const double dw5 = u * b; + const double dw6 = u * v; + const double dw7 = a * v; + + VectorType dxdu; + VectorType dxdv; + VectorType dxdw; + for(int d = 0; d < 3; ++d) + { + dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d] + du4 * p4[d] + du5 * p5[d] + + du6 * p6[d] + du7 * p7[d]; + dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d] + dv4 * p4[d] + dv5 * p5[d] + + dv6 * p6[d] + dv7 * p7[d]; + dxdw[d] = dw0 * p0[d] + dw1 * p1[d] + dw2 * p2[d] + dw3 * p3[d] + dw4 * p4[d] + dw5 * p5[d] + + dw6 * p6[d] + dw7 * p7[d]; + } + + return axom::utilities::abs(VectorType::scalar_triple_product(dxdu, dxdv, dxdw)); +} + +inline int quadraturePointCount(const numerics::QuadratureRule& ruleX, + const numerics::QuadratureRule& ruleY, + const numerics::QuadratureRule& ruleZ, + int dim) +{ + return dim == 2 ? ruleX.getNumPoints() * ruleY.getNumPoints() + : ruleX.getNumPoints() * ruleY.getNumPoints() * ruleZ.getNumPoints(); +} + +} // namespace detail + template class GenerateQuadratureMesh { @@ -56,30 +218,18 @@ class GenerateQuadratureMesh using CoordsetType = typename CoordsetView::value_type; using PointType = primal::Point; - /// Struct for capturing views. struct ViewPackage { TopologyView topologyView; CoordsetView coordsetView; }; - /*! - * \brief Constructs the generator from a topology and coordset view. - * - * \param [in] topologyView The source topology view. - * \param [in] coordsetView The source coordset view. - */ GenerateQuadratureMesh(const TopologyView& topologyView, const CoordsetView& coordsetView) : m_topologyView(topologyView) , m_coordsetView(coordsetView) , m_allocator_id(axom::execution_space::allocatorID()) { } - /*! - * \brief Sets the allocator used for generated Conduit-backed storage. - * - * \param [in] allocator_id The allocator to use for generated arrays. - */ void setAllocatorID(int allocator_id) { SLIC_ERROR_IF(!axom::isValidAllocatorID(allocator_id), "Invalid allocator id."); @@ -90,23 +240,6 @@ class GenerateQuadratureMesh int getAllocatorID() const { return m_allocator_id; } - /*! - * \brief Executes the quadrature-point mesh generation. - * - * \param [in] n_topology The source topology node. - * \param [in] n_coordset The source coordset node. - * \param [in] outputTopologyName The generated point-topology name. - * \param [in] outputCoordsetName The generated coordset name. - * \param [in] originalElementsFieldName The generated provenance field name. - * \param [in] quadratureWeightsFieldName The generated reference-weight field - * name. - * \param [in] quadraturePhysicalWeightsFieldName The generated physical-weight - * field name. - * \param [in] ruleX The quadrature rule in the first logical direction. - * \param [in] ruleY The quadrature rule in the second logical direction. - * \param [in] ruleZ The quadrature rule in the third logical direction. - * \param [in,out] n_output The Blueprint mesh tree to augment. - */ void execute(const conduit::Node& AXOM_UNUSED_PARAM(n_topology), const conduit::Node& n_coordset, const std::string& outputTopologyName, @@ -135,7 +268,6 @@ class GenerateQuadratureMesh n_outputCoordset.reset(); n_outputCoordset["type"] = "explicit"; - // Store the sampled coordinates as plain explicit coordset components. axom::StackArray, CoordsetView::dimension()> coordViews; for(int d = 0; d < dim; ++d) { @@ -151,7 +283,6 @@ class GenerateQuadratureMesh n_outputTopo["coordset"] = outputCoordsetName; n_outputTopo["elements/shape"] = "point"; - // The derived topology is a point mesh, so connectivity is the identity. conduit::Node& n_connectivity = n_outputTopo["elements/connectivity"]; n_connectivity.set_allocator(conduitAllocatorId); n_connectivity.set(conduit::DataType::index_t(numPoints)); @@ -195,7 +326,6 @@ class GenerateQuadratureMesh n_physicalWeightValues.set(conduit::DataType::float64(numPoints)); auto physicalQuadratureWeights = utils::make_array_view(n_physicalWeightValues); - // Package these views into a struct to help with device access. const ViewPackage deviceViews {m_topologyView, m_coordsetView}; axom::for_all( @@ -228,12 +358,14 @@ class GenerateQuadratureMesh else { pt = detail::mapToPhysicalPoint(zone, deviceViews.coordsetView, xi, eta, zeta); - physicalMeasure = - detail::computePhysicalMeasureFactor(zone, deviceViews.coordsetView, xi, eta, zeta); + physicalMeasure = detail::computePhysicalMeasureFactor( + zone, + deviceViews.coordsetView, + xi, + eta, + zeta); } - // Retain both the reference-space tensor-product weights and the - // Jacobian-weighted physical weights for downstream consumers. const double referenceWeight = wx * wy * wz; for(int d = 0; d < dim; ++d) { @@ -252,7 +384,6 @@ class GenerateQuadratureMesh }); } -// The following members are private (unless using CUDA) #if !defined(__CUDACC__) private: #endif @@ -261,8 +392,7 @@ class GenerateQuadratureMesh int m_allocator_id; }; -} // namespace shaping -} // namespace quest +} // namespace bump } // namespace axom #endif diff --git a/src/axom/bump/tests/CMakeLists.txt b/src/axom/bump/tests/CMakeLists.txt index a79c77dda4..937dfc4d31 100644 --- a/src/axom/bump/tests/CMakeLists.txt +++ b/src/axom/bump/tests/CMakeLists.txt @@ -13,6 +13,7 @@ #------------------------------------------------------------------------------ set(gtest_bump_tests + bump_blueprint_quadrature_mesh.cpp bump_clipfield.cpp bump_cutfield.cpp bump_coordset_extents.cpp diff --git a/src/axom/bump/tests/bump_blueprint_quadrature_mesh.cpp b/src/axom/bump/tests/bump_blueprint_quadrature_mesh.cpp new file mode 100644 index 0000000000..3660126a5a --- /dev/null +++ b/src/axom/bump/tests/bump_blueprint_quadrature_mesh.cpp @@ -0,0 +1,356 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/bump/GenerateQuadratureMesh.hpp" +#include "axom/bump/utilities/conduit_memory.hpp" +#include "axom/bump/views/dispatch_coordset.hpp" +#include "axom/bump/views/dispatch_topology.hpp" + +#include "conduit.hpp" +#include "conduit_blueprint.hpp" + +#include + +namespace +{ + +template +bool compareArrayView(axom::ArrayView lhs, axom::ArrayView rhs) +{ + if(lhs.size() != rhs.size()) + { + return false; + } + + for(axom::IndexType i = 0; i < lhs.size(); ++i) + { + if(lhs[i] != rhs[i]) + { + return false; + } + } + return true; +} + +void setNodeValues(conduit::Node& node, axom::ArrayView values) +{ + node.set(conduit::DataType::float64(values.size())); + auto* data = node.as_float64_ptr(); + for(axom::IndexType i = 0; i < values.size(); ++i) + { + data[i] = values[i]; + } +} + +void setNodeValues(conduit::Node& node, axom::ArrayView values) +{ + node.set(conduit::DataType::index_t(values.size())); + auto* data = node.as_index_t_ptr(); + for(axom::IndexType i = 0; i < values.size(); ++i) + { + data[i] = values[i]; + } +} + +conduit::Node makeQuadMesh(const std::string& topoName = "mesh") +{ + conduit::Node mesh; + + mesh["coordsets/coords/type"] = "explicit"; + const axom::Array x {{0., 1., 0., 1.}}; + const axom::Array y {{0., 0., 1., 1.}}; + setNodeValues(mesh["coordsets/coords/values/x"], x.view()); + setNodeValues(mesh["coordsets/coords/values/y"], y.view()); + + mesh["topologies"][topoName]["type"] = "unstructured"; + mesh["topologies"][topoName]["coordset"] = "coords"; + mesh["topologies"][topoName]["elements/shape"] = "quad"; + const axom::Array connectivity {{0, 1, 3, 2}}; + setNodeValues(mesh["topologies"][topoName]["elements/connectivity"], connectivity.view()); + + return mesh; +} + +conduit::Node makeHexMesh(const std::string& topoName = "mesh") +{ + conduit::Node mesh; + + mesh["coordsets/coords/type"] = "explicit"; + const axom::Array x {{0., 1., 0., 1., 0., 1., 0., 1.}}; + const axom::Array y {{0., 0., 1., 1., 0., 0., 1., 1.}}; + const axom::Array z {{0., 0., 0., 0., 1., 1., 1., 1.}}; + setNodeValues(mesh["coordsets/coords/values/x"], x.view()); + setNodeValues(mesh["coordsets/coords/values/y"], y.view()); + setNodeValues(mesh["coordsets/coords/values/z"], z.view()); + + mesh["topologies"][topoName]["type"] = "unstructured"; + mesh["topologies"][topoName]["coordset"] = "coords"; + mesh["topologies"][topoName]["elements/shape"] = "hex"; + const axom::Array connectivity {{0, 1, 3, 2, 4, 5, 7, 6}}; + setNodeValues(mesh["topologies"][topoName]["elements/connectivity"], connectivity.view()); + + return mesh; +} + +conduit::Node makeDistortedQuadMesh(const std::string& topoName = "mesh") +{ + conduit::Node mesh; + + mesh["coordsets/coords/type"] = "explicit"; + const axom::Array x {{0., 2., 0., 1.}}; + const axom::Array y {{0., 0., 1., 1.}}; + setNodeValues(mesh["coordsets/coords/values/x"], x.view()); + setNodeValues(mesh["coordsets/coords/values/y"], y.view()); + + mesh["topologies"][topoName]["type"] = "unstructured"; + mesh["topologies"][topoName]["coordset"] = "coords"; + mesh["topologies"][topoName]["elements/shape"] = "quad"; + const axom::Array connectivity {{0, 1, 3, 2}}; + setNodeValues(mesh["topologies"][topoName]["elements/connectivity"], connectivity.view()); + + return mesh; +} + +conduit::Node makeStructuredQuadMesh(const std::string& topoName = "mesh") +{ + conduit::Node mesh; + + mesh["coordsets/coords/type"] = "explicit"; + const axom::Array x {{0., 1., 0., 1.}}; + const axom::Array y {{0., 0., 1., 1.}}; + setNodeValues(mesh["coordsets/coords/values/x"], x.view()); + setNodeValues(mesh["coordsets/coords/values/y"], y.view()); + + mesh["topologies"][topoName]["type"] = "structured"; + mesh["topologies"][topoName]["coordset"] = "coords"; + mesh["topologies"][topoName]["elements/shape"] = "quad"; + mesh["topologies"][topoName]["elements/dims/i"] = 1; + mesh["topologies"][topoName]["elements/dims/j"] = 1; + + return mesh; +} + +void generateQuadratureMesh(conduit::Node& mesh, + const std::string& topologyName, + axom::ArrayView sampleResolution, + axom::numerics::QuadratureType quadratureType) +{ + namespace views = axom::bump::views; + + const int allocatorId = axom::execution_space::allocatorID(); + auto ruleX = + axom::numerics::get_quadrature_rule(quadratureType, sampleResolution[0], allocatorId); + auto ruleY = + axom::numerics::get_quadrature_rule(quadratureType, sampleResolution[1], allocatorId); + const int nz = sampleResolution.size() > 2 ? sampleResolution[2] : 1; + auto ruleZ = axom::numerics::get_quadrature_rule(quadratureType, nz, allocatorId); + + const conduit::Node& topoNode = + mesh.fetch_existing("topologies").fetch_existing(topologyName); + const conduit::Node& coordsetNode = + mesh.fetch_existing("coordsets").fetch_existing(topoNode.fetch_existing("coordset").as_string()); + + views::dispatch_explicit_coordset(coordsetNode, [&](auto coordsetView) { + using CoordsetView = typename std::decay::type; + constexpr int supportedShapes = (CoordsetView::dimension() == 2) + ? views::select_shapes(views::Quad_ShapeID) + : views::select_shapes(views::Hex_ShapeID); + + views::dispatch_topology( + topoNode, + [&](const auto&, auto topoView) { + axom::bump::GenerateQuadratureMesh + generator(topoView, coordsetView); + generator.setAllocatorID(allocatorId); + generator.execute(topoNode, + coordsetNode, + "quadrature_points", + "quadrature_points", + "originalElements", + "quadratureWeights", + "quadraturePhysicalWeights", + ruleX, + ruleY, + ruleZ, + mesh); + }); + }); +} + +TEST(bump_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) +{ + conduit::Node mesh = makeQuadMesh(); + + int sampleResolution[] = {2, 3}; + generateQuadratureMesh(mesh, + "mesh", + axom::ArrayView {sampleResolution, 2}, + axom::numerics::QuadratureType::ClosedUniform); + + conduit::Node info; + EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); + + const conduit::Node& quadTopo = mesh["topologies/quadrature_points"]; + EXPECT_EQ(quadTopo["type"].as_string(), "unstructured"); + EXPECT_EQ(quadTopo["coordset"].as_string(), "quadrature_points"); + EXPECT_EQ(quadTopo["elements/shape"].as_string(), "point"); + + namespace utils = axom::bump::utilities; + const auto connView = utils::make_array_view( + mesh["topologies/quadrature_points/elements/connectivity"]); + const auto sizesView = + utils::make_array_view(mesh["topologies/quadrature_points/elements/sizes"]); + const auto offsetsView = + utils::make_array_view(mesh["topologies/quadrature_points/elements/offsets"]); + const auto originalElementsView = + utils::make_array_view(mesh["fields/originalElements/values"]); + const auto quadratureWeightsView = + utils::make_array_view(mesh["fields/quadratureWeights/values"]); + const auto physicalQuadratureWeightsView = + utils::make_array_view(mesh["fields/quadraturePhysicalWeights/values"]); + + const axom::Array expectedX {{0., 1., 0., 1., 0., 1.}}; + const axom::Array expectedY {{0., 0., 0.5, 0.5, 1., 1.}}; + const axom::Array expectedConn {{0, 1, 2, 3, 4, 5}}; + const axom::Array expectedSizes {{1, 1, 1, 1, 1, 1}}; + const axom::Array expectedOffsets {{0, 1, 2, 3, 4, 5}}; + const axom::Array expectedOriginalElements {{0, 0, 0, 0, 0, 0}}; + const axom::Array expectedWeights { + {1. / 12., 1. / 12., 1. / 3., 1. / 3., 1. / 12., 1. / 12.}}; + + axom::bump::views::dispatch_explicit_coordset( + mesh["coordsets/quadrature_points"], + [&](auto coordsetView) { + for(axom::IndexType i = 0; i < expectedX.size(); ++i) + { + EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-12); + EXPECT_NEAR(coordsetView[i][1], expectedY[i], 1e-12); + } + }); + EXPECT_TRUE(compareArrayView(expectedConn.view(), connView)); + EXPECT_TRUE(compareArrayView(expectedSizes.view(), sizesView)); + EXPECT_TRUE(compareArrayView(expectedOffsets.view(), offsetsView)); + EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); + for(axom::IndexType i = 0; i < expectedWeights.size(); ++i) + { + EXPECT_NEAR(expectedWeights[i], quadratureWeightsView[i], 1e-12); + EXPECT_NEAR(expectedWeights[i], physicalQuadratureWeightsView[i], 1e-12); + } +} + +TEST(bump_blueprint_quadrature_mesh, generate_open_uniform_hex_mesh) +{ + conduit::Node mesh = makeHexMesh(); + + int sampleResolution[3] = {2, 1, 2}; + generateQuadratureMesh(mesh, + "mesh", + axom::ArrayView {sampleResolution, 3}, + axom::numerics::QuadratureType::OpenUniform); + + conduit::Node info; + EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); + + EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/type")) << mesh.to_yaml(); + EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/values/x")) << mesh.to_yaml(); + EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/values/y")) << mesh.to_yaml(); + EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/values/z")) << mesh.to_yaml(); + + namespace utils = axom::bump::utilities; + const auto originalElementsView = + utils::make_array_view(mesh["fields/originalElements/values"]); + const auto quadratureWeightsView = + utils::make_array_view(mesh["fields/quadratureWeights/values"]); + const auto physicalQuadratureWeightsView = + utils::make_array_view(mesh["fields/quadraturePhysicalWeights/values"]); + + const axom::Array expectedX {{1. / 3., 2. / 3., 1. / 3., 2. / 3.}}; + const axom::Array expectedY {{0.5, 0.5, 0.5, 0.5}}; + const axom::Array expectedZ {{1. / 3., 1. / 3., 2. / 3., 2. / 3.}}; + const axom::Array expectedOriginalElements {{0, 0, 0, 0}}; + const axom::Array expectedWeights {{0.25, 0.25, 0.25, 0.25}}; + + axom::bump::views::dispatch_explicit_coordset( + mesh["coordsets/quadrature_points"], + [&](auto coordsetView) { + for(axom::IndexType i = 0; i < expectedX.size(); ++i) + { + EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-6); + EXPECT_NEAR(coordsetView[i][1], expectedY[i], 1e-6); + EXPECT_NEAR(coordsetView[i][2], expectedZ[i], 1e-6); + } + }); + EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); + for(axom::IndexType i = 0; i < expectedWeights.size(); ++i) + { + EXPECT_NEAR(expectedWeights[i], quadratureWeightsView[i], 1e-12); + EXPECT_NEAR(expectedWeights[i], physicalQuadratureWeightsView[i], 1e-12); + } +} + +TEST(bump_blueprint_quadrature_mesh, generate_closed_uniform_structured_quad_mesh) +{ + conduit::Node mesh = makeStructuredQuadMesh(); + + int sampleResolution[] = {2, 2}; + generateQuadratureMesh(mesh, + "mesh", + axom::ArrayView {sampleResolution, 2}, + axom::numerics::QuadratureType::ClosedUniform); + + conduit::Node info; + EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); + + ASSERT_TRUE(mesh.has_path("coordsets/quadrature_points/values/x")); + ASSERT_TRUE(mesh.has_path("fields/originalElements/values")); + + namespace utils = axom::bump::utilities; + const auto originalElementsView = + utils::make_array_view(mesh["fields/originalElements/values"]); + + const axom::Array expectedX {{0., 1., 0., 1.}}; + const axom::Array expectedY {{0., 0., 1., 1.}}; + const axom::Array expectedOriginalElements {{0, 0, 0, 0}}; + + axom::bump::views::dispatch_explicit_coordset( + mesh["coordsets/quadrature_points"], + [&](auto coordsetView) { + for(axom::IndexType i = 0; i < expectedX.size(); ++i) + { + EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-12); + EXPECT_NEAR(coordsetView[i][1], expectedY[i], 1e-12); + } + }); + EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); +} + +TEST(bump_blueprint_quadrature_mesh, mapped_zone_helper_computes_distorted_quad_measure_factor) +{ + conduit::Node mesh = makeDistortedQuadMesh(); + + double lowerFactor = 0.; + double upperFactor = 0.; + + axom::bump::views::dispatch_explicit_coordset(mesh["coordsets/coords"], [&](auto coordsetView) { + axom::bump::views::dispatch_topology(mesh["topologies/mesh"], [&](const auto&, auto topoView) { + const auto zone = topoView.zone(0); + lowerFactor = + axom::bump::detail::computePhysicalMeasureFactor(zone, coordsetView, 0.5, 0.0); + upperFactor = + axom::bump::detail::computePhysicalMeasureFactor(zone, coordsetView, 0.5, 1.0); + }); + }); + + EXPECT_NEAR(lowerFactor, 2.0, 1e-12); + EXPECT_NEAR(upperFactor, 1.0, 1e-12); +} + +} // namespace diff --git a/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp b/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp deleted file mode 100644 index 607ef7593c..0000000000 --- a/src/axom/quest/detail/shaping/MappedZoneUtilities.hpp +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright (c) Lawrence Livermore National Security, LLC and other -// Axom Project Contributors. See top-level LICENSE and COPYRIGHT -// files for dates and other details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -#ifndef AXOM_QUEST_MAPPED_ZONE_UTILITIES_HPP_ -#define AXOM_QUEST_MAPPED_ZONE_UTILITIES_HPP_ - -#include "axom/config.hpp" - -#include "axom/core.hpp" -#include "axom/core/numerics/Determinants.hpp" -#include "axom/primal.hpp" - -/*! - * \file MappedZoneUtilities.hpp - * - * \brief Header-only utilities for evaluating low-order mapped quad/hex zones. - */ - -namespace axom -{ -namespace quest -{ -namespace shaping -{ -namespace detail -{ - -/*! - * \brief Maps a point in the unit square to a physical quad using bilinear - * shape functions. - * - * \tparam ShapeType A zone type exposing `getId(i)` for its node ids. - * \tparam CoordsetView A coordset view whose entries are point-like. - * - * \param [in] zone The source zone. - * \param [in] coordsetView The coordinate storage for the source mesh. - * \param [in] u The first reference-space coordinate in `[0,1]`. - * \param [in] v The second reference-space coordinate in `[0,1]`. - * - * \return The mapped physical-space point. - */ -template -AXOM_HOST_DEVICE primal::Point -mapToPhysicalPoint(const ShapeType& zone, const CoordsetView& coordsetView, double u, double v) -{ - using PointType = primal::Point; - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - - const double n0 = (1.0 - u) * (1.0 - v); - const double n1 = u * (1.0 - v); - const double n2 = u * v; - const double n3 = (1.0 - u) * v; - - PointType pt; - for(int d = 0; d < 2; ++d) - { - pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d]; - } - return pt; -} - -/*! - * \brief Maps a point in the unit cube to a physical hex using trilinear - * shape functions. - * - * \tparam ShapeType A zone type exposing `getId(i)` for its node ids. - * \tparam CoordsetView A coordset view whose entries are point-like. - * - * \param [in] zone The source zone. - * \param [in] coordsetView The coordinate storage for the source mesh. - * \param [in] u The first reference-space coordinate in `[0,1]`. - * \param [in] v The second reference-space coordinate in `[0,1]`. - * \param [in] w The third reference-space coordinate in `[0,1]`. - * - * \return The mapped physical-space point. - */ -template -AXOM_HOST_DEVICE primal::Point -mapToPhysicalPoint(const ShapeType& zone, const CoordsetView& coordsetView, double u, double v, double w) -{ - using PointType = primal::Point; - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - const auto p4 = coordsetView[zone.getId(4)]; - const auto p5 = coordsetView[zone.getId(5)]; - const auto p6 = coordsetView[zone.getId(6)]; - const auto p7 = coordsetView[zone.getId(7)]; - - const double a = 1.0 - u; - const double b = 1.0 - v; - const double c = 1.0 - w; - - const double n0 = a * b * c; - const double n1 = u * b * c; - const double n2 = u * v * c; - const double n3 = a * v * c; - const double n4 = a * b * w; - const double n5 = u * b * w; - const double n6 = u * v * w; - const double n7 = a * v * w; - - PointType pt; - for(int d = 0; d < 3; ++d) - { - pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d] + n4 * p4[d] + n5 * p5[d] + - n6 * p6[d] + n7 * p7[d]; - } - return pt; -} - -/*! - * \brief Evaluates the local physical area scale for a mapped quad. - * - * Computes the determinant of the 2x2 Jacobian for the bilinear map from the - * unit square to the physical zone, returning its absolute value. - * - * \tparam ShapeType A zone type exposing `getId(i)` for its node ids. - * \tparam CoordsetView A coordset view whose entries are point-like. - * - * \param [in] zone The source zone. - * \param [in] coordsetView The coordinate storage for the source mesh. - * \param [in] u The first reference-space coordinate in `[0,1]`. - * \param [in] v The second reference-space coordinate in `[0,1]`. - * - * \return The local Jacobian area scale `|det(dx/du, dx/dv)|`. - */ -template -AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v) -{ - using VectorType = primal::Vector; - - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - - const double du0 = -(1.0 - v); - const double du1 = (1.0 - v); - const double du2 = v; - const double du3 = -v; - - const double dv0 = -(1.0 - u); - const double dv1 = -u; - const double dv2 = u; - const double dv3 = 1.0 - u; - - VectorType dxdu; - VectorType dxdv; - for(int d = 0; d < 2; ++d) - { - dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d]; - dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d]; - } - - return axom::utilities::abs(axom::numerics::determinant(dxdu[0], dxdv[0], dxdu[1], dxdv[1])); -} - -/*! - * \brief Evaluates the local physical volume scale for a mapped hex. - * - * Computes the determinant of the 3x3 Jacobian for the trilinear map from the - * unit cube to the physical zone, returning its absolute value. - * - * \tparam ShapeType A zone type exposing `getId(i)` for its node ids. - * \tparam CoordsetView A coordset view whose entries are point-like. - * - * \param [in] zone The source zone. - * \param [in] coordsetView The coordinate storage for the source mesh. - * \param [in] u The first reference-space coordinate in `[0,1]`. - * \param [in] v The second reference-space coordinate in `[0,1]`. - * \param [in] w The third reference-space coordinate in `[0,1]`. - * - * \return The local Jacobian volume scale `|det(dx/du, dx/dv, dx/dw)|`. - */ -template -AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v, - double w) -{ - using VectorType = primal::Vector; - - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - const auto p4 = coordsetView[zone.getId(4)]; - const auto p5 = coordsetView[zone.getId(5)]; - const auto p6 = coordsetView[zone.getId(6)]; - const auto p7 = coordsetView[zone.getId(7)]; - - const double a = 1.0 - u; - const double b = 1.0 - v; - const double c = 1.0 - w; - - const double du0 = -b * c; - const double du1 = b * c; - const double du2 = v * c; - const double du3 = -v * c; - const double du4 = -b * w; - const double du5 = b * w; - const double du6 = v * w; - const double du7 = -v * w; - - const double dv0 = -a * c; - const double dv1 = -u * c; - const double dv2 = u * c; - const double dv3 = a * c; - const double dv4 = -a * w; - const double dv5 = -u * w; - const double dv6 = u * w; - const double dv7 = a * w; - - const double dw0 = -a * b; - const double dw1 = -u * b; - const double dw2 = -u * v; - const double dw3 = -a * v; - const double dw4 = a * b; - const double dw5 = u * b; - const double dw6 = u * v; - const double dw7 = a * v; - - VectorType dxdu; - VectorType dxdv; - VectorType dxdw; - for(int d = 0; d < 3; ++d) - { - dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d] + du4 * p4[d] + du5 * p5[d] + - du6 * p6[d] + du7 * p7[d]; - dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d] + dv4 * p4[d] + dv5 * p5[d] + - dv6 * p6[d] + dv7 * p7[d]; - dxdw[d] = dw0 * p0[d] + dw1 * p1[d] + dw2 * p2[d] + dw3 * p3[d] + dw4 * p4[d] + dw5 * p5[d] + - dw6 * p6[d] + dw7 * p7[d]; - } - - return axom::utilities::abs(VectorType::scalar_triple_product(dxdu, dxdv, dxdw)); -} - -/*! - * \brief Returns the tensor-product quadrature point count per zone. - * - * \param [in] ruleX The rule in the first logical direction. - * \param [in] ruleY The rule in the second logical direction. - * \param [in] ruleZ The rule in the third logical direction. - * \param [in] dim The logical dimension of the source zones. - * - * \return The number of quadrature points generated for one zone. - */ -inline int quadraturePointCount(const numerics::QuadratureRule& ruleX, - const numerics::QuadratureRule& ruleY, - const numerics::QuadratureRule& ruleZ, - int dim) -{ - return dim == 2 ? ruleX.getNumPoints() * ruleY.getNumPoints() - : ruleX.getNumPoints() * ruleY.getNumPoints() * ruleZ.getNumPoints(); -} - -} // namespace detail -} // namespace shaping -} // namespace quest -} // namespace axom - -#endif diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index d525ed6c25..0dbb325fe1 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -11,7 +11,7 @@ #include "conduit_blueprint_mesh.hpp" #if defined(AXOM_USE_BUMP) - #include "GenerateQuadratureMesh.hpp" + #include "axom/bump/GenerateQuadratureMesh.hpp" #include "axom/bump/views/dispatch_topology.hpp" #include "axom/bump/views/dispatch_unstructured_topology.hpp" #endif @@ -94,8 +94,9 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, views::dispatch_topology( topoNode, [&](const auto&, auto topoView) { - GenerateQuadratureMesh generator(topoView, - coordsetView); + axom::bump::GenerateQuadratureMesh generator( + topoView, + coordsetView); generator.setAllocatorID(allocatorID); generator.execute(topoNode, coordsetNode, diff --git a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp index 50dc3a3b8f..434853c989 100644 --- a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp +++ b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp @@ -13,7 +13,6 @@ #include "axom/core.hpp" #include "axom/quest/IntersectionShaper.hpp" #include "axom/quest/SamplingShaper.hpp" - #include "axom/quest/detail/shaping/MappedZoneUtilities.hpp" #include "axom/quest/detail/shaping/shaping_helpers.hpp" #include "axom/quest/util/mesh_helpers.hpp" #include "axom/bump/utilities/conduit_memory.hpp" @@ -152,27 +151,6 @@ conduit::Node makeQuadMesh(const std::string& topoName = "mesh") return mesh; } -conduit::Node makeHexMesh(const std::string& topoName = "mesh") -{ - conduit::Node mesh; - - mesh["coordsets/coords/type"] = "explicit"; - const axom::Array x {{0., 1., 0., 1., 0., 1., 0., 1.}}; - const axom::Array y {{0., 0., 1., 1., 0., 0., 1., 1.}}; - const axom::Array z {{0., 0., 0., 0., 1., 1., 1., 1.}}; - setNodeValues(mesh["coordsets/coords/values/x"], x.view()); - setNodeValues(mesh["coordsets/coords/values/y"], y.view()); - setNodeValues(mesh["coordsets/coords/values/z"], z.view()); - - mesh["topologies"][topoName]["type"] = "unstructured"; - mesh["topologies"][topoName]["coordset"] = "coords"; - mesh["topologies"][topoName]["elements/shape"] = "hex"; - const axom::Array connectivity {{0, 1, 3, 2, 4, 5, 7, 6}}; - setNodeValues(mesh["topologies"][topoName]["elements/connectivity"], connectivity.view()); - - return mesh; -} - conduit::Node makeDistortedQuadMesh(const std::string& topoName = "mesh") { conduit::Node mesh; @@ -213,185 +191,6 @@ conduit::Node makeStructuredQuadMesh(const std::string& topoName = "mesh") } // namespace -TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_quad_mesh) -{ - conduit::Node mesh = makeQuadMesh(); - - int sampleResolution[] = {2, 3}; - axom::quest::shaping::generateQuadraturePointMesh( - mesh, - "mesh", - axom::execution_space::allocatorID(), - axom::ArrayView {sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); - - conduit::Node info; - EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); - - const conduit::Node& quadTopo = mesh["topologies/quadrature_points"]; - EXPECT_EQ(quadTopo["type"].as_string(), "unstructured"); - EXPECT_EQ(quadTopo["coordset"].as_string(), "quadrature_points"); - EXPECT_EQ(quadTopo["elements/shape"].as_string(), "point"); - - namespace utils = axom::bump::utilities; - const auto connView = utils::make_array_view( - mesh["topologies/quadrature_points/elements/connectivity"]); - const auto sizesView = - utils::make_array_view(mesh["topologies/quadrature_points/elements/sizes"]); - const auto offsetsView = - utils::make_array_view(mesh["topologies/quadrature_points/elements/offsets"]); - const auto originalElementsView = - utils::make_array_view(mesh["fields/originalElements/values"]); - const auto quadratureWeightsView = - utils::make_array_view(mesh["fields/quadratureWeights/values"]); - const auto physicalQuadratureWeightsView = - utils::make_array_view(mesh["fields/quadraturePhysicalWeights/values"]); - - const axom::Array expectedX {{0., 1., 0., 1., 0., 1.}}; - const axom::Array expectedY {{0., 0., 0.5, 0.5, 1., 1.}}; - const axom::Array expectedConn {{0, 1, 2, 3, 4, 5}}; - const axom::Array expectedSizes {{1, 1, 1, 1, 1, 1}}; - const axom::Array expectedOffsets {{0, 1, 2, 3, 4, 5}}; - const axom::Array expectedOriginalElements {{0, 0, 0, 0, 0, 0}}; - const axom::Array expectedWeights { - {1. / 12., 1. / 12., 1. / 3., 1. / 3., 1. / 12., 1. / 12.}}; - - axom::bump::views::dispatch_explicit_coordset( - mesh["coordsets/quadrature_points"], - [&](auto coordsetView) { - for(axom::IndexType i = 0; i < expectedX.size(); ++i) - { - EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-12); - EXPECT_NEAR(coordsetView[i][1], expectedY[i], 1e-12); - } - }); - EXPECT_TRUE(compareArrayView(expectedConn.view(), connView)); - EXPECT_TRUE(compareArrayView(expectedSizes.view(), sizesView)); - EXPECT_TRUE(compareArrayView(expectedOffsets.view(), offsetsView)); - EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); - for(axom::IndexType i = 0; i < expectedWeights.size(); ++i) - { - EXPECT_NEAR(expectedWeights[i], quadratureWeightsView[i], 1e-12); - EXPECT_NEAR(expectedWeights[i], physicalQuadratureWeightsView[i], 1e-12); - } -} - -TEST(quest_blueprint_quadrature_mesh, generate_open_uniform_hex_mesh) -{ - conduit::Node mesh = makeHexMesh(); - - int sampleResolution[3] = {2, 1, 2}; - axom::quest::shaping::generateQuadraturePointMesh( - mesh, - "mesh", - axom::execution_space::allocatorID(), - axom::ArrayView {sampleResolution, 3}, - axom::numerics::QuadratureType::OpenUniform); - - conduit::Node info; - EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); - - EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/type")) << mesh.to_yaml(); - EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/values/x")) << mesh.to_yaml(); - EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/values/y")) << mesh.to_yaml(); - EXPECT_TRUE(mesh.has_path("coordsets/quadrature_points/values/z")) << mesh.to_yaml(); - - namespace utils = axom::bump::utilities; - const auto originalElementsView = - utils::make_array_view(mesh["fields/originalElements/values"]); - const auto quadratureWeightsView = - utils::make_array_view(mesh["fields/quadratureWeights/values"]); - const auto physicalQuadratureWeightsView = - utils::make_array_view(mesh["fields/quadraturePhysicalWeights/values"]); - - const axom::Array expectedX {{1. / 3., 2. / 3., 1. / 3., 2. / 3.}}; - const axom::Array expectedY {{0.5, 0.5, 0.5, 0.5}}; - const axom::Array expectedZ {{1. / 3., 1. / 3., 2. / 3., 2. / 3.}}; - const axom::Array expectedOriginalElements {{0, 0, 0, 0}}; - const axom::Array expectedWeights {{0.25, 0.25, 0.25, 0.25}}; - - axom::bump::views::dispatch_explicit_coordset( - mesh["coordsets/quadrature_points"], - [&](auto coordsetView) { - for(axom::IndexType i = 0; i < expectedX.size(); ++i) - { - EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-6); - EXPECT_NEAR(coordsetView[i][1], expectedY[i], 1e-6); - EXPECT_NEAR(coordsetView[i][2], expectedZ[i], 1e-6); - } - }); - EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); - for(axom::IndexType i = 0; i < expectedWeights.size(); ++i) - { - EXPECT_NEAR(expectedWeights[i], quadratureWeightsView[i], 1e-12); - EXPECT_NEAR(expectedWeights[i], physicalQuadratureWeightsView[i], 1e-12); - } -} - -TEST(quest_blueprint_quadrature_mesh, generate_closed_uniform_structured_quad_mesh) -{ - conduit::Node mesh = makeStructuredQuadMesh(); - - int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateQuadraturePointMesh( - mesh, - "mesh", - axom::execution_space::allocatorID(), - axom::ArrayView {sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); - - conduit::Node info; - EXPECT_TRUE(conduit::blueprint::mesh::verify(mesh, info)) << info.to_yaml(); - - ASSERT_TRUE(mesh.has_path("coordsets/quadrature_points/values/x")); - ASSERT_TRUE(mesh.has_path("fields/originalElements/values")); - - namespace utils = axom::bump::utilities; - const auto originalElementsView = - utils::make_array_view(mesh["fields/originalElements/values"]); - - const axom::Array expectedX {{0., 1., 0., 1.}}; - const axom::Array expectedY {{0., 0., 1., 1.}}; - const axom::Array expectedOriginalElements {{0, 0, 0, 0}}; - - axom::bump::views::dispatch_explicit_coordset( - mesh["coordsets/quadrature_points"], - [&](auto coordsetView) { - for(axom::IndexType i = 0; i < expectedX.size(); ++i) - { - EXPECT_NEAR(coordsetView[i][0], expectedX[i], 1e-12); - EXPECT_NEAR(coordsetView[i][1], expectedY[i], 1e-12); - } - }); - EXPECT_TRUE(compareArrayView(expectedOriginalElements.view(), originalElementsView)); -} - -TEST(quest_blueprint_quadrature_mesh, mapped_zone_helper_computes_distorted_quad_measure_factor) -{ - conduit::Node mesh = makeDistortedQuadMesh(); - double lowerFactor = -1.; - double upperFactor = -1.; - - axom::bump::views::dispatch_explicit_coordset(mesh["coordsets/coords"], [&](auto coordsetView) { - axom::bump::views::dispatch_unstructured_topology( - mesh["topologies/mesh"], - [&](const auto&, auto topoView) { - const auto zone = topoView.zone(0); - lowerFactor = axom::quest::shaping::detail::computePhysicalMeasureFactor(zone, - coordsetView, - 1. / 3., - 1. / 3.); - upperFactor = axom::quest::shaping::detail::computePhysicalMeasureFactor(zone, - coordsetView, - 1. / 3., - 2. / 3.); - }); - }); - - EXPECT_NEAR(lowerFactor, 5. / 3., 1e-12); - EXPECT_NEAR(upperFactor, 4. / 3., 1e-12); -} - TEST(quest_blueprint_quadrature_mesh, state_wrapper_generation_is_idempotent) { conduit::Node mesh = makeQuadMesh(); @@ -634,6 +433,7 @@ TEST(quest_blueprint_quadrature_mesh, blueprint_shapers_support_nondefault_topol EXPECT_EQ(intersectionShaper.blueprintMeshDimension(), 2); } +#ifdef AXOM_USE_C2C TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_quad_blueprint_mesh) { const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); @@ -688,6 +488,7 @@ dimensions: 2 computeStructuredMaterialMeasure(shaper.internalMesh(), "vol_frac_circleMat", cellArea); EXPECT_NEAR(totalArea, 3.14159265358979323846, 5e-2); } +#endif TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_hex_blueprint_mesh) { From 985fc15140d2fecde95b45f191474f1c8a7d3ba5 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 8 Jun 2026 15:39:56 -0700 Subject: [PATCH 34/72] Moved some utility functions to separate file. --- src/axom/bump/CMakeLists.txt | 1 + src/axom/bump/GenerateQuadratureMesh.hpp | 164 +--------------- src/axom/bump/MappedZoneUtilities.hpp | 237 +++++++++++++++++++++++ 3 files changed, 239 insertions(+), 163 deletions(-) create mode 100644 src/axom/bump/MappedZoneUtilities.hpp diff --git a/src/axom/bump/CMakeLists.txt b/src/axom/bump/CMakeLists.txt index 00cc3b1f87..b9832fcf91 100644 --- a/src/axom/bump/CMakeLists.txt +++ b/src/axom/bump/CMakeLists.txt @@ -77,6 +77,7 @@ set(bump_headers GenerateQuadratureMesh.hpp HashNaming.hpp IndexingPolicies.hpp + MappedZoneUtilities.hpp MakePointMesh.hpp MakePolyhedralTopology.hpp MakeUnstructured.hpp diff --git a/src/axom/bump/GenerateQuadratureMesh.hpp b/src/axom/bump/GenerateQuadratureMesh.hpp index a2456e85d0..5ee85b1869 100644 --- a/src/axom/bump/GenerateQuadratureMesh.hpp +++ b/src/axom/bump/GenerateQuadratureMesh.hpp @@ -11,10 +11,10 @@ #if defined(AXOM_USE_CONDUIT) + #include "axom/bump/MappedZoneUtilities.hpp" #include "axom/bump/utilities/blueprint_utilities.hpp" #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/core.hpp" - #include "axom/core/numerics/Determinants.hpp" #include "axom/core/numerics/quadrature.hpp" #include "axom/primal.hpp" #include "axom/sidre/core/ConduitMemory.hpp" @@ -38,168 +38,6 @@ namespace bump namespace detail { -template -AXOM_HOST_DEVICE primal::Point -mapToPhysicalPoint(const ShapeType& zone, const CoordsetView& coordsetView, double u, double v) -{ - using PointType = primal::Point; - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - - const double n0 = (1.0 - u) * (1.0 - v); - const double n1 = u * (1.0 - v); - const double n2 = u * v; - const double n3 = (1.0 - u) * v; - - PointType pt; - for(int d = 0; d < 2; ++d) - { - pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d]; - } - return pt; -} - -template -AXOM_HOST_DEVICE primal::Point -mapToPhysicalPoint(const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v, - double w) -{ - using PointType = primal::Point; - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - const auto p4 = coordsetView[zone.getId(4)]; - const auto p5 = coordsetView[zone.getId(5)]; - const auto p6 = coordsetView[zone.getId(6)]; - const auto p7 = coordsetView[zone.getId(7)]; - - const double a = 1.0 - u; - const double b = 1.0 - v; - const double c = 1.0 - w; - - const double n0 = a * b * c; - const double n1 = u * b * c; - const double n2 = u * v * c; - const double n3 = a * v * c; - const double n4 = a * b * w; - const double n5 = u * b * w; - const double n6 = u * v * w; - const double n7 = a * v * w; - - PointType pt; - for(int d = 0; d < 3; ++d) - { - pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d] + n4 * p4[d] + n5 * p5[d] + - n6 * p6[d] + n7 * p7[d]; - } - return pt; -} - -template -AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v) -{ - using VectorType = primal::Vector; - - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - - const double du0 = -(1.0 - v); - const double du1 = (1.0 - v); - const double du2 = v; - const double du3 = -v; - - const double dv0 = -(1.0 - u); - const double dv1 = -u; - const double dv2 = u; - const double dv3 = 1.0 - u; - - VectorType dxdu; - VectorType dxdv; - for(int d = 0; d < 2; ++d) - { - dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d]; - dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d]; - } - - return axom::utilities::abs(axom::numerics::determinant(dxdu[0], dxdv[0], dxdu[1], dxdv[1])); -} - -template -AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v, - double w) -{ - using VectorType = primal::Vector; - - const auto p0 = coordsetView[zone.getId(0)]; - const auto p1 = coordsetView[zone.getId(1)]; - const auto p2 = coordsetView[zone.getId(2)]; - const auto p3 = coordsetView[zone.getId(3)]; - const auto p4 = coordsetView[zone.getId(4)]; - const auto p5 = coordsetView[zone.getId(5)]; - const auto p6 = coordsetView[zone.getId(6)]; - const auto p7 = coordsetView[zone.getId(7)]; - - const double a = 1.0 - u; - const double b = 1.0 - v; - const double c = 1.0 - w; - - const double du0 = -b * c; - const double du1 = b * c; - const double du2 = v * c; - const double du3 = -v * c; - const double du4 = -b * w; - const double du5 = b * w; - const double du6 = v * w; - const double du7 = -v * w; - - const double dv0 = -a * c; - const double dv1 = -u * c; - const double dv2 = u * c; - const double dv3 = a * c; - const double dv4 = -a * w; - const double dv5 = -u * w; - const double dv6 = u * w; - const double dv7 = a * w; - - const double dw0 = -a * b; - const double dw1 = -u * b; - const double dw2 = -u * v; - const double dw3 = -a * v; - const double dw4 = a * b; - const double dw5 = u * b; - const double dw6 = u * v; - const double dw7 = a * v; - - VectorType dxdu; - VectorType dxdv; - VectorType dxdw; - for(int d = 0; d < 3; ++d) - { - dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d] + du4 * p4[d] + du5 * p5[d] + - du6 * p6[d] + du7 * p7[d]; - dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d] + dv4 * p4[d] + dv5 * p5[d] + - dv6 * p6[d] + dv7 * p7[d]; - dxdw[d] = dw0 * p0[d] + dw1 * p1[d] + dw2 * p2[d] + dw3 * p3[d] + dw4 * p4[d] + dw5 * p5[d] + - dw6 * p6[d] + dw7 * p7[d]; - } - - return axom::utilities::abs(VectorType::scalar_triple_product(dxdu, dxdv, dxdw)); -} - inline int quadraturePointCount(const numerics::QuadratureRule& ruleX, const numerics::QuadratureRule& ruleY, const numerics::QuadratureRule& ruleZ, diff --git a/src/axom/bump/MappedZoneUtilities.hpp b/src/axom/bump/MappedZoneUtilities.hpp new file mode 100644 index 0000000000..f666d11c3b --- /dev/null +++ b/src/axom/bump/MappedZoneUtilities.hpp @@ -0,0 +1,237 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef AXOM_BUMP_MAPPED_ZONE_UTILITIES_HPP_ +#define AXOM_BUMP_MAPPED_ZONE_UTILITIES_HPP_ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/core/numerics/Determinants.hpp" +#include "axom/primal.hpp" + +namespace axom +{ +namespace bump +{ +namespace detail +{ + +/*! + * \file MappedZoneUtilities.hpp + * + * \brief Helper functions for mapping low-order quad and hex zones from + * reference-space quadrature coordinates into physical space. + */ + +/*! + * \brief Map a quadrilateral's reference-space point to physical space. + * + * \param zone The quadrilateral zone to map. + * \param coordsetView The coordset view that provides zone vertices. + * \param u The first reference-space coordinate in [0, 1]. + * \param v The second reference-space coordinate in [0, 1]. + * + * \return The physical point corresponding to \a (u, v). + */ +template +AXOM_HOST_DEVICE primal::Point +mapToPhysicalPoint(const ShapeType& zone, const CoordsetView& coordsetView, double u, double v) +{ + using PointType = primal::Point; + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + + const double n0 = (1.0 - u) * (1.0 - v); + const double n1 = u * (1.0 - v); + const double n2 = u * v; + const double n3 = (1.0 - u) * v; + + PointType pt; + for(int d = 0; d < 2; ++d) + { + pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d]; + } + return pt; +} + +/*! + * \brief Map a hexahedron's reference-space point to physical space. + * + * \param zone The hexahedral zone to map. + * \param coordsetView The coordset view that provides zone vertices. + * \param u The first reference-space coordinate in [0, 1]. + * \param v The second reference-space coordinate in [0, 1]. + * \param w The third reference-space coordinate in [0, 1]. + * + * \return The physical point corresponding to \a (u, v, w). + */ +template +AXOM_HOST_DEVICE primal::Point +mapToPhysicalPoint(const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v, + double w) +{ + using PointType = primal::Point; + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + const auto p4 = coordsetView[zone.getId(4)]; + const auto p5 = coordsetView[zone.getId(5)]; + const auto p6 = coordsetView[zone.getId(6)]; + const auto p7 = coordsetView[zone.getId(7)]; + + const double a = 1.0 - u; + const double b = 1.0 - v; + const double c = 1.0 - w; + + const double n0 = a * b * c; + const double n1 = u * b * c; + const double n2 = u * v * c; + const double n3 = a * v * c; + const double n4 = a * b * w; + const double n5 = u * b * w; + const double n6 = u * v * w; + const double n7 = a * v * w; + + PointType pt; + for(int d = 0; d < 3; ++d) + { + pt[d] = n0 * p0[d] + n1 * p1[d] + n2 * p2[d] + n3 * p3[d] + n4 * p4[d] + n5 * p5[d] + + n6 * p6[d] + n7 * p7[d]; + } + return pt; +} + +/*! + * \brief Compute the quadrilateral area scale at a reference-space point. + * + * \param zone The quadrilateral zone to evaluate. + * \param coordsetView The coordset view that provides zone vertices. + * \param u The first reference-space coordinate in [0, 1]. + * \param v The second reference-space coordinate in [0, 1]. + * + * \return The absolute Jacobian determinant at \a (u, v). + */ +template +AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v) +{ + using VectorType = primal::Vector; + + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + + const double du0 = -(1.0 - v); + const double du1 = (1.0 - v); + const double du2 = v; + const double du3 = -v; + + const double dv0 = -(1.0 - u); + const double dv1 = -u; + const double dv2 = u; + const double dv3 = 1.0 - u; + + VectorType dxdu; + VectorType dxdv; + for(int d = 0; d < 2; ++d) + { + dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d]; + dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d]; + } + + return axom::utilities::abs(axom::numerics::determinant(dxdu[0], dxdv[0], dxdu[1], dxdv[1])); +} + +/*! + * \brief Compute the hexahedral volume scale at a reference-space point. + * + * \param zone The hexahedral zone to evaluate. + * \param coordsetView The coordset view that provides zone vertices. + * \param u The first reference-space coordinate in [0, 1]. + * \param v The second reference-space coordinate in [0, 1]. + * \param w The third reference-space coordinate in [0, 1]. + * + * \return The absolute Jacobian determinant at \a (u, v, w). + */ +template +AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, + const CoordsetView& coordsetView, + double u, + double v, + double w) +{ + using VectorType = primal::Vector; + + const auto p0 = coordsetView[zone.getId(0)]; + const auto p1 = coordsetView[zone.getId(1)]; + const auto p2 = coordsetView[zone.getId(2)]; + const auto p3 = coordsetView[zone.getId(3)]; + const auto p4 = coordsetView[zone.getId(4)]; + const auto p5 = coordsetView[zone.getId(5)]; + const auto p6 = coordsetView[zone.getId(6)]; + const auto p7 = coordsetView[zone.getId(7)]; + + const double a = 1.0 - u; + const double b = 1.0 - v; + const double c = 1.0 - w; + + const double du0 = -b * c; + const double du1 = b * c; + const double du2 = v * c; + const double du3 = -v * c; + const double du4 = -b * w; + const double du5 = b * w; + const double du6 = v * w; + const double du7 = -v * w; + + const double dv0 = -a * c; + const double dv1 = -u * c; + const double dv2 = u * c; + const double dv3 = a * c; + const double dv4 = -a * w; + const double dv5 = -u * w; + const double dv6 = u * w; + const double dv7 = a * w; + + const double dw0 = -a * b; + const double dw1 = -u * b; + const double dw2 = -u * v; + const double dw3 = -a * v; + const double dw4 = a * b; + const double dw5 = u * b; + const double dw6 = u * v; + const double dw7 = a * v; + + VectorType dxdu; + VectorType dxdv; + VectorType dxdw; + for(int d = 0; d < 3; ++d) + { + dxdu[d] = du0 * p0[d] + du1 * p1[d] + du2 * p2[d] + du3 * p3[d] + du4 * p4[d] + du5 * p5[d] + + du6 * p6[d] + du7 * p7[d]; + dxdv[d] = dv0 * p0[d] + dv1 * p1[d] + dv2 * p2[d] + dv3 * p3[d] + dv4 * p4[d] + dv5 * p5[d] + + dv6 * p6[d] + dv7 * p7[d]; + dxdw[d] = dw0 * p0[d] + dw1 * p1[d] + dw2 * p2[d] + dw3 * p3[d] + dw4 * p4[d] + dw5 * p5[d] + + dw6 * p6[d] + dw7 * p7[d]; + } + + return axom::utilities::abs(VectorType::scalar_triple_product(dxdu, dxdv, dxdw)); +} + +} // namespace detail +} // namespace bump +} // namespace axom + +#endif From 758ac16b396b092c689780604302aa501739281d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 8 Jun 2026 16:07:20 -0700 Subject: [PATCH 35/72] Fixes, comments. --- src/axom/bump/GenerateQuadratureMesh.hpp | 70 +++++++++++++++++-- src/axom/quest/SamplingShaper.cpp | 12 ++++ .../quest/detail/shaping/PrimitiveSampler.hpp | 2 +- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/axom/bump/GenerateQuadratureMesh.hpp b/src/axom/bump/GenerateQuadratureMesh.hpp index 5ee85b1869..cb721aa6d6 100644 --- a/src/axom/bump/GenerateQuadratureMesh.hpp +++ b/src/axom/bump/GenerateQuadratureMesh.hpp @@ -9,8 +9,6 @@ #include "axom/config.hpp" -#if defined(AXOM_USE_CONDUIT) - #include "axom/bump/MappedZoneUtilities.hpp" #include "axom/bump/utilities/blueprint_utilities.hpp" #include "axom/bump/utilities/conduit_memory.hpp" @@ -38,6 +36,16 @@ namespace bump namespace detail { +/*! + * \brief Returns the number of tensor-product quadrature points per zone. + * + * \param ruleX Quadrature rule in the reference x direction. + * \param ruleY Quadrature rule in the reference y direction. + * \param ruleZ Quadrature rule in the reference z direction. + * \param dim Mesh dimension, expected to be 2 or 3. + * + * \return The number of quadrature points generated for one zone. + */ inline int quadraturePointCount(const numerics::QuadratureRule& ruleX, const numerics::QuadratureRule& ruleY, const numerics::QuadratureRule& ruleZ, @@ -49,6 +57,19 @@ inline int quadraturePointCount(const numerics::QuadratureRule& ruleX, } // namespace detail +/*! + * \brief Builds a Blueprint point mesh of quadrature samples over a low-order + * quad or hex mesh. + * + * The generated mesh stores one point element for each tensor-product + * quadrature point in each zone. It also creates fields that record the + * originating zone and both reference-space and physical-space quadrature + * weights for each generated point. + * + * \tparam ExecSpace Execution space used to populate the output arrays. + * \tparam TopologyView View type for the source topology. + * \tparam CoordsetView View type for the source coordset. + */ template class GenerateQuadratureMesh { @@ -56,18 +77,34 @@ class GenerateQuadratureMesh using CoordsetType = typename CoordsetView::value_type; using PointType = primal::Point; + /*! + * \brief Bundles the topology and coordset views for device access. + */ struct ViewPackage { TopologyView topologyView; CoordsetView coordsetView; }; + /*! + * \brief Constructs a quadrature-mesh generator over the supplied mesh views. + * + * \param topologyView View of the source Blueprint topology. + * \param coordsetView View of the source Blueprint coordset. + */ GenerateQuadratureMesh(const TopologyView& topologyView, const CoordsetView& coordsetView) : m_topologyView(topologyView) , m_coordsetView(coordsetView) , m_allocator_id(axom::execution_space::allocatorID()) { } + /*! + * \brief Sets the allocator used for output Conduit arrays. + * + * The allocator must be valid and accessible from \a ExecSpace. + * + * \param allocator_id Axom allocator identifier for output storage. + */ void setAllocatorID(int allocator_id) { SLIC_ERROR_IF(!axom::isValidAllocatorID(allocator_id), "Invalid allocator id."); @@ -76,8 +113,35 @@ class GenerateQuadratureMesh m_allocator_id = allocator_id; } + /*! + * \brief Returns the allocator used for output Conduit arrays. + * + * \return The Axom allocator identifier for output storage. + */ int getAllocatorID() const { return m_allocator_id; } + /*! + * \brief Generates a Blueprint point mesh containing quadrature samples. + * + * The output mesh contains explicit point coordinates, a point-element + * topology, the source zone index for each point, and both reference and + * physical quadrature weights. + * + * \param n_topology Source topology node. Currently unused because the + * topology is accessed through \a m_topologyView. + * \param n_coordset Source coordset node, used to preserve axis naming. + * \param outputTopologyName Name of the generated point topology. + * \param outputCoordsetName Name of the generated explicit coordset. + * \param originalElementsFieldName Name of the field storing source zone ids. + * \param quadratureWeightsFieldName Name of the field storing reference + * quadrature weights. + * \param quadraturePhysicalWeightsFieldName Name of the field storing + * physical quadrature weights. + * \param ruleX Quadrature rule in the reference x direction. + * \param ruleY Quadrature rule in the reference y direction. + * \param ruleZ Quadrature rule in the reference z direction. + * \param n_output Output Blueprint node populated with the generated mesh. + */ void execute(const conduit::Node& AXOM_UNUSED_PARAM(n_topology), const conduit::Node& n_coordset, const std::string& outputTopologyName, @@ -234,5 +298,3 @@ class GenerateQuadratureMesh } // namespace axom #endif - -#endif diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 0763fb1981..158a876ecd 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -25,6 +25,18 @@ void SamplingShaper::setQuadratureType(axom::numerics::QuadratureType qtype) SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); } } + else if(m_mfem_state != nullptr) + { + // Check that the value is valid. + if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) + { + m_quadratureType = qtype; + } + else + { + SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); + } + } } void SamplingShaper::setSamplingResolution(int sampleRes) diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index c8962e8e0b..c7b36eabb8 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -181,7 +181,7 @@ class PrimitiveSampler "A projector callback function is required when FromDim != ToDim"); auto* mesh = mfemState.m_dc->GetMesh(); - SLIC_ERROR_IF(mesh != nullptr, "No input mesh"); + SLIC_ERROR_IF(mesh == nullptr, "No input mesh"); auto& inoutQFuncs = mfemState.m_inoutShapeQFuncs; SLIC_ASSERT(inoutQFuncs.Has("positions")); From de18011c6ebc0783e1e701af3cb721c8310a5014 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 8 Jun 2026 16:07:51 -0700 Subject: [PATCH 36/72] make style --- src/axom/bump/GenerateQuadratureMesh.hpp | 32 +++++++------- .../tests/bump_blueprint_quadrature_mesh.cpp | 22 ++++------ .../shaping/shaping_helpers_blueprint.cpp | 42 ++++++++++--------- .../shaping/shaping_helpers_blueprint.hpp | 22 +++++----- src/axom/quest/examples/shaping_driver.cpp | 3 +- .../tests/quest_blueprint_quadrature_mesh.cpp | 4 +- 6 files changed, 59 insertions(+), 66 deletions(-) diff --git a/src/axom/bump/GenerateQuadratureMesh.hpp b/src/axom/bump/GenerateQuadratureMesh.hpp index cb721aa6d6..85cc470477 100644 --- a/src/axom/bump/GenerateQuadratureMesh.hpp +++ b/src/axom/bump/GenerateQuadratureMesh.hpp @@ -9,18 +9,18 @@ #include "axom/config.hpp" - #include "axom/bump/MappedZoneUtilities.hpp" - #include "axom/bump/utilities/blueprint_utilities.hpp" - #include "axom/bump/utilities/conduit_memory.hpp" - #include "axom/core.hpp" - #include "axom/core/numerics/quadrature.hpp" - #include "axom/primal.hpp" - #include "axom/sidre/core/ConduitMemory.hpp" - #include "axom/slic.hpp" - - #include - #include - #include +#include "axom/bump/MappedZoneUtilities.hpp" +#include "axom/bump/utilities/blueprint_utilities.hpp" +#include "axom/bump/utilities/conduit_memory.hpp" +#include "axom/core.hpp" +#include "axom/core/numerics/quadrature.hpp" +#include "axom/primal.hpp" +#include "axom/sidre/core/ConduitMemory.hpp" +#include "axom/slic.hpp" + +#include +#include +#include /*! * \file GenerateQuadratureMesh.hpp @@ -260,12 +260,8 @@ class GenerateQuadratureMesh else { pt = detail::mapToPhysicalPoint(zone, deviceViews.coordsetView, xi, eta, zeta); - physicalMeasure = detail::computePhysicalMeasureFactor( - zone, - deviceViews.coordsetView, - xi, - eta, - zeta); + physicalMeasure = + detail::computePhysicalMeasureFactor(zone, deviceViews.coordsetView, xi, eta, zeta); } const double referenceWeight = wx * wy * wz; diff --git a/src/axom/bump/tests/bump_blueprint_quadrature_mesh.cpp b/src/axom/bump/tests/bump_blueprint_quadrature_mesh.cpp index 3660126a5a..fffec29c4d 100644 --- a/src/axom/bump/tests/bump_blueprint_quadrature_mesh.cpp +++ b/src/axom/bump/tests/bump_blueprint_quadrature_mesh.cpp @@ -144,15 +144,12 @@ void generateQuadratureMesh(conduit::Node& mesh, namespace views = axom::bump::views; const int allocatorId = axom::execution_space::allocatorID(); - auto ruleX = - axom::numerics::get_quadrature_rule(quadratureType, sampleResolution[0], allocatorId); - auto ruleY = - axom::numerics::get_quadrature_rule(quadratureType, sampleResolution[1], allocatorId); + auto ruleX = axom::numerics::get_quadrature_rule(quadratureType, sampleResolution[0], allocatorId); + auto ruleY = axom::numerics::get_quadrature_rule(quadratureType, sampleResolution[1], allocatorId); const int nz = sampleResolution.size() > 2 ? sampleResolution[2] : 1; auto ruleZ = axom::numerics::get_quadrature_rule(quadratureType, nz, allocatorId); - const conduit::Node& topoNode = - mesh.fetch_existing("topologies").fetch_existing(topologyName); + const conduit::Node& topoNode = mesh.fetch_existing("topologies").fetch_existing(topologyName); const conduit::Node& coordsetNode = mesh.fetch_existing("coordsets").fetch_existing(topoNode.fetch_existing("coordset").as_string()); @@ -165,10 +162,9 @@ void generateQuadratureMesh(conduit::Node& mesh, views::dispatch_topology( topoNode, [&](const auto&, auto topoView) { - axom::bump::GenerateQuadratureMesh - generator(topoView, coordsetView); + axom::bump::GenerateQuadratureMesh generator( + topoView, + coordsetView); generator.setAllocatorID(allocatorId); generator.execute(topoNode, coordsetNode, @@ -342,10 +338,8 @@ TEST(bump_blueprint_quadrature_mesh, mapped_zone_helper_computes_distorted_quad_ axom::bump::views::dispatch_explicit_coordset(mesh["coordsets/coords"], [&](auto coordsetView) { axom::bump::views::dispatch_topology(mesh["topologies/mesh"], [&](const auto&, auto topoView) { const auto zone = topoView.zone(0); - lowerFactor = - axom::bump::detail::computePhysicalMeasureFactor(zone, coordsetView, 0.5, 0.0); - upperFactor = - axom::bump::detail::computePhysicalMeasureFactor(zone, coordsetView, 0.5, 1.0); + lowerFactor = axom::bump::detail::computePhysicalMeasureFactor(zone, coordsetView, 0.5, 0.0); + upperFactor = axom::bump::detail::computePhysicalMeasureFactor(zone, coordsetView, 0.5, 1.0); }); }); diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 0dbb325fe1..e091813335 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -8,15 +8,15 @@ #if defined(AXOM_USE_CONDUIT) -#include "conduit_blueprint_mesh.hpp" + #include "conduit_blueprint_mesh.hpp" -#if defined(AXOM_USE_BUMP) - #include "axom/bump/GenerateQuadratureMesh.hpp" - #include "axom/bump/views/dispatch_topology.hpp" - #include "axom/bump/views/dispatch_unstructured_topology.hpp" -#endif + #if defined(AXOM_USE_BUMP) + #include "axom/bump/GenerateQuadratureMesh.hpp" + #include "axom/bump/views/dispatch_topology.hpp" + #include "axom/bump/views/dispatch_unstructured_topology.hpp" + #endif -#include + #include namespace axom { @@ -77,7 +77,7 @@ std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) return ""; } -#if defined(AXOM_USE_BUMP) + #if defined(AXOM_USE_BUMP) template void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, const conduit::Node& coordsetNode, @@ -89,7 +89,9 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, conduit::Node& meshNode) { namespace views = axom::bump::views; - constexpr int SupportedShapes = (CoordsetView::dimension() == 2) ? views::select_shapes(views::Quad_ShapeID) : views::select_shapes(views::Hex_ShapeID); + constexpr int SupportedShapes = (CoordsetView::dimension() == 2) + ? views::select_shapes(views::Quad_ShapeID) + : views::select_shapes(views::Hex_ShapeID); views::dispatch_topology( topoNode, @@ -111,7 +113,7 @@ void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, meshNode); }); } -#endif + #endif } // namespace @@ -120,7 +122,7 @@ std::string getBlueprintCellShape(const conduit::Node& topoNode) return getBlueprintCellShapeImpl(topoNode); } -#if defined(AXOM_USE_BUMP) + #if defined(AXOM_USE_BUMP) void printRegisteredFieldNames(const BlueprintState& bpState, const std::set& knownMaterials, VolFracSampling AXOM_UNUSED_PARAM(vfSampling), @@ -246,12 +248,12 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, int selectedAllocatorID = allocatorID; if(!axom::execution_space::usesAllocId(selectedAllocatorID) && !axom::execution_space::usesAllocId(selectedAllocatorID) - #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && !axom::execution_space::usesAllocId(selectedAllocatorID) - #endif - #if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #endif + #if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) && !axom::execution_space::usesAllocId(selectedAllocatorID) - #endif + #endif ) { selectedAllocatorID = axom::execution_space::allocatorID(); @@ -263,7 +265,7 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, auto ruleZ = getBlueprintQuadratureRule(quadratureType, nz, selectedAllocatorID); axom::bump::views::dispatch_explicit_coordset(coordsetNode, [&](auto coordsetView) { - #if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #if defined(AXOM_USE_HIP) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) if(axom::execution_space::usesAllocId(selectedAllocatorID)) { buildBlueprintQuadratureMesh(topoNode, @@ -276,8 +278,8 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, bpMeshNode); return; } - #endif - #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) + #endif + #if defined(AXOM_USE_CUDA) && defined(AXOM_USE_RAJA) && defined(AXOM_USE_UMPIRE) if(axom::execution_space::usesAllocId(selectedAllocatorID)) { buildBlueprintQuadratureMesh(topoNode, @@ -290,7 +292,7 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, bpMeshNode); return; } - #endif + #endif if(axom::execution_space::usesAllocId(selectedAllocatorID)) { buildBlueprintQuadratureMesh(topoNode, @@ -529,7 +531,7 @@ conduit::Node* cloneInOutFunction(const conduit::Node* node) return new conduit::Node(*node); } -#endif // defined(AXOM_USE_BUMP) + #endif // defined(AXOM_USE_BUMP) } // end namespace shaping } // end namespace quest diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index fe7118fa1b..e6762c3d82 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -11,14 +11,14 @@ #if defined(AXOM_USE_CONDUIT) -#include "axom/fmt.hpp" + #include "axom/fmt.hpp" -#if defined(AXOM_USE_BUMP) - #include "axom/bump/utilities/conduit_memory.hpp" - #include "axom/bump/views/dispatch_coordset.hpp" -#endif + #if defined(AXOM_USE_BUMP) + #include "axom/bump/utilities/conduit_memory.hpp" + #include "axom/bump/views/dispatch_coordset.hpp" + #endif -#include "conduit_node.hpp" + #include "conduit_node.hpp" #include #include @@ -107,7 +107,7 @@ struct BlueprintState return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] : nullptr; } -#if defined(AXOM_USE_BUMP) + #if defined(AXOM_USE_BUMP) conduit::Node* createMaterialFunction(const std::string& name) { constexpr const char* quadratureTopologyName = "quadrature_points"; @@ -137,10 +137,10 @@ struct BlueprintState return &fieldNode; } -#endif + #endif }; -#if defined(AXOM_USE_BUMP) + #if defined(AXOM_USE_BUMP) /*! * \brief Print the registered field names in the \a bpState. * @@ -301,7 +301,7 @@ void sampleInOutField(const std::string& shapeName, using CoordsetView = typename std::decay::type; // Limit to handling coordsets whose dimensions match FromDim. - if constexpr (CoordsetView::dimension() == FromDim) + if constexpr(CoordsetView::dimension() == FromDim) { numQueryPoints = coordsetView.size(); valuesNode.set(conduit::DataType::float64(numQueryPoints)); @@ -331,7 +331,7 @@ void sampleInOutField(const std::string& shapeName, static_cast(numQueryPoints / timer.elapsed()))); } -#endif // defined(AXOM_USE_BUMP) + #endif // defined(AXOM_USE_BUMP) } // end namespace shaping } // end namespace quest diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index c305ad00a0..fce5fe2a69 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -776,7 +776,8 @@ int main(int argc, char** argv) originalBlueprintMeshGroup, "mesh"); #else - SLIC_ERROR_ROOT("inline_mesh_blueprint requires Axom to be configured with Conduit and Bump."); + SLIC_ERROR_ROOT( + "inline_mesh_blueprint requires Axom to be configured with Conduit and Bump."); #endif } else diff --git a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp index 434853c989..a1c1761ee1 100644 --- a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp +++ b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp @@ -433,7 +433,7 @@ TEST(quest_blueprint_quadrature_mesh, blueprint_shapers_support_nondefault_topol EXPECT_EQ(intersectionShaper.blueprintMeshDimension(), 2); } -#ifdef AXOM_USE_C2C + #ifdef AXOM_USE_C2C TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_quad_blueprint_mesh) { const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); @@ -488,7 +488,7 @@ dimensions: 2 computeStructuredMaterialMeasure(shaper.internalMesh(), "vol_frac_circleMat", cellArea); EXPECT_NEAR(totalArea, 3.14159265358979323846, 5e-2); } -#endif + #endif TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_hex_blueprint_mesh) { From 88baa88ec6f040adf022664fd8fc927021e574dd Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 8 Jun 2026 16:47:30 -0700 Subject: [PATCH 37/72] Removed unused vars. --- src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index e091813335..4e95c67784 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -28,12 +28,6 @@ namespace shaping namespace { -constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; -constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; -constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; -constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; -constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; - numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureType quadratureType, int npts, int allocatorID) From 226d107bbddd0aefbce9f83c9a2a2fc038e18f47 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 8 Jun 2026 17:37:53 -0700 Subject: [PATCH 38/72] Fixed some compilation issues when a subset of Axom libraries or dependencies are enabled. --- src/axom/quest/SamplingShaper.cpp | 6 +- .../shaping/shaping_helpers_blueprint.cpp | 32 +- .../shaping/shaping_helpers_blueprint.hpp | 2 +- src/axom/quest/examples/CMakeLists.txt | 4 +- src/axom/quest/examples/shaping_driver.cpp | 4 +- src/axom/quest/tests/CMakeLists.txt | 15 - .../tests/quest_blueprint_quadrature_mesh.cpp | 547 ------------------ 7 files changed, 30 insertions(+), 580 deletions(-) delete mode 100644 src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 158a876ecd..e6ff9d225f 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -13,6 +13,7 @@ namespace quest void SamplingShaper::setQuadratureType(axom::numerics::QuadratureType qtype) { +#if defined(AXOM_USE_CONDUIT) if(m_bp_state != nullptr) { // For Blueprint, we rely on Axom quadrature types and not all are implemented yet. @@ -25,7 +26,9 @@ void SamplingShaper::setQuadratureType(axom::numerics::QuadratureType qtype) SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); } } - else if(m_mfem_state != nullptr) +#endif +#if defined(AXOM_USE_MFEM) + if(m_mfem_state != nullptr) { // Check that the value is valid. if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) @@ -37,6 +40,7 @@ void SamplingShaper::setQuadratureType(axom::numerics::QuadratureType qtype) SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); } } +#endif } void SamplingShaper::setSamplingResolution(int sampleRes) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 4e95c67784..8dcf380ab5 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -28,18 +28,6 @@ namespace shaping namespace { -numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureType quadratureType, - int npts, - int allocatorID) -{ - SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); - SLIC_ERROR_IF( - !axom::numerics::is_supported_quadrature_type(quadratureType), - axom::fmt::format("Quadrature type {} is not yet supported for Blueprint quadrature meshes.", - static_cast(quadratureType))); - - return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); -} std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) { const std::string topoType = topoNode.fetch_existing("type").as_string(); @@ -72,6 +60,26 @@ std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) } #if defined(AXOM_USE_BUMP) + +constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; +constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; +constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; +constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; +constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; + +numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureType quadratureType, + int npts, + int allocatorID) +{ + SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); + SLIC_ERROR_IF( + !axom::numerics::is_supported_quadrature_type(quadratureType), + axom::fmt::format("Quadrature type {} is not yet supported for Blueprint quadrature meshes.", + static_cast(quadratureType))); + + return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); +} + template void buildBlueprintQuadratureMesh(const conduit::Node& topoNode, const conduit::Node& coordsetNode, diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index e6762c3d82..2ed0ca2f58 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -40,7 +40,7 @@ namespace shaping */ std::string getBlueprintCellShape(const conduit::Node& topoNode); -/// A class that contains Blueprint mesh and field state for SamplingShaper class. +/// A class that contains Blueprint mesh and field state for Shaper class. struct BlueprintState { virtual ~BlueprintState() = default; diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index cd355694fb..327bfbbecf 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -192,7 +192,7 @@ if(AXOM_HAS_MFEM_WITH_MPI AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) set_tests_properties(${_testname} PROPERTIES PASS_REGULAR_EXPRESSION "Volume of material 'steel' is 65.") - if(CONDUIT_FOUND) + if(CONDUIT_FOUND AND AXOM_ENABLE_BUMP) set(_testname quest_shaping_driver_ex_sampling_circles_blueprint) axom_add_test( NAME ${_testname} @@ -369,7 +369,7 @@ if(AXOM_HAS_MFEM_WITH_MPI AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) endif() endif() # Blueprint-only shaping test -if(CONDUIT_FOUND AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE AND AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) +if(CONDUIT_FOUND AND AXOM_ENABLE_BUMP AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE AND AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) set(_nranks 1) set(_testname quest_shaping_driver_ex_sampling_blueprint_3D) axom_add_test(NAME ${_testname} diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index fce5fe2a69..c40f7ac658 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -777,7 +777,7 @@ int main(int argc, char** argv) "mesh"); #else SLIC_ERROR_ROOT( - "inline_mesh_blueprint requires Axom to be configured with Conduit and Bump."); + "Using inline_mesh_blueprint with SamplingShaper requires Axom to be configured with Conduit+Bump."); #endif } else @@ -801,7 +801,7 @@ int main(int argc, char** argv) originalBlueprintMeshGroup, "mesh"); #else - SLIC_ERROR_ROOT("inline_mesh_blueprint requires Axom to be configured with Conduit."); + SLIC_ERROR_ROOT("Using inline_mesh_blueprint with IntersectionShaper requires Axom to be configured with Conduit."); #endif } else diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index bef0d73806..d30e7f40ab 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -87,21 +87,6 @@ if(CONDUIT_FOUND AND AXOM_DATA_DIR) endif() -if(CONDUIT_FOUND AND MFEM_FOUND AND AXOM_ENABLE_SIDRE) - axom_add_executable( - NAME quest_blueprint_quadrature_mesh_test - SOURCES quest_blueprint_quadrature_mesh.cpp - OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} - DEPENDS_ON ${quest_tests_depends} conduit::conduit mfem - FOLDER axom/quest/tests - ) - - axom_add_test( - NAME quest_blueprint_quadrature_mesh - COMMAND quest_blueprint_quadrature_mesh_test - ) -endif() - #------------------------------------------------------------------------------ # Tests that use MFEM when available #------------------------------------------------------------------------------ diff --git a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp b/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp deleted file mode 100644 index a1c1761ee1..0000000000 --- a/src/axom/quest/tests/quest_blueprint_quadrature_mesh.cpp +++ /dev/null @@ -1,547 +0,0 @@ -// Copyright (c) Lawrence Livermore National Security, LLC and other -// Axom Project Contributors. See top-level LICENSE and COPYRIGHT -// files for dates and other details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -#include "axom/config.hpp" - -#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_MFEM) - - #include "gtest/gtest.h" - - #include "axom/core.hpp" - #include "axom/quest/IntersectionShaper.hpp" - #include "axom/quest/SamplingShaper.hpp" - #include "axom/quest/detail/shaping/shaping_helpers.hpp" - #include "axom/quest/util/mesh_helpers.hpp" - #include "axom/bump/utilities/conduit_memory.hpp" - #include "axom/bump/views/dispatch_coordset.hpp" - #include "axom/bump/views/dispatch_unstructured_topology.hpp" - #include "axom/sidre.hpp" - - #include "conduit.hpp" - #include "conduit_blueprint.hpp" - -namespace -{ - -class BlueprintIntersectionShaperForTest : public axom::quest::IntersectionShaper -{ -public: - using axom::quest::IntersectionShaper::IntersectionShaper; - - void ensureInternalMeshIsUnstructured() { ensureBlueprintMeshIsUnstructured(); } - int blueprintMeshDimension() { return getBlueprintMeshDimension(); } - - std::string internalTopologyType() const - { - return m_bp_state->m_internal_node.fetch_existing("topologies") - .fetch_existing(m_bp_state->m_topology_name) - .fetch_existing("type") - .as_string(); - } -}; - -class BlueprintSamplingShaperForTest : public axom::quest::SamplingShaper -{ -public: - using axom::quest::SamplingShaper::SamplingShaper; - - const conduit::Node& internalMesh() const { return m_bp_state->m_internal_node; } -}; - -const std::string unit_circle_contour = - "piece = circle(origin=(0cm, 0cm), radius=1cm, start=0deg, end=360deg)"; - -template -bool compareArrayView(axom::ArrayView lhs, axom::ArrayView rhs) -{ - if(lhs.size() != rhs.size()) - { - return false; - } - - for(axom::IndexType i = 0; i < lhs.size(); ++i) - { - if(lhs[i] != rhs[i]) - { - return false; - } - } - return true; -} - -void setNodeValues(conduit::Node& node, axom::ArrayView values) -{ - node.set(conduit::DataType::float64(values.size())); - auto* data = node.as_float64_ptr(); - for(axom::IndexType i = 0; i < values.size(); ++i) - { - data[i] = values[i]; - } -} - -void setNodeValues(conduit::Node& node, axom::ArrayView values) -{ - node.set(conduit::DataType::index_t(values.size())); - auto* data = node.as_index_t_ptr(); - for(axom::IndexType i = 0; i < values.size(); ++i) - { - data[i] = values[i]; - } -} - -void runSamplingShaper(BlueprintSamplingShaperForTest& shaper, const axom::klee::ShapeSet& shapeSet) -{ - auto getShapeDim = [](const auto& shape) { - static std::map formatDim { - {"c2c", axom::klee::Dimensions::Two}, - {"stl", axom::klee::Dimensions::Three}}; - - const auto& shapeDim = shape.getGeometry().getInputDimensions(); - const auto& formatStr = shape.getGeometry().getFormat(); - return formatDim.find(formatStr) != formatDim.end() ? formatDim[formatStr] : shapeDim; - }; - - for(const auto& shape : shapeSet.getShapes()) - { - const auto shapeDim = getShapeDim(shape); - shaper.loadShape(shape); - shaper.prepareShapeQuery(shapeDim, shape); - shaper.runShapeQuery(shape); - shaper.applyReplacementRules(shape); - shaper.finalizeShapeQuery(); - } - - shaper.adjustVolumeFractions(); -} - -double computeStructuredMaterialMeasure(const conduit::Node& mesh, - const std::string& vfFieldName, - double cellMeasure) -{ - namespace utils = axom::bump::utilities; - const auto values = utils::make_array_view( - mesh.fetch_existing("fields").fetch_existing(vfFieldName).fetch_existing("values")); - double total = 0.; - for(axom::IndexType i = 0; i < values.size(); ++i) - { - total += values[i] * cellMeasure; - } - return total; -} - -conduit::Node makeQuadMesh(const std::string& topoName = "mesh") -{ - conduit::Node mesh; - - mesh["coordsets/coords/type"] = "explicit"; - const axom::Array x {{0., 1., 0., 1.}}; - const axom::Array y {{0., 0., 1., 1.}}; - setNodeValues(mesh["coordsets/coords/values/x"], x.view()); - setNodeValues(mesh["coordsets/coords/values/y"], y.view()); - - mesh["topologies"][topoName]["type"] = "unstructured"; - mesh["topologies"][topoName]["coordset"] = "coords"; - mesh["topologies"][topoName]["elements/shape"] = "quad"; - const axom::Array connectivity {{0, 1, 3, 2}}; - setNodeValues(mesh["topologies"][topoName]["elements/connectivity"], connectivity.view()); - - return mesh; -} - -conduit::Node makeDistortedQuadMesh(const std::string& topoName = "mesh") -{ - conduit::Node mesh; - - mesh["coordsets/coords/type"] = "explicit"; - const axom::Array x {{0., 2., 0., 1.}}; - const axom::Array y {{0., 0., 1., 1.}}; - setNodeValues(mesh["coordsets/coords/values/x"], x.view()); - setNodeValues(mesh["coordsets/coords/values/y"], y.view()); - - mesh["topologies"][topoName]["type"] = "unstructured"; - mesh["topologies"][topoName]["coordset"] = "coords"; - mesh["topologies"][topoName]["elements/shape"] = "quad"; - const axom::Array connectivity {{0, 1, 3, 2}}; - setNodeValues(mesh["topologies"][topoName]["elements/connectivity"], connectivity.view()); - - return mesh; -} - -conduit::Node makeStructuredQuadMesh(const std::string& topoName = "mesh") -{ - conduit::Node mesh; - - mesh["coordsets/coords/type"] = "explicit"; - const axom::Array x {{0., 1., 0., 1.}}; - const axom::Array y {{0., 0., 1., 1.}}; - setNodeValues(mesh["coordsets/coords/values/x"], x.view()); - setNodeValues(mesh["coordsets/coords/values/y"], y.view()); - - mesh["topologies"][topoName]["type"] = "structured"; - mesh["topologies"][topoName]["coordset"] = "coords"; - mesh["topologies"][topoName]["elements/shape"] = "quad"; - mesh["topologies"][topoName]["elements/dims/i"] = 1; - mesh["topologies"][topoName]["elements/dims/j"] = 1; - - return mesh; -} - -} // namespace - -TEST(quest_blueprint_quadrature_mesh, state_wrapper_generation_is_idempotent) -{ - conduit::Node mesh = makeQuadMesh(); - - axom::quest::shaping::BlueprintState bpState; - bpState.m_allocator_id = axom::execution_space::allocatorID(); - bpState.m_topology_name = "mesh"; - bpState.m_internal_node = mesh; - - int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateSamplingPositions(bpState, - axom::ArrayView {sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); - - ASSERT_TRUE(bpState.m_internal_node.has_path("fields/originalElements/values")); - conduit::Node savedOriginalElements; - savedOriginalElements.set_external(bpState.m_internal_node["fields/originalElements/values"]); - - axom::quest::shaping::generateSamplingPositions(bpState, - axom::ArrayView {sampleResolution, 2}, - axom::numerics::QuadratureType::OpenUniform); - - EXPECT_TRUE(bpState.m_internal_node.has_path("topologies/quadrature_points")); - - namespace utils = axom::bump::utilities; - const auto originalElementsView = utils::make_array_view( - bpState.m_internal_node["fields/originalElements/values"]); - const auto savedOriginalElementsView = - utils::make_array_view(savedOriginalElements); - - EXPECT_TRUE(compareArrayView(savedOriginalElementsView, originalElementsView)); -} - -TEST(quest_blueprint_quadrature_mesh, blueprint_state_field_helpers_support_replacement_ops) -{ - conduit::Node mesh = makeQuadMesh(); - - axom::quest::shaping::BlueprintState bpState; - bpState.m_allocator_id = axom::execution_space::allocatorID(); - bpState.m_topology_name = "mesh"; - bpState.m_internal_node = mesh; - - int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateSamplingPositions(bpState, - axom::ArrayView {sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); - - conduit::Node& shapeField = bpState.m_internal_node["fields/inout_shape"]; - shapeField["association"] = "element"; - shapeField["topology"] = "quadrature_points"; - const axom::Array shapeValues {{1., 0., 1., 0.}}; - setNodeValues(shapeField["values"], shapeValues.view()); - - conduit::Node* materialField = bpState.createMaterialFunction("mat_inout_void"); - ASSERT_NE(materialField, nullptr); - const axom::Array materialValues {{1., 1., 0., 0.}}; - setNodeValues((*materialField)["values"], materialValues.view()); - - conduit::Node* shapeCopy = - axom::quest::shaping::cloneInOutFunction(bpState.getShapeFunction("inout_shape")); - ASSERT_NE(shapeCopy, nullptr); - - axom::quest::shaping::replaceMaterial(shapeCopy, materialField, true); - - namespace utils = axom::bump::utilities; - const auto replacedView = utils::make_array_view((*materialField)["values"]); - const axom::Array expectedReplaced {{0., 1., 0., 0.}}; - EXPECT_TRUE(compareArrayView(expectedReplaced.view(), replacedView)); - - conduit::Node* createdField = bpState.createMaterialFunction("mat_inout_created"); - ASSERT_NE(createdField, nullptr); - axom::quest::shaping::copyShapeIntoMaterial(shapeCopy, createdField, false); - - const auto copiedView = utils::make_array_view((*createdField)["values"]); - EXPECT_TRUE(compareArrayView(shapeValues.view(), copiedView)); - - delete shapeCopy; -} - -TEST(quest_blueprint_quadrature_mesh, compute_volume_fractions_for_material_from_quadrature_weights) -{ - conduit::Node mesh = makeQuadMesh(); - - axom::quest::shaping::BlueprintState bpState; - bpState.m_allocator_id = axom::execution_space::allocatorID(); - bpState.m_topology_name = "mesh"; - bpState.m_internal_node = mesh; - - int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateSamplingPositions(bpState, - axom::ArrayView {sampleResolution, 2}, - axom::numerics::QuadratureType::ClosedUniform); - - conduit::Node* materialField = bpState.createMaterialFunction("mat_inout_test"); - ASSERT_NE(materialField, nullptr); - - const axom::Array materialValues {{1., 0., 1., 0.}}; - setNodeValues((*materialField)["values"], materialValues.view()); - - axom::quest::shaping::computeVolumeFractionsForMaterial(bpState, "mat_inout_test"); - - ASSERT_TRUE(bpState.m_internal_node.has_path("fields/vol_frac_test/values")); - namespace utils = axom::bump::utilities; - const auto volFracValues = - utils::make_array_view(bpState.m_internal_node["fields/vol_frac_test/values"]); - - ASSERT_EQ(volFracValues.size(), 1); - EXPECT_NEAR(volFracValues[0], 0.5, 1e-12); -} - -TEST(quest_blueprint_quadrature_mesh, - compute_volume_fractions_for_material_uses_physical_quadrature_weights) -{ - conduit::Node mesh = makeDistortedQuadMesh(); - - axom::quest::shaping::BlueprintState bpState; - bpState.m_allocator_id = axom::execution_space::allocatorID(); - bpState.m_topology_name = "mesh"; - bpState.m_internal_node = mesh; - - int sampleResolution[] = {2, 2}; - axom::quest::shaping::generateSamplingPositions(bpState, - axom::ArrayView {sampleResolution, 2}, - axom::numerics::QuadratureType::OpenUniform); - - conduit::Node* materialField = bpState.createMaterialFunction("mat_inout_test"); - ASSERT_NE(materialField, nullptr); - - const axom::Array materialValues {{1., 1., 0., 0.}}; - setNodeValues((*materialField)["values"], materialValues.view()); - - axom::quest::shaping::computeVolumeFractionsForMaterial(bpState, "mat_inout_test"); - - ASSERT_TRUE(bpState.m_internal_node.has_path("fields/vol_frac_test/values")); - namespace utils = axom::bump::utilities; - const auto volFracValues = - utils::make_array_view(bpState.m_internal_node["fields/vol_frac_test/values"]); - - ASSERT_EQ(volFracValues.size(), 1); - EXPECT_NEAR(volFracValues[0], 5. / 9., 1e-12); -} - -TEST(quest_blueprint_quadrature_mesh, sampling_shaper_constructs_from_blueprint_node_and_group) -{ - conduit::Node mesh = makeQuadMesh(); - axom::klee::ShapeSet shapeSet; - const auto policy = axom::runtime_policy::Policy::seq; - const int allocatorId = axom::policyToDefaultAllocatorID(policy); - - axom::quest::SamplingShaper nodeShaper(policy, allocatorId, shapeSet, mesh, "mesh"); - - axom::sidre::DataStore ds; - auto* meshGroup = ds.getRoot()->createGroup("mesh"); - ASSERT_TRUE(meshGroup->importConduitTree(mesh)); - - axom::quest::SamplingShaper groupShaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); -} - -TEST(quest_blueprint_quadrature_mesh, sampling_shaper_verify_accepts_structured_quad_mesh) -{ - conduit::Node mesh = makeStructuredQuadMesh(); - axom::klee::ShapeSet shapeSet; - const auto policy = axom::runtime_policy::Policy::seq; - const int allocatorId = axom::policyToDefaultAllocatorID(policy); - - axom::quest::SamplingShaper nodeShaper(policy, allocatorId, shapeSet, mesh, "mesh"); - std::string whyBad; - EXPECT_TRUE(nodeShaper.verifyInputMesh(whyBad)) << whyBad; - - axom::sidre::DataStore ds; - auto* meshGroup = ds.getRoot()->createGroup("mesh"); - ASSERT_TRUE(meshGroup->importConduitTree(mesh)); - - axom::quest::SamplingShaper groupShaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); - whyBad.clear(); - EXPECT_TRUE(groupShaper.verifyInputMesh(whyBad)) << whyBad; -} - -TEST(quest_blueprint_quadrature_mesh, intersection_shaper_verify_accepts_structured_quad_mesh) -{ - conduit::Node mesh = makeStructuredQuadMesh(); - axom::klee::ShapeSet shapeSet; - const auto policy = axom::runtime_policy::Policy::seq; - const int allocatorId = axom::policyToDefaultAllocatorID(policy); - - BlueprintIntersectionShaperForTest nodeShaper(policy, allocatorId, shapeSet, mesh, "mesh"); - std::string whyBad; - EXPECT_TRUE(nodeShaper.verifyInputMesh(whyBad)) << whyBad; - - axom::sidre::DataStore ds; - auto* meshGroup = ds.getRoot()->createGroup("mesh"); - ASSERT_TRUE(meshGroup->importConduitTree(mesh)); - - BlueprintIntersectionShaperForTest groupShaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); - whyBad.clear(); - EXPECT_TRUE(groupShaper.verifyInputMesh(whyBad)) << whyBad; -} - -TEST(quest_blueprint_quadrature_mesh, - intersection_shaper_lazy_conversion_keeps_original_sidre_group_structured) -{ - conduit::Node mesh = makeStructuredQuadMesh(); - axom::klee::ShapeSet shapeSet; - const auto policy = axom::runtime_policy::Policy::seq; - const int allocatorId = axom::policyToDefaultAllocatorID(policy); - - axom::sidre::DataStore ds; - auto* meshGroup = ds.getRoot()->createGroup("mesh"); - ASSERT_TRUE(meshGroup->importConduitTree(mesh)); - - BlueprintIntersectionShaperForTest shaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); - EXPECT_EQ(shaper.internalTopologyType(), "structured"); - - shaper.ensureInternalMeshIsUnstructured(); - EXPECT_EQ(shaper.internalTopologyType(), "unstructured"); - - conduit::Node originalMeshNode; - ASSERT_TRUE(meshGroup->createNativeLayout(originalMeshNode)); - EXPECT_EQ(originalMeshNode["topologies/mesh/type"].as_string(), "structured"); -} - -TEST(quest_blueprint_quadrature_mesh, blueprint_shapers_support_nondefault_topology_names) -{ - conduit::Node mesh = makeStructuredQuadMesh("cells"); - axom::klee::ShapeSet shapeSet; - const auto policy = axom::runtime_policy::Policy::seq; - const int allocatorId = axom::policyToDefaultAllocatorID(policy); - - axom::quest::SamplingShaper samplingShaper(policy, allocatorId, shapeSet, mesh, "cells"); - std::string whyBad; - EXPECT_TRUE(samplingShaper.verifyInputMesh(whyBad)) << whyBad; - - BlueprintIntersectionShaperForTest intersectionShaper(policy, - allocatorId, - shapeSet, - mesh, - "cells"); - whyBad.clear(); - EXPECT_TRUE(intersectionShaper.verifyInputMesh(whyBad)) << whyBad; - EXPECT_EQ(intersectionShaper.blueprintMeshDimension(), 2); -} - - #ifdef AXOM_USE_C2C -TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_quad_blueprint_mesh) -{ - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); - const auto policy = axom::runtime_policy::Policy::seq; - const int allocatorId = axom::policyToDefaultAllocatorID(policy); - - axom::sidre::DataStore ds; - auto* meshGroup = ds.getRoot()->createGroup("mesh"); - meshGroup->setDefaultArrayAllocator(allocatorId); - - const axom::primal::BoundingBox bbox {{-2., -2.}, {2., 2.}}; - const axom::NumericArray resolution {64, 64}; - axom::quest::util::make_structured_blueprint_box_mesh_2d(meshGroup, - bbox, - resolution, - "mesh", - "coords", - policy); - - axom::utilities::filesystem::TempFile contourFile(testname, ".contour"); - contourFile.write(unit_circle_contour); - - const std::string shapeYaml = axom::fmt::format(R"( -dimensions: 2 - -shapes: -- name: circle_shape - material: circleMat - geometry: - format: c2c - path: {} -)", - contourFile.getPath()); - - axom::utilities::filesystem::TempFile shapeFile(testname, ".yaml"); - shapeFile.write(shapeYaml); - - const axom::klee::ShapeSet shapeSet = axom::klee::readShapeSet(shapeFile.getPath()); - - BlueprintSamplingShaperForTest shaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); - std::string whyBad; - ASSERT_TRUE(shaper.verifyInputMesh(whyBad)) << whyBad; - - runSamplingShaper(shaper, shapeSet); - - conduit::Node info; - EXPECT_TRUE(conduit::blueprint::mesh::verify(shaper.internalMesh(), info)) << info.to_yaml(); - ASSERT_TRUE(shaper.internalMesh().has_path("fields/vol_frac_circleMat/values")); - - const double cellArea = bbox.range()[0] * bbox.range()[1] / (resolution[0] * resolution[1]); - const double totalArea = - computeStructuredMaterialMeasure(shaper.internalMesh(), "vol_frac_circleMat", cellArea); - EXPECT_NEAR(totalArea, 3.14159265358979323846, 5e-2); -} - #endif - -TEST(quest_blueprint_quadrature_mesh, sampling_shaper_shapes_structured_hex_blueprint_mesh) -{ - const auto& testname = ::testing::UnitTest::GetInstance()->current_test_info()->name(); - const auto policy = axom::runtime_policy::Policy::seq; - const int allocatorId = axom::policyToDefaultAllocatorID(policy); - - axom::sidre::DataStore ds; - auto* meshGroup = ds.getRoot()->createGroup("mesh"); - meshGroup->setDefaultArrayAllocator(allocatorId); - - const axom::primal::BoundingBox bbox {{-2., -2., -2.}, {2., 2., 2.}}; - const axom::NumericArray resolution {8, 8, 8}; - axom::quest::util::make_structured_blueprint_box_mesh_3d(meshGroup, - bbox, - resolution, - "mesh", - "coords", - policy); - - const std::string tetPath = axom::fmt::format("{}/quest/tetrahedron.stl", AXOM_DATA_DIR); - const std::string shapeYaml = axom::fmt::format(R"( -dimensions: 3 - -shapes: -- name: tet_shape - material: steel - geometry: - format: stl - path: {} -)", - tetPath); - - axom::utilities::filesystem::TempFile shapeFile(testname, ".yaml"); - shapeFile.write(shapeYaml); - - const axom::klee::ShapeSet shapeSet = axom::klee::readShapeSet(shapeFile.getPath()); - - BlueprintSamplingShaperForTest shaper(policy, allocatorId, shapeSet, meshGroup, "mesh"); - std::string whyBad; - ASSERT_TRUE(shaper.verifyInputMesh(whyBad)) << whyBad; - - runSamplingShaper(shaper, shapeSet); - - conduit::Node info; - EXPECT_TRUE(conduit::blueprint::mesh::verify(shaper.internalMesh(), info)) << info.to_yaml(); - ASSERT_TRUE(shaper.internalMesh().has_path("fields/vol_frac_steel/values")); - - const double cellVolume = bbox.range()[0] * bbox.range()[1] * bbox.range()[2] / - (resolution[0] * resolution[1] * resolution[2]); - const double totalVolume = - computeStructuredMaterialMeasure(shaper.internalMesh(), "vol_frac_steel", cellVolume); - EXPECT_NEAR(totalVolume, 8. / 3., 5e-2); -} - -#endif From a2ab091f70e7b36cc022d2f43968d27e5b568934 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 8 Jun 2026 17:39:09 -0700 Subject: [PATCH 39/72] make style --- src/axom/bump/MappedZoneUtilities.hpp | 6 +----- src/axom/quest/examples/shaping_driver.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/axom/bump/MappedZoneUtilities.hpp b/src/axom/bump/MappedZoneUtilities.hpp index f666d11c3b..73b2c6106f 100644 --- a/src/axom/bump/MappedZoneUtilities.hpp +++ b/src/axom/bump/MappedZoneUtilities.hpp @@ -72,11 +72,7 @@ mapToPhysicalPoint(const ShapeType& zone, const CoordsetView& coordsetView, doub */ template AXOM_HOST_DEVICE primal::Point -mapToPhysicalPoint(const ShapeType& zone, - const CoordsetView& coordsetView, - double u, - double v, - double w) +mapToPhysicalPoint(const ShapeType& zone, const CoordsetView& coordsetView, double u, double v, double w) { using PointType = primal::Point; const auto p0 = coordsetView[zone.getId(0)]; diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index c40f7ac658..4ad08c671b 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -777,7 +777,8 @@ int main(int argc, char** argv) "mesh"); #else SLIC_ERROR_ROOT( - "Using inline_mesh_blueprint with SamplingShaper requires Axom to be configured with Conduit+Bump."); + "Using inline_mesh_blueprint with SamplingShaper requires Axom to be configured with " + "Conduit+Bump."); #endif } else @@ -801,7 +802,9 @@ int main(int argc, char** argv) originalBlueprintMeshGroup, "mesh"); #else - SLIC_ERROR_ROOT("Using inline_mesh_blueprint with IntersectionShaper requires Axom to be configured with Conduit."); + SLIC_ERROR_ROOT( + "Using inline_mesh_blueprint with IntersectionShaper requires Axom to be configured with " + "Conduit."); #endif } else From 94980a9c411af4ee345ca28330ee2a201a4d408f Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 11 Jun 2026 17:34:09 -0700 Subject: [PATCH 40/72] Try increasing CI timeout for some jobs to see if it improves reliability. --- .github/workflows/ci-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 0371952443..d49e765d66 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -105,7 +105,7 @@ jobs: echo "compiler_image ${{ matrix.config.compiler_image }}" echo "host_config ${{ matrix.config.host_config }}" - name: Build and Test ${{ matrix.build_type }} - ${{ matrix.config.job_name }} - timeout-minutes: 80 + timeout-minutes: 100 run: | DO_BUILD=${{ matrix.config.do_build }} \ DO_BENCHMARKS=${{ matrix.config.do_benchmarks }} \ From da7d7c2077aadf859fda61bdb70f8b6e83bef452 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 12 Jun 2026 11:23:46 -0700 Subject: [PATCH 41/72] Use --resolution instead of --res in a quest test so it does not conflict with flux arguments. --- src/axom/quest/examples/CMakeLists.txt | 7 ++++--- src/axom/quest/examples/shaping_driver.cpp | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 8823032918..e1c2bb6f26 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -200,10 +200,11 @@ if(AXOM_HAS_MFEM_WITH_MPI AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) -i ${shaping_data_dir}/circles.yaml --method sampling --verbose - inline_mesh_blueprint --min -6 -6 --max 6 6 --res 25 25 -d 2 + inline_mesh_blueprint --min -6 -6 --max 6 6 --resolution 25 25 -d 2 NUM_MPI_TASKS ${_nranks}) + # Analytic area for annulus w/ outer/inner radii 5 and 2.5 is ~58.905 set_tests_properties(${_testname} PROPERTIES - PASS_REGULAR_EXPRESSION "Saved quadrature point mesh to 'shaping_quadrature'.") + PASS_REGULAR_EXPRESSION "Volume of material 'steel' is 58.") endif() set(_testname quest_shaping_driver_ex_sampling_balls_and_jacks) @@ -379,7 +380,7 @@ if(CONDUIT_FOUND AND AXOM_ENABLE_BUMP AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE --method sampling --sampling inout --background-material void - inline_mesh_blueprint --min -6 -6 -6 --max 6 6 6 --res 16 16 16 -d 3 + inline_mesh_blueprint --min -6 -6 -6 --max 6 6 6 --resolution 16 16 16 -d 3 --sampling-resolution 5 5 5 --quadrature-type gausslegendre NUM_MPI_TASKS ${_nranks}) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 0e42c17cd6..8803d97f02 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -398,7 +398,7 @@ struct Input ->expected(2, 3) ->required(); - inline_mesh_subcommand->add_option("--res, --resolution", boxResolution) + inline_mesh_subcommand->add_option("--res,--resolution", boxResolution) ->description("Resolution of the box mesh (i,j[,k])") ->expected(2, 3) ->required(); @@ -428,7 +428,7 @@ struct Input ->description("Max bounds for box mesh (x,y[,z])") ->expected(2, 3) ->required(); - inline_mesh_blueprint_subcommand->add_option("--res", boxResolution) + inline_mesh_blueprint_subcommand->add_option("--res,--resolution", boxResolution) ->description("Resolution of the box mesh (i,j[,k])") ->expected(2, 3) ->required(); From 2bb5e5a145803d4ad0e77a934170e52216c6dfe2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 12 Jun 2026 17:30:54 -0700 Subject: [PATCH 42/72] Refactoring to support Sidre better --- src/axom/quest/IntersectionShaper.hpp | 72 +++--- src/axom/quest/SamplingShaper.cpp | 22 +- src/axom/quest/Shaper.cpp | 37 +-- src/axom/quest/Shaper.hpp | 4 +- .../shaping/shaping_helpers_blueprint.cpp | 119 ++++----- .../shaping/shaping_helpers_blueprint.hpp | 228 ++++++++++++++---- 6 files changed, 316 insertions(+), 166 deletions(-) diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index c0742e5beb..7cd1135ff1 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -1996,14 +1996,15 @@ class IntersectionShaper : public Shaper } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) + if(m_bp_state != nullptr) { - auto fieldsGrp = m_bp_state->m_group_ptr->getGroup("fields"); - if(fieldsGrp != nullptr) + const conduit::Node& bpMeshNode = m_bp_state->getBlueprintMeshNode(); + if(bpMeshNode.has_path("fields")) { - for(auto& group : fieldsGrp->groups()) + const conduit::Node& fieldsNode = bpMeshNode.fetch_existing("fields"); + for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) { - std::string materialName = fieldNameToMaterialName(group.getName()); + std::string materialName = fieldNameToMaterialName(fieldsNode.child(i).name()); if(!materialName.empty()) { materialNames.emplace_back(materialName); @@ -2537,10 +2538,9 @@ class IntersectionShaper : public Shaper } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) + if(m_bp_state != nullptr) { - std::string fieldPath = axom::fmt::format("fields/{}", fieldName); - has = m_bp_state->m_group_ptr->hasGroup(fieldPath); + has = m_bp_state->hasField(fieldName); } #endif return has; @@ -2579,23 +2579,24 @@ class IntersectionShaper : public Shaper #endif #if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) + if(m_bp_state != nullptr) { std::string fieldPath = "fields/" + fieldName; auto dtype = conduit::DataType::float64(m_cellCount); - axom::sidre::View* valuesView = nullptr; - if(m_bp_state->m_group_ptr->hasGroup(fieldPath)) + if(m_bp_state->hasField(fieldName)) { - auto* fieldGrp = m_bp_state->m_group_ptr->getGroup(fieldPath); - valuesView = fieldGrp->getView("values"); - SLIC_ASSERT(fieldGrp->getView("association")->getString() == std::string("element")); - SLIC_ASSERT(fieldGrp->getView("topology")->getString() == m_bp_state->m_topology_name); - SLIC_ASSERT(valuesView->getNumElements() == m_cellCount); - SLIC_ASSERT(valuesView->getNode().dtype().id() == dtype.id()); + conduit::Node& fieldNode = m_bp_state->getField(fieldName); + SLIC_ASSERT(fieldNode.fetch_existing("association").as_string() == std::string("element")); + SLIC_ASSERT(fieldNode.fetch_existing("topology").as_string() == m_bp_state->m_topology_name); + + conduit::Node& valuesNode = fieldNode.fetch_existing("values"); + SLIC_ASSERT(valuesNode.dtype().id() == dtype.id()); + SLIC_ASSERT(valuesNode.dtype().number_of_elements() == m_cellCount); + rval = axom::ArrayView(valuesNode.as_double_ptr(), m_cellCount); } else { - if(m_bp_state->m_external_node_ptr != nullptr) + if(m_bp_state->isConduitBacked()) { /* If the computational mesh is an external conduit::Node, it @@ -2604,7 +2605,7 @@ class IntersectionShaper : public Shaper the allocator id for only array data. conduit::Node doesn't have this capability. */ - SLIC_WARNING_IF(m_bp_state->m_external_node_ptr != nullptr, + SLIC_WARNING_IF(m_bp_state->isConduitBacked(), "For a computational mesh in a conduit::Node, all" " output fields must be preallocated before shaping." " IntersectionShaper will NOT contravene the user's" @@ -2616,23 +2617,12 @@ class IntersectionShaper : public Shaper " with the mesh as a sidre::Group with your" " specific allocator id."); } - else + else if(m_bp_state->isSidreBacked()) { - constexpr axom::IndexType componentCount = 1; - axom::IndexType shape[2] = {m_cellCount, componentCount}; - auto* fieldGrp = m_bp_state->m_group_ptr->createGroup(fieldPath); - // valuesView = fieldGrp->createView("values"); - valuesView = - fieldGrp->createViewWithShape("values", axom::sidre::DataTypeId::FLOAT64_ID, 2, shape); - fieldGrp->createView("association")->setString("element"); - fieldGrp->createView("topology")->setString(m_bp_state->m_topology_name); - fieldGrp->createView("volume_dependent") - ->setString(std::string(volumeDependent ? "true" : "false")); - valuesView->allocate(); + rval = + m_bp_state->createField(fieldName, m_bp_state->m_topology_name, m_cellCount, true, volumeDependent); } } - - rval = axom::ArrayView(static_cast(valuesView->getVoidPtr()), m_cellCount); } #endif return rval; @@ -2662,7 +2652,7 @@ class IntersectionShaper : public Shaper } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) + if(m_bp_state != nullptr) { populateVertCoordsFromBlueprintMesh2D(vertCoords); } @@ -2708,7 +2698,7 @@ class IntersectionShaper : public Shaper } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) + if(m_bp_state != nullptr) { populateVertCoordsFromBlueprintMesh3D(vertCoords); } @@ -2752,8 +2742,7 @@ class IntersectionShaper : public Shaper // conduit::Node meshNode; // m_group_ptr->createNativeLayout(m_internal_node); - const conduit::Node& topoNode = m_bp_state->m_internal_node.fetch_existing("topologies") - .fetch_existing(m_bp_state->m_topology_name); + const conduit::Node& topoNode = m_bp_state->getBlueprintTopologyNode(); const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); // Assume unstructured and hexahedral @@ -2773,7 +2762,7 @@ class IntersectionShaper : public Shaper const auto* connPtr = static_cast(connNode.data_ptr()); axom::ArrayView conn(connPtr, m_cellCount, NUM_VERTS_PER_QUAD); - const conduit::Node& coordNode = m_bp_state->m_internal_node["coordsets"][coordsetName]; + const conduit::Node& coordNode = m_bp_state->getBlueprintCoordsetNode(coordsetName); const conduit::Node& coordValues = coordNode.fetch_existing("values"); axom::IndexType vertexCount = coordValues["x"].dtype().number_of_elements(); bool isInterleaved = conduit::blueprint::mcarray::is_interleaved(coordValues); @@ -2825,8 +2814,7 @@ class IntersectionShaper : public Shaper // conduit::Node meshNode; // m_group_ptr->createNativeLayout(m_internal_node); - const conduit::Node& topoNode = m_bp_state->m_internal_node.fetch_existing("topologies") - .fetch_existing(m_bp_state->m_topology_name); + const conduit::Node& topoNode = m_bp_state->getBlueprintTopologyNode(); const conduit::Node& topoCoordsetNode = topoNode.fetch_existing("coordset"); const std::string coordsetName = topoCoordsetNode.as_string(); @@ -2847,7 +2835,7 @@ class IntersectionShaper : public Shaper const auto* connPtr = static_cast(connNode.data_ptr()); axom::ArrayView conn(connPtr, m_cellCount, NUM_VERTS_PER_HEX); - const conduit::Node& coordNode = m_bp_state->m_internal_node["coordsets"][coordsetName]; + const conduit::Node& coordNode = m_bp_state->getBlueprintCoordsetNode(coordsetName); const conduit::Node& coordValues = coordNode.fetch_existing("values"); axom::IndexType vertexCount = coordValues["x"].dtype().number_of_elements(); bool isInterleaved = conduit::blueprint::mcarray::is_interleaved(coordValues); @@ -3002,7 +2990,7 @@ class IntersectionShaper : public Shaper } #endif #if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) + if(m_bp_state != nullptr) { dim = getBlueprintMeshDimension(); } diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index e6ff9d225f..2a4116c85d 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -167,20 +167,23 @@ void SamplingShaper::saveQuadraturePoints(const std::string& filename) const // Save the Blueprint quadrature point mesh as a Blueprint file. if(m_bp_state != nullptr) { - constexpr const char* quadName = "quadrature_points"; - const conduit::Node& bpMesh = m_bp_state->m_internal_node; + const conduit::Node& bpMesh = m_bp_state->getBlueprintMeshNode(); - if(!bpMesh.has_path(axom::fmt::format("coordsets/{}", quadName)) || - !bpMesh.has_path(axom::fmt::format("topologies/{}", quadName))) + if(!bpMesh.has_path(axom::fmt::format("coordsets/{}", + shaping::QUADRATURE_COORDSET_NAME)) || + !bpMesh.has_path(axom::fmt::format("topologies/{}", + shaping::QUADRATURE_TOPOLOGY_NAME))) { SLIC_WARNING("No Blueprint quadrature point mesh is available to save."); return; } - n_mesh["coordsets"][quadName].update( - bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", quadName))); - n_mesh["topologies"][quadName].update( - bpMesh.fetch_existing(axom::fmt::format("topologies/{}", quadName))); + n_mesh["coordsets"][shaping::QUADRATURE_COORDSET_NAME].update( + bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", + shaping::QUADRATURE_COORDSET_NAME))); + n_mesh["topologies"][shaping::QUADRATURE_TOPOLOGY_NAME].update( + bpMesh.fetch_existing(axom::fmt::format("topologies/{}", + shaping::QUADRATURE_TOPOLOGY_NAME))); if(bpMesh.has_path("fields")) { @@ -188,7 +191,8 @@ void SamplingShaper::saveQuadraturePoints(const std::string& filename) const for(conduit::index_t i = 0; i < fields.number_of_children(); ++i) { const conduit::Node& field = fields.child(i); - if(field.has_path("topology") && field.fetch_existing("topology").as_string() == quadName) + if(field.has_path("topology") && + field.fetch_existing("topology").as_string() == shaping::QUADRATURE_TOPOLOGY_NAME) { n_mesh["fields"][field.name()].update(field); } diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 315731daab..5f4c7bd7c9 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -103,9 +103,8 @@ Shaper::Shaper(RuntimePolicy execPolicy, #endif { m_bp_state = createBlueprintState(); - auto* internalGrp = m_dataStore.getRoot()->createGroup("internalGrp"); - internalGrp->setDefaultArrayAllocator(m_allocatorId); - m_bp_state->m_group_ptr = internalGrp->copyGroup(bpGrp); + bpGrp->setDefaultArrayAllocator(m_allocatorId); + m_bp_state->m_group_ptr = bpGrp; m_bp_state->m_allocator_id = m_allocatorId; m_bp_state->m_topology_name = resolveBlueprintTopologyName(bpGrp, topo); m_bp_state->m_external_node_ptr = nullptr; @@ -145,10 +144,6 @@ Shaper::Shaper(RuntimePolicy execPolicy, m_bp_state->m_topology_name = resolveBlueprintTopologyName(bpNode, topo); m_bp_state->m_external_node_ptr = &bpNode; - m_bp_state->m_group_ptr = m_dataStore.getRoot()->createGroup("internalGrp"); - m_bp_state->m_group_ptr->setDefaultArrayAllocator(m_allocatorId); - m_bp_state->m_group_ptr->importConduitTreeExternal(bpNode); - refreshBlueprintMeshState(); setFilePath(shapeSet.getPath()); @@ -304,22 +299,21 @@ std::string Shaper::resolveBlueprintTopologyName(const conduit::Node& bpMesh, void Shaper::refreshBlueprintMeshState() { SLIC_ASSERT(m_bp_state != nullptr); - SLIC_ASSERT(m_bp_state->m_group_ptr != nullptr); - m_bp_state->m_group_ptr->createNativeLayout(m_bp_state->m_internal_node); + m_bp_state->refreshBlueprintMeshNode(); m_cellCount = conduit::blueprint::mesh::topology::length(getBlueprintTopologyNode()); } const conduit::Node& Shaper::getBlueprintTopologyNode() const { SLIC_ASSERT(m_bp_state != nullptr); - return m_bp_state->m_internal_node.fetch_existing("topologies") - .fetch_existing(m_bp_state->m_topology_name); + return m_bp_state->getBlueprintTopologyNode(); } const conduit::Node& Shaper::getBlueprintCoordsetNode() const { + SLIC_ASSERT(m_bp_state != nullptr); const std::string coordsetName = getBlueprintTopologyNode().fetch_existing("coordset").as_string(); - return m_bp_state->m_internal_node.fetch_existing("coordsets").fetch_existing(coordsetName); + return m_bp_state->getBlueprintCoordsetNode(coordsetName); } std::string Shaper::getBlueprintCellShape() const @@ -347,13 +341,13 @@ bool Shaper::verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(std::string& w { bool rval = true; - if(m_bp_state != nullptr && m_bp_state->m_group_ptr != nullptr) + if(m_bp_state != nullptr) { conduit::Node info; // Conduit's verify should work even if m_internal_node has array data on // devices. because the verification doesn't dereference array data. // If this changes in the future, more care must be taken. - rval = conduit::blueprint::mesh::verify(m_bp_state->m_internal_node, info); + rval = conduit::blueprint::mesh::verify(m_bp_state->getBlueprintMeshNode(), info); if(rval) { const std::string topoType = getBlueprintTopologyNode().fetch_existing("type").as_string(); @@ -380,7 +374,7 @@ bool Shaper::verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(std::string& w void Shaper::ensureBlueprintMeshIsUnstructured() { - if(m_bp_state == nullptr || m_bp_state->m_group_ptr == nullptr) + if(m_bp_state == nullptr) { return; } @@ -392,6 +386,13 @@ void Shaper::ensureBlueprintMeshIsUnstructured() return; } + if(!m_bp_state->isSidreBacked()) + { + SLIC_ERROR( + "Structured Blueprint meshes backed by conduit::Node are not yet supported for " + "in-place conversion to unstructured topology."); + } + AXOM_ANNOTATE_SCOPE("Shaper::convertStructured"); const std::string shapeType = getBlueprintCellShape(); @@ -456,12 +457,14 @@ void Shaper::saveResults(bool AXOM_UNUSED_PARAM(extra)) { const std::string filename("shaping"); #if defined(CONDUIT_RELAY_MPI_ENABLED) - conduit::relay::mpi::io::blueprint::save_mesh(m_bp_state->m_internal_node, + conduit::relay::mpi::io::blueprint::save_mesh(m_bp_state->getBlueprintMeshNode(), filename, outputProtocol(), m_comm); #else - conduit::relay::io::blueprint::save_mesh(m_bp_state->m_internal_node, filename, outputProtocol()); + conduit::relay::io::blueprint::save_mesh(m_bp_state->getBlueprintMeshNode(), + filename, + outputProtocol()); #endif } #endif diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index c4d2f85bb2..499f432583 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -140,11 +140,11 @@ class Shaper shaping::BlueprintState* getBlueprintState() { return m_bp_state.get(); } conduit::Node* getBlueprintMeshNode() { - return m_bp_state != nullptr ? &m_bp_state->m_internal_node : nullptr; + return m_bp_state != nullptr ? &m_bp_state->getBlueprintMeshNode() : nullptr; } const conduit::Node* getBlueprintMeshNode() const { - return m_bp_state != nullptr ? &m_bp_state->m_internal_node : nullptr; + return m_bp_state != nullptr ? &m_bp_state->getBlueprintMeshNode() : nullptr; } #endif diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 8dcf380ab5..e29d000cb1 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -61,12 +61,6 @@ std::string getBlueprintCellShapeImpl(const conduit::Node& topoNode) #if defined(AXOM_USE_BUMP) -constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; -constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; -constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; -constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; -constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; - numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureType quadratureType, int npts, int allocatorID) @@ -145,9 +139,10 @@ void printRegisteredFieldNames(const BlueprintState& bpState, auto extractMatchingFields = [&](const std::string& prefix) { std::vector names; - if(bpState.m_internal_node.has_path("fields")) + const conduit::Node& bpMeshNode = bpState.getBlueprintMeshNode(); + if(bpMeshNode.has_path("fields")) { - const conduit::Node& fieldsNode = bpState.m_internal_node.fetch_existing("fields"); + const conduit::Node& fieldsNode = bpMeshNode.fetch_existing("fields"); for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) { const std::string name = fieldsNode.child(i).name(); @@ -162,9 +157,10 @@ void printRegisteredFieldNames(const BlueprintState& bpState, auto extractOtherFields = [&]() { std::vector names; - if(bpState.m_internal_node.has_path("fields")) + const conduit::Node& bpMeshNode = bpState.getBlueprintMeshNode(); + if(bpMeshNode.has_path("fields")) { - const conduit::Node& fieldsNode = bpState.m_internal_node.fetch_existing("fields"); + const conduit::Node& fieldsNode = bpMeshNode.fetch_existing("fields"); for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) { const std::string name = fieldsNode.child(i).name(); @@ -179,14 +175,15 @@ void printRegisteredFieldNames(const BlueprintState& bpState, return names; }; - const std::vector topologyNames = bpState.m_internal_node.has_path("topologies") - ? extractChildren(bpState.m_internal_node.fetch_existing("topologies")) + const conduit::Node& bpMeshNode = bpState.getBlueprintMeshNode(); + const std::vector topologyNames = bpMeshNode.has_path("topologies") + ? extractChildren(bpMeshNode.fetch_existing("topologies")) : std::vector {}; - const std::vector coordsetNames = bpState.m_internal_node.has_path("coordsets") - ? extractChildren(bpState.m_internal_node.fetch_existing("coordsets")) + const std::vector coordsetNames = bpMeshNode.has_path("coordsets") + ? extractChildren(bpMeshNode.fetch_existing("coordsets")) : std::vector {}; - const std::vector fieldNames = bpState.m_internal_node.has_path("fields") - ? extractChildren(bpState.m_internal_node.fetch_existing("fields")) + const std::vector fieldNames = bpMeshNode.has_path("fields") + ? extractChildren(bpMeshNode.fetch_existing("fields")) : std::vector {}; axom::fmt::memory_buffer out; @@ -213,7 +210,8 @@ void printRegisteredFieldNames(const BlueprintState& bpState, SLIC_INFO_ROOT(axom::fmt::to_string(out)); } -void generateQuadraturePointMesh(conduit::Node& bpMeshNode, +void generateQuadraturePointMesh(const conduit::Node& bpMeshNode, + conduit::Node& outputMeshNode, const std::string& topologyName, int allocatorID, axom::ArrayView sampleResolution, @@ -277,7 +275,7 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, ruleX, ruleY, ruleZ, - bpMeshNode); + outputMeshNode); return; } #endif @@ -291,7 +289,7 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, ruleX, ruleY, ruleZ, - bpMeshNode); + outputMeshNode); return; } #endif @@ -304,7 +302,7 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, ruleX, ruleY, ruleZ, - bpMeshNode); + outputMeshNode); return; } @@ -315,7 +313,7 @@ void generateQuadraturePointMesh(conduit::Node& bpMeshNode, ruleX, ruleY, ruleZ, - bpMeshNode); + outputMeshNode); }); } @@ -326,24 +324,40 @@ void generateSamplingPositions(BlueprintState& bpState, AXOM_ANNOTATE_SCOPE("generateSamplingPositions"); checkSampleResolution(bpState, sampleResolution, quadratureType); - if(bpState.m_internal_node.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) + conduit::Node& bpMeshNode = bpState.getBlueprintMeshNode(); + if(bpMeshNode.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME))) { return; } - generateQuadraturePointMesh(bpState.m_internal_node, - bpState.m_topology_name, - bpState.m_allocator_id, - sampleResolution, - quadratureType); + if(bpState.isSidreBacked()) + { + conduit::Node quadratureMesh; + generateQuadraturePointMesh(bpMeshNode, + quadratureMesh, + bpState.m_topology_name, + bpState.m_allocator_id, + sampleResolution, + quadratureType); + bpState.importQuadraturePointMesh(quadratureMesh); + } + else + { + generateQuadraturePointMesh(bpMeshNode, + bpMeshNode, + bpState.m_topology_name, + bpState.m_allocator_id, + sampleResolution, + quadratureType); + } } void importInitialVolumeFractions(BlueprintState& bpState, const std::map& initialVolumeFractions) { conduit::Node& n_mesh = bpState.getBlueprintMeshNode(); - const std::string quadName("quadrature_points"); - const conduit::Node& n_quad_points = n_mesh.fetch_existing("coordsets/" + quadName); + const conduit::Node& n_quad_points = + n_mesh.fetch_existing(axom::fmt::format("coordsets/{}", QUADRATURE_COORDSET_NAME)); const auto totalQuadPoints = conduit::blueprint::mesh::coordset::length(n_quad_points); // Get the topology we want to sample. @@ -374,13 +388,11 @@ void importInitialVolumeFractions(BlueprintState& bpState, const auto src_values = n_src_field["values"].as_double_accessor(); // Make the new quadrature field. - const auto destPath = axom::fmt::format("fields/mat_inout_{}", name); - conduit::Node& n_dest_field = n_mesh.fetch(destPath); - n_dest_field["topology"] = quadName; - n_dest_field["association"] = "element"; - conduit::Node& n_dest_values = n_dest_field["values"]; - n_dest_values.set(conduit::DataType::float64(totalQuadPoints)); - double* dptr = n_dest_values.as_double_ptr(); + auto destValues = + bpState.createField(axom::fmt::format("mat_inout_{}", name), + QUADRATURE_TOPOLOGY_NAME, + totalQuadPoints); + double* dptr = destValues.data(); // Copy the source field into the dest field. We just copy samplesPerZone values // from the source into the dest since each block of samplesPerZone points in @@ -408,11 +420,18 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin axom::fmt::format("Missing Blueprint material field '{}' for volume fraction projection.", matField)); - conduit::Node& bpMeshNode = bpState.m_internal_node; - SLIC_ERROR_IF(!bpMeshNode.has_path("fields/originalElements/values"), + conduit::Node& bpMeshNode = bpState.getBlueprintMeshNode(); + const std::string originalElementsPath = + axom::fmt::format("fields/{}/values", ORIGINAL_ELEMENTS_FIELD_NAME); + const std::string quadraturePhysicalWeightsPath = + axom::fmt::format("fields/{}/values", QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME); + const std::string quadratureWeightsPath = + axom::fmt::format("fields/{}/values", QUADRATURE_WEIGHTS_FIELD_NAME); + + SLIC_ERROR_IF(!bpMeshNode.has_path(originalElementsPath), "Missing Blueprint originalElements field for volume fraction projection."); - SLIC_ERROR_IF(!bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") && - !bpMeshNode.has_path("fields/quadratureWeights/values"), + SLIC_ERROR_IF(!bpMeshNode.has_path(quadraturePhysicalWeightsPath) && + !bpMeshNode.has_path(quadratureWeightsPath), "Missing Blueprint quadrature weight field for volume fraction projection."); const conduit::Node& topoNode = @@ -422,11 +441,11 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin namespace utils = axom::bump::utilities; const auto originalElements = - utils::make_array_view(bpMeshNode["fields/originalElements/values"]); + utils::make_array_view(bpMeshNode.fetch_existing(originalElementsPath)); const conduit::Node& quadratureWeightsNode = - bpMeshNode.has_path("fields/quadraturePhysicalWeights/values") - ? bpMeshNode["fields/quadraturePhysicalWeights/values"] - : bpMeshNode["fields/quadratureWeights/values"]; + bpMeshNode.has_path(quadraturePhysicalWeightsPath) + ? bpMeshNode.fetch_existing(quadraturePhysicalWeightsPath) + : bpMeshNode.fetch_existing(quadratureWeightsPath); const auto quadratureWeights = utils::make_array_view(quadratureWeightsNode); const auto inoutValues = utils::make_array_view(inout->fetch_existing("values")); @@ -434,17 +453,7 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin SLIC_ASSERT(originalElements.size() == inoutValues.size()); const std::string vfName = axom::fmt::format("vol_frac_{}", matField.substr(10)); - conduit::Node& vfNode = bpMeshNode["fields/" + vfName]; - vfNode.reset(); - vfNode["association"] = "element"; - vfNode["topology"] = bpState.m_topology_name; - - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); - conduit::Node& valuesNode = vfNode["values"]; - valuesNode.set_allocator(conduitAllocatorId); - valuesNode.set(conduit::DataType::float64(numZones)); - auto vfValues = utils::make_array_view(valuesNode); + auto vfValues = bpState.createField(vfName, bpState.m_topology_name, numZones); axom::Array totalWeights(numZones, numZones, bpState.m_allocator_id); auto totalWeightsView = totalWeights.view(); diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 2ed0ca2f58..edf41973af 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -40,6 +40,14 @@ namespace shaping */ std::string getBlueprintCellShape(const conduit::Node& topoNode); +#if defined(AXOM_USE_BUMP) +constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; +constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; +constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; +constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; +constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; +#endif + /// A class that contains Blueprint mesh and field state for Shaper class. struct BlueprintState { @@ -51,6 +59,18 @@ struct BlueprintState conduit::Node* m_external_node_ptr {nullptr}; conduit::Node m_internal_node; + bool isSidreBacked() const { return m_group_ptr != nullptr; } + bool isConduitBacked() const { return m_external_node_ptr != nullptr; } + + void refreshBlueprintMeshNode() + { + if(isSidreBacked()) + { + m_internal_node.reset(); + m_group_ptr->createNativeLayout(m_internal_node); + } + } + int meshDimension() const { const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); @@ -68,28 +88,73 @@ struct BlueprintState return -1; } - conduit::Node& getBlueprintMeshNode() { return m_internal_node; } + conduit::Node& getBlueprintMeshNode() + { + return isConduitBacked() ? *m_external_node_ptr : m_internal_node; + } + + const conduit::Node& getBlueprintMeshNode() const + { + return isConduitBacked() ? *m_external_node_ptr : m_internal_node; + } const conduit::Node& getBlueprintTopologyNode() const { - return m_internal_node.fetch_existing("topologies").fetch_existing(m_topology_name); + return getBlueprintMeshNode().fetch_existing("topologies").fetch_existing(m_topology_name); + } + + conduit::Node& getBlueprintCoordsetNode(const std::string& name) + { + return getBlueprintMeshNode().fetch_existing("coordsets").fetch_existing(name); + } + + const conduit::Node& getBlueprintCoordsetNode(const std::string& name) const + { + return getBlueprintMeshNode().fetch_existing("coordsets").fetch_existing(name); + } + + bool hasField(const std::string& name) const + { + return getBlueprintMeshNode().has_path(axom::fmt::format("fields/{}", name)); + } + + conduit::Node& getField(const std::string& name) + { + return getBlueprintMeshNode().fetch_existing("fields").fetch_existing(name); + } + + const conduit::Node& getField(const std::string& name) const + { + return getBlueprintMeshNode().fetch_existing("fields").fetch_existing(name); } conduit::Node* getShapeFunction(const std::string& name) { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] : nullptr; + return hasField(name) ? &getField(name) : nullptr; } const conduit::Node* getShapeFunction(const std::string& name) const { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] : nullptr; + return hasField(name) ? &getField(name) : nullptr; } void deleteShapeFunction(const std::string& name) { - if(m_internal_node.has_path("fields")) + if(isSidreBacked()) { - conduit::Node& n_fields = m_internal_node["fields"]; + const std::string fieldPath = axom::fmt::format("fields/{}", name); + if(m_group_ptr->hasGroup(fieldPath)) + { + m_group_ptr->destroyGroupAndData(fieldPath); + refreshBlueprintMeshNode(); + } + return; + } + + conduit::Node& bpMeshNode = getBlueprintMeshNode(); + if(bpMeshNode.has_path("fields")) + { + conduit::Node& n_fields = bpMeshNode["fields"]; if(n_fields.has_path(name)) { n_fields.remove(name); @@ -99,43 +164,136 @@ struct BlueprintState conduit::Node* getMaterialFunction(const std::string& name) { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] : nullptr; + return hasField(name) ? &getField(name) : nullptr; } const conduit::Node* getMaterialFunction(const std::string& name) const { - return m_internal_node.has_path("fields/" + name) ? &m_internal_node["fields/" + name] : nullptr; + return hasField(name) ? &getField(name) : nullptr; } - #if defined(AXOM_USE_BUMP) - conduit::Node* createMaterialFunction(const std::string& name) + axom::ArrayView createField(const std::string& name, + const std::string& topologyName, + axom::IndexType size, + bool addVolumeDependent = false, + bool volumeDependent = false) { - constexpr const char* quadratureTopologyName = "quadrature_points"; - SLIC_ERROR_IF( - !m_internal_node.has_path("coordsets/quadrature_points/values"), - std::string("Cannot create material function '") + name + "' without quadrature points."); + if(isSidreBacked()) + { + const std::string fieldPath = axom::fmt::format("fields/{}", name); + if(m_group_ptr->hasGroup(fieldPath)) + { + m_group_ptr->destroyGroupAndData(fieldPath); + } - conduit::Node& fieldNode = m_internal_node["fields/" + name]; + auto* fieldGrp = m_group_ptr->createGroup(fieldPath); + SLIC_ASSERT(fieldGrp != nullptr); + fieldGrp->createViewString("association", "element"); + fieldGrp->createViewString("topology", topologyName); + if(addVolumeDependent) + { + fieldGrp->createViewString("volume_dependent", volumeDependent ? "true" : "false"); + } + + auto* valuesView = + fieldGrp->createViewAndAllocate("values", axom::sidre::DataTypeId::FLOAT64_ID, size); + SLIC_ASSERT(valuesView != nullptr); + refreshBlueprintMeshNode(); + return axom::ArrayView(static_cast(valuesView->getVoidPtr()), size); + } + + conduit::Node& fieldNode = getBlueprintMeshNode()["fields/" + name]; fieldNode.reset(); fieldNode["association"] = "element"; - fieldNode["topology"] = quadratureTopologyName; + fieldNode["topology"] = topologyName; + if(addVolumeDependent) + { + fieldNode["volume_dependent"] = volumeDependent ? "true" : "false"; + } const auto conduitAllocatorId = axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); conduit::Node& valuesNode = fieldNode["values"]; valuesNode.set_allocator(conduitAllocatorId); + valuesNode.set(conduit::DataType::float64(size)); + return axom::bump::utilities::make_array_view(valuesNode); + } + + #if defined(AXOM_USE_BUMP) + void importQuadraturePointMesh(const conduit::Node& quadratureMesh) + { + auto replaceSubtree = [&](const std::string& path, const conduit::Node& node) { + if(isSidreBacked()) + { + if(m_group_ptr->hasGroup(path)) + { + m_group_ptr->destroyGroupAndData(path); + } + + auto* group = m_group_ptr->createGroup(path); + SLIC_ERROR_IF(group == nullptr, + axom::fmt::format("Failed to create Sidre group for Blueprint path '{}'.", + path)); + const bool importSuccess = group->importConduitTree(node); + SLIC_ERROR_IF(!importSuccess, + axom::fmt::format("Failed to import Blueprint subtree '{}'.", path)); + return; + } + + conduit::Node& outputNode = getBlueprintMeshNode()[path]; + outputNode.reset(); + outputNode.update(node); + }; + + const std::string quadratureCoordsetPath = + axom::fmt::format("coordsets/{}", QUADRATURE_COORDSET_NAME); + const std::string quadratureTopologyPath = + axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME); + const std::string originalElementsPath = + axom::fmt::format("fields/{}", ORIGINAL_ELEMENTS_FIELD_NAME); + const std::string quadratureWeightsPath = + axom::fmt::format("fields/{}", QUADRATURE_WEIGHTS_FIELD_NAME); + const std::string quadraturePhysicalWeightsPath = + axom::fmt::format("fields/{}", QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME); + + SLIC_ERROR_IF(!quadratureMesh.has_path(quadratureCoordsetPath), + "Quadrature mesh is missing its Blueprint quadrature coordset."); + SLIC_ERROR_IF(!quadratureMesh.has_path(quadratureTopologyPath), + "Quadrature mesh is missing its Blueprint quadrature topology."); + + replaceSubtree(quadratureCoordsetPath, quadratureMesh.fetch_existing(quadratureCoordsetPath)); + replaceSubtree(quadratureTopologyPath, quadratureMesh.fetch_existing(quadratureTopologyPath)); + replaceSubtree(originalElementsPath, quadratureMesh.fetch_existing(originalElementsPath)); + replaceSubtree(quadratureWeightsPath, quadratureMesh.fetch_existing(quadratureWeightsPath)); + + if(quadratureMesh.has_path(quadraturePhysicalWeightsPath)) + { + replaceSubtree(quadraturePhysicalWeightsPath, + quadratureMesh.fetch_existing(quadraturePhysicalWeightsPath)); + } + + if(isSidreBacked()) + { + refreshBlueprintMeshNode(); + } + } + + conduit::Node* createMaterialFunction(const std::string& name) + { + SLIC_ERROR_IF( + !getBlueprintMeshNode().has_path(axom::fmt::format("coordsets/{}/values", QUADRATURE_COORDSET_NAME)), + std::string("Cannot create material function '") + name + "' without quadrature points."); const conduit::Node& values = - m_internal_node["coordsets/quadrature_points"].fetch_existing("values"); + getBlueprintCoordsetNode(QUADRATURE_COORDSET_NAME).fetch_existing("values"); const auto numValues = values.child(0).dtype().number_of_elements(); - valuesNode.set(conduit::DataType::float64(numValues)); - auto fieldValues = axom::bump::utilities::make_array_view(valuesNode); + auto fieldValues = createField(name, QUADRATURE_TOPOLOGY_NAME, numValues); for(axom::IndexType i = 0; i < fieldValues.size(); ++i) { fieldValues[i] = 0.; } - return &fieldNode; + return &getField(name); } #endif }; @@ -198,7 +356,8 @@ conduit::Node* cloneInOutFunction(const conduit::Node* node); * \param sampleResolution The number of samples in each dimension. * \param quadratureType The quadrature type that determines the sample locations. */ -void generateQuadraturePointMesh(conduit::Node& bpMeshNode, +void generateQuadraturePointMesh(const conduit::Node& bpMeshNode, + conduit::Node& outputMeshNode, const std::string& topologyName, int allocatorID, axom::ArrayView sampleResolution, @@ -272,31 +431,18 @@ void sampleInOutField(const std::string& shapeName, SLIC_ERROR_IF(FromDim != ToDim && !projector, "A projector callback function is required when FromDim != ToDim"); - constexpr const char* quadratureCoordsetName = "quadrature_points"; - constexpr const char* quadratureTopologyName = "quadrature_points"; - const std::string inoutName = axom::fmt::format("inout_{}", shapeName); - - conduit::Node& bpMeshNode = bpState.m_internal_node; - SLIC_ERROR_IF(!bpMeshNode.has_path("coordsets/quadrature_points"), + conduit::Node& bpMeshNode = bpState.getBlueprintMeshNode(); + SLIC_ERROR_IF(!bpMeshNode.has_path(axom::fmt::format("coordsets/{}", QUADRATURE_COORDSET_NAME)), "Missing Blueprint quadrature coordset. Generate sampling positions first."); - SLIC_ERROR_IF(!bpMeshNode.has_path("topologies/quadrature_points"), + SLIC_ERROR_IF(!bpMeshNode.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME)), "Missing Blueprint quadrature topology. Generate sampling positions first."); - conduit::Node& inoutNode = bpMeshNode["fields/" + inoutName]; - inoutNode.reset(); - inoutNode["association"] = "element"; - inoutNode["topology"] = quadratureTopologyName; - - namespace utils = axom::bump::utilities; - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(bpState.m_allocator_id); - conduit::Node& valuesNode = inoutNode["values"]; - valuesNode.set_allocator(conduitAllocatorId); + const std::string inoutName = axom::fmt::format("inout_{}", shapeName); axom::utilities::Timer timer(true); axom::IndexType numQueryPoints = 0; axom::bump::views::dispatch_explicit_coordset( - bpMeshNode["coordsets/" + std::string(quadratureCoordsetName)], + bpMeshNode["coordsets/" + std::string(QUADRATURE_COORDSET_NAME)], [&](auto coordsetView) { using CoordsetView = typename std::decay::type; @@ -304,8 +450,8 @@ void sampleInOutField(const std::string& shapeName, if constexpr(CoordsetView::dimension() == FromDim) { numQueryPoints = coordsetView.size(); - valuesNode.set(conduit::DataType::float64(numQueryPoints)); - auto inoutValues = utils::make_array_view(valuesNode); + auto inoutValues = + bpState.createField(inoutName, QUADRATURE_TOPOLOGY_NAME, numQueryPoints); for(axom::IndexType i = 0; i < numQueryPoints; ++i) { From 19f903784dc61f25004a770f6e6707d034d0d1db Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 12 Jun 2026 17:51:08 -0700 Subject: [PATCH 43/72] More refactoring. --- src/axom/quest/IntersectionShaper.hpp | 12 +--- src/axom/quest/SamplingShaper.cpp | 2 +- src/axom/quest/SamplingShaper.hpp | 11 ++-- .../quest/detail/shaping/PrimitiveSampler.hpp | 2 +- .../detail/shaping/WindingNumberSampler.hpp | 2 +- .../quest/detail/shaping/shaping_helpers.cpp | 55 +++++++++++++++- .../quest/detail/shaping/shaping_helpers.hpp | 9 +++ .../shaping/shaping_helpers_blueprint.cpp | 20 +++--- .../shaping/shaping_helpers_blueprint.hpp | 2 +- .../detail/shaping/shaping_helpers_mfem.cpp | 7 ++- .../detail/shaping/shaping_helpers_mfem.hpp | 4 +- src/axom/quest/examples/CMakeLists.txt | 62 ++++++++++--------- src/axom/quest/examples/shaping_driver.cpp | 62 ++++++++++++++++--- src/axom/quest/tests/quest_initialize.cpp | 1 + .../quest/tests/quest_sampling_shaper.cpp | 55 ++++++++++++++++ 15 files changed, 231 insertions(+), 75 deletions(-) diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 7cd1135ff1..fd05c2f5f0 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -1380,7 +1380,7 @@ class IntersectionShaper : public Shaper */ std::string materialNameToFieldName(const std::string& materialName) const { - return axom::fmt::format("vol_frac_{}", materialName); + return shaping::volumeFractionFieldName(materialName); } /*! @@ -1392,13 +1392,7 @@ class IntersectionShaper : public Shaper */ std::string fieldNameToMaterialName(const std::string& fieldName) const { - const std::string vol_frac_("vol_frac_"); - std::string name; - if(fieldName.find(vol_frac_) == 0) - { - name = fieldName.substr(vol_frac_.size()); - } - return name; + return shaping::materialNameFromVolumeFractionFieldName(fieldName); } /*! @@ -1537,7 +1531,7 @@ class IntersectionShaper : public Shaper int dataSize = matVF.first.size(); // Get this shape's array. - auto shapeVolFracName = axom::fmt::format("shape_vol_frac_{}", shape.getName()); + auto shapeVolFracName = shaping::shapeVolumeFractionFieldName(shape.getName()); // auto* shapeVolFrac = this->getDC()->GetField(shapeVolFracName); auto shapeVolFrac = getScalarCellData(shapeVolFracName); SLIC_ERROR_IF(shapeVolFrac.empty(), diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 2a4116c85d..69430196c7 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -449,7 +449,7 @@ void SamplingShaper::adjustVolumeFractions() for(const auto& materialName : m_knownMaterials) { - const auto matName = axom::fmt::format("mat_inout_{}", materialName); + const auto matName = shaping::materialInOutFieldName(materialName); SLIC_INFO_ROOT(axom::fmt::format("Generating volume fraction fields for '{}' material", matName)); switch(m_vfSampling) diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index b1d7344cc7..5f92333ace 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -693,8 +693,8 @@ class SamplingShaper : public Shaper axom::fmt::format("Applying replacement rules for shape '{}'", shapeName))); auto* shapeFunc = shape.getGeometry().hasGeometry() - ? meshState.getShapeFunction(axom::fmt::format("inout_{}", shapeName)) - : meshState.getMaterialFunction(axom::fmt::format("mat_inout_{}", thisMatName)); + ? meshState.getShapeFunction(shaping::shapeInOutFieldName(shapeName)) + : meshState.getMaterialFunction(shaping::materialInOutFieldName(thisMatName)); if(shape.getGeometry().hasGeometry()) { @@ -734,8 +734,7 @@ class SamplingShaper : public Shaper thisMatName, shouldReplace ? "yes" : "no")); - auto* otherMatFunc = - meshState.getMaterialFunction(axom::fmt::format("mat_inout_{}", otherMatName)); + auto* otherMatFunc = meshState.getMaterialFunction(shaping::materialInOutFieldName(otherMatName)); SLIC_ERROR_IF(otherMatFunc == nullptr, axom::fmt::format("Missing inout samples for material '{}' while applying " "replacement rules for shape '{}'.", @@ -745,7 +744,7 @@ class SamplingShaper : public Shaper quest::shaping::replaceMaterial(shapeFuncCopy, otherMatFunc, shouldReplace); } - const std::string materialFunctionName = axom::fmt::format("mat_inout_{}", thisMatName); + const std::string materialFunctionName = shaping::materialInOutFieldName(thisMatName); auto* materialFunc = meshState.getMaterialFunction(materialFunctionName); const bool hadExistingMaterial = (materialFunc != nullptr); @@ -764,7 +763,7 @@ class SamplingShaper : public Shaper quest::shaping::copyShapeIntoMaterial(shapeFuncCopy, materialFunc, reuseExisting); if(shape.getGeometry().hasGeometry()) { - meshState.deleteShapeFunction(axom::fmt::format("inout_{}", shapeName)); + meshState.deleteShapeFunction(shaping::shapeInOutFieldName(shapeName)); } delete shapeFuncCopy; diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index c7b36eabb8..6815d65d83 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -193,7 +193,7 @@ class PrimitiveSampler // Sample the in/out field at each point // store in QField which we register with the QFunc collection - const std::string inoutName = axom::fmt::format("inout_{}", m_shapeName); + const std::string inoutName = shaping::shapeInOutFieldName(m_shapeName); const int vdim = 1; auto* inout = new mfem::QuadratureFunction(sp, vdim); inoutQFuncs.Register(inoutName, inout, true); diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index c4d6c8a27d..f2a8f5441d 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -174,7 +174,7 @@ class WindingNumberSampler // Sample the in/out field at each point // store in QField which we register with the QFunc collection - const std::string inoutName = axom::fmt::format("inout_{}", m_shapeName); + const std::string inoutName = shaping::shapeInOutFieldName(m_shapeName); const int vdim = 1; auto* inout = new mfem::QuadratureFunction(sp, vdim); inoutQFuncs.Register(inoutName, inout, true); diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index cc74347073..c6883b2bc8 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -6,5 +6,56 @@ #include "shaping_helpers.hpp" -// Common shaping helpers are header-only. Backend-specific implementations live in -// shaping_helpers_mfem.cpp and shaping_helpers_blueprint.cpp. +namespace axom +{ +namespace quest +{ +namespace shaping +{ +namespace +{ +constexpr const char* SHAPE_INOUT_PREFIX = "inout_"; +constexpr const char* MATERIAL_INOUT_PREFIX = "mat_inout_"; +constexpr const char* VOLUME_FRACTION_PREFIX = "vol_frac_"; +constexpr const char* SHAPE_VOLUME_FRACTION_PREFIX = "shape_vol_frac_"; + +std::string extractSuffixedName(const std::string& fieldName, const std::string& prefix) +{ + return axom::utilities::string::startsWith(fieldName, prefix) ? fieldName.substr(prefix.size()) + : std::string {}; +} +} // namespace + +std::string shapeInOutFieldName(const std::string& shapeName) +{ + return axom::fmt::format("{}{}", SHAPE_INOUT_PREFIX, shapeName); +} + +std::string materialInOutFieldName(const std::string& materialName) +{ + return axom::fmt::format("{}{}", MATERIAL_INOUT_PREFIX, materialName); +} + +std::string volumeFractionFieldName(const std::string& materialName) +{ + return axom::fmt::format("{}{}", VOLUME_FRACTION_PREFIX, materialName); +} + +std::string shapeVolumeFractionFieldName(const std::string& shapeName) +{ + return axom::fmt::format("{}{}", SHAPE_VOLUME_FRACTION_PREFIX, shapeName); +} + +std::string materialNameFromMaterialInOutFieldName(const std::string& fieldName) +{ + return extractSuffixedName(fieldName, MATERIAL_INOUT_PREFIX); +} + +std::string materialNameFromVolumeFractionFieldName(const std::string& fieldName) +{ + return extractSuffixedName(fieldName, VOLUME_FRACTION_PREFIX); +} + +} // namespace shaping +} // namespace quest +} // namespace axom diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index 2dfef97d12..e181980adf 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -21,6 +21,7 @@ #include "axom/slic.hpp" #include +#include #include #include @@ -125,6 +126,14 @@ enum class VolFracSampling : int SAMPLE_AT_QPTS }; +std::string shapeInOutFieldName(const std::string& shapeName); +std::string materialInOutFieldName(const std::string& materialName); +std::string volumeFractionFieldName(const std::string& materialName); +std::string shapeVolumeFractionFieldName(const std::string& shapeName); + +std::string materialNameFromMaterialInOutFieldName(const std::string& fieldName); +std::string materialNameFromVolumeFractionFieldName(const std::string& fieldName); + template void checkSampleResolution(const MeshState& meshState, axom::ArrayView sampleResolution, diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index e29d000cb1..783f1d8943 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -164,9 +164,9 @@ void printRegisteredFieldNames(const BlueprintState& bpState, for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) { const std::string name = fieldsNode.child(i).name(); - if(!axom::utilities::string::startsWith(name, "inout_") && - !axom::utilities::string::startsWith(name, "mat_inout_") && - !axom::utilities::string::startsWith(name, "vol_frac_")) + if(shaping::materialNameFromVolumeFractionFieldName(name).empty() && + shaping::materialNameFromMaterialInOutFieldName(name).empty() && + !axom::utilities::string::startsWith(name, "inout_")) { names.push_back(name); } @@ -381,17 +381,16 @@ void importInitialVolumeFractions(BlueprintState& bpState, } // Get the source field. - const auto srcPath = axom::fmt::format("fields/vol_frac_{}", name); + const auto srcPath = axom::fmt::format("fields/{}", shaping::volumeFractionFieldName(name)); conduit::Node& n_src_field = n_mesh.fetch_existing(srcPath); SLIC_ERROR_IF(n_src_field.fetch_existing("association").as_string() != "element", "The imported field must have element association."); const auto src_values = n_src_field["values"].as_double_accessor(); // Make the new quadrature field. - auto destValues = - bpState.createField(axom::fmt::format("mat_inout_{}", name), - QUADRATURE_TOPOLOGY_NAME, - totalQuadPoints); + auto destValues = bpState.createField(shaping::materialInOutFieldName(name), + QUADRATURE_TOPOLOGY_NAME, + totalQuadPoints); double* dptr = destValues.data(); // Copy the source field into the dest field. We just copy samplesPerZone values @@ -412,7 +411,8 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin { AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); - SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); + const std::string materialName = shaping::materialNameFromMaterialInOutFieldName(matField); + SLIC_ASSERT(!materialName.empty()); conduit::Node* inout = bpState.getMaterialFunction(matField); SLIC_ERROR_IF( @@ -452,7 +452,7 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin SLIC_ASSERT(originalElements.size() == quadratureWeights.size()); SLIC_ASSERT(originalElements.size() == inoutValues.size()); - const std::string vfName = axom::fmt::format("vol_frac_{}", matField.substr(10)); + const std::string vfName = shaping::volumeFractionFieldName(materialName); auto vfValues = bpState.createField(vfName, bpState.m_topology_name, numZones); axom::Array totalWeights(numZones, numZones, bpState.m_allocator_id); auto totalWeightsView = totalWeights.view(); diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index edf41973af..17a8e2ea22 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -437,7 +437,7 @@ void sampleInOutField(const std::string& shapeName, SLIC_ERROR_IF(!bpMeshNode.has_path(axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME)), "Missing Blueprint quadrature topology. Generate sampling positions first."); - const std::string inoutName = axom::fmt::format("inout_{}", shapeName); + const std::string inoutName = shaping::shapeInOutFieldName(shapeName); axom::utilities::Timer timer(true); axom::IndexType numQueryPoints = 0; diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp index 62dcfc5776..2a8edce228 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp @@ -501,7 +501,7 @@ void importInitialVolumeFractions(SamplingMFEMState& mfemState, interp->Values(*gf, *matQFunc); } - const auto matName = axom::fmt::format("mat_inout_{}", name); + const auto matName = shaping::materialInOutFieldName(name); mfemState.materialQFuncs().Register(matName, matQFunc, true); } } @@ -514,7 +514,8 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, { AXOM_ANNOTATE_SCOPE("computeVolumeFractionsForMaterial"); - SLIC_ASSERT(axom::utilities::string::startsWith(matField, "mat_inout_")); + const std::string materialName = shaping::materialNameFromMaterialInOutFieldName(matField); + SLIC_ASSERT(!materialName.empty()); auto* inout = mfemState.getMaterialFunction(matField); SLIC_ASSERT(inout != nullptr); @@ -553,7 +554,7 @@ void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, SLIC_INFO_ROOT( axom::fmt::format(axom::utilities::locale(), "Mesh has dim {} and {:L} elements", dim, NE)); - const auto vf_name = axom::fmt::format("vol_frac_{}", matField.substr(10)); + const auto vf_name = shaping::volumeFractionFieldName(materialName); mfem::GridFunction* vf = getOrAllocateL2GridFunction(dc, vf_name, volfracOrder, dim, mfem::BasisType::Positive); const mfem::FiniteElementSpace* fes = vf->FESpace(); diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp index 1e042817bf..a3864dc45b 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp @@ -304,7 +304,7 @@ void sampleInOutField(const std::string shapeName, const auto pos = mfem::Reshape(pos_coef->HostRead(), dim, nq, NE); - const std::string inoutName = axom::fmt::format("inout_{}", shapeName); + const std::string inoutName = shaping::shapeInOutFieldName(shapeName); auto* inout = new mfem::QuadratureFunction(sp, 1); inoutQFuncs.Register(inoutName, inout, true); auto inout_vals = mfem::Reshape(inout->HostWrite(), nq, NE); @@ -384,7 +384,7 @@ void computeVolumeFractionsBaseline(const std::string& shapeName, return; } - const auto volFracName = axom::fmt::format("vol_frac_{}", shapeName); + const auto volFracName = shaping::volumeFractionFieldName(shapeName); mfem::GridFunction* volFrac = shaping::getOrAllocateL2GridFunction(dc, volFracName, outputOrder, dim, mfem::BasisType::Positive); const mfem::FiniteElementSpace* fes = volFrac->FESpace(); diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index e1c2bb6f26..f22dfe15ea 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -193,18 +193,20 @@ if(AXOM_HAS_MFEM_WITH_MPI AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) PASS_REGULAR_EXPRESSION "Volume of material 'steel' is 65.") if(CONDUIT_FOUND AND AXOM_ENABLE_BUMP) - set(_testname quest_shaping_driver_ex_sampling_circles_blueprint) - axom_add_test( - NAME ${_testname} - COMMAND quest_shaping_driver_ex - -i ${shaping_data_dir}/circles.yaml - --method sampling - --verbose - inline_mesh_blueprint --min -6 -6 --max 6 6 --resolution 25 25 -d 2 - NUM_MPI_TASKS ${_nranks}) - # Analytic area for annulus w/ outer/inner radii 5 and 2.5 is ~58.905 - set_tests_properties(${_testname} PROPERTIES - PASS_REGULAR_EXPRESSION "Volume of material 'steel' is 58.") + foreach(_backing sidre conduit) + set(_testname quest_shaping_driver_ex_sampling_circles_blueprint_${_backing}) + axom_add_test( + NAME ${_testname} + COMMAND quest_shaping_driver_ex + -i ${shaping_data_dir}/circles.yaml + --method sampling + --verbose + inline_mesh_blueprint --backing ${_backing} --min -6 -6 --max 6 6 --resolution 25 25 -d 2 + NUM_MPI_TASKS ${_nranks}) + # Analytic area for annulus w/ outer/inner radii 5 and 2.5 is ~58.905 + set_tests_properties(${_testname} PROPERTIES + PASS_REGULAR_EXPRESSION "Volume of material 'steel' is 58.") + endforeach() endif() set(_testname quest_shaping_driver_ex_sampling_balls_and_jacks) @@ -372,23 +374,25 @@ endif() # Blueprint-only shaping test if(CONDUIT_FOUND AND AXOM_ENABLE_BUMP AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE AND AXOM_ENABLE_TESTS AND AXOM_DATA_DIR) set(_nranks 1) - set(_testname quest_shaping_driver_ex_sampling_blueprint_3D) - axom_add_test(NAME ${_testname} - COMMAND quest_shaping_driver_ex - -i ${shaping_data_dir}/spheres.yaml - --verbose - --method sampling - --sampling inout - --background-material void - inline_mesh_blueprint --min -6 -6 -6 --max 6 6 6 --resolution 16 16 16 -d 3 - --sampling-resolution 5 5 5 - --quadrature-type gausslegendre - NUM_MPI_TASKS ${_nranks}) - # bbox volume: 12^3 = 1728; sphere(r=5): ~523.6; sphere(r=2): 33.5 - # expected analytic volume when fully resolved: ~1237.9 - # NOTE: the answer depends on the quadrature type. This answer is for gausslegendre. - set_tests_properties(${_testname} PROPERTIES - PASS_REGULAR_EXPRESSION "Volume of material 'void' is 1,?239.") + foreach(_backing sidre conduit) + set(_testname quest_shaping_driver_ex_sampling_blueprint_3D_${_backing}) + axom_add_test(NAME ${_testname} + COMMAND quest_shaping_driver_ex + -i ${shaping_data_dir}/spheres.yaml + --verbose + --method sampling + --sampling inout + --background-material void + inline_mesh_blueprint --backing ${_backing} --min -6 -6 -6 --max 6 6 6 --resolution 16 16 16 -d 3 + --sampling-resolution 5 5 5 + --quadrature-type gausslegendre + NUM_MPI_TASKS ${_nranks}) + # bbox volume: 12^3 = 1728; sphere(r=5): ~523.6; sphere(r=2): 33.5 + # expected analytic volume when fully resolved: ~1237.9 + # NOTE: the answer depends on the quadrature type. This answer is for gausslegendre. + set_tests_properties(${_testname} PROPERTIES + PASS_REGULAR_EXPRESSION "Volume of material 'void' is 1,?239.") + endforeach() endif() # Distributed closest point example ------------------------------------------- diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 8803d97f02..2ffce0bc96 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -78,6 +78,12 @@ enum class BlueprintTopologyType : int Unstructured }; +enum class BlueprintMeshBacking : int +{ + Sidre, + Conduit +}; + struct AxisymmetricProjector32 { AXOM_HOST_DEVICE Point2D operator()(Point3D pt) const @@ -127,6 +133,7 @@ struct Input int boxDim {-1}; InlineMeshKind inlineMeshKind {InlineMeshKind::None}; BlueprintTopologyType blueprintTopologyType {BlueprintTopologyType::Structured}; + BlueprintMeshBacking blueprintMeshBacking {BlueprintMeshBacking::Sidre}; std::string shapeFile; klee::ShapeSet shapeSet; @@ -412,6 +419,9 @@ struct Input std::map blueprintTopoMap { {"structured", BlueprintTopologyType::Structured}, {"unstructured", BlueprintTopologyType::Unstructured}}; + std::map blueprintBackingMap { + {"sidre", BlueprintMeshBacking::Sidre}, + {"conduit", BlueprintMeshBacking::Conduit}}; auto* inline_mesh_blueprint_subcommand = app.add_subcommand("inline_mesh_blueprint") @@ -441,6 +451,10 @@ struct Input ->description("Blueprint topology type for the inline mesh") ->capture_default_str() ->transform(axom::CLI::CheckedTransformer(blueprintTopoMap, axom::CLI::ignore_case)); + inline_mesh_blueprint_subcommand->add_option("--backing", blueprintMeshBacking) + ->description("Inline Blueprint mesh backing used to construct the shaper") + ->capture_default_str() + ->transform(axom::CLI::CheckedTransformer(blueprintBackingMap, axom::CLI::ignore_case)); #endif // we want either the mesh_file or an inline mesh @@ -718,6 +732,7 @@ int main(int argc, char** argv) #if defined(AXOM_USE_CONDUIT) std::unique_ptr originalBlueprintMeshDS; sidre::Group* originalBlueprintMeshGroup = nullptr; + conduit::Node originalBlueprintMeshNode; #endif #if defined(AXOM_USE_MFEM) std::unique_ptr originalMeshDC; @@ -736,6 +751,11 @@ int main(int argc, char** argv) #if defined(AXOM_USE_CONDUIT) originalBlueprintMeshDS = params.createBlueprintBoxMesh(); originalBlueprintMeshGroup = originalBlueprintMeshDS->getRoot()->getGroup("mesh"); + SLIC_ASSERT(originalBlueprintMeshGroup != nullptr); + if(params.blueprintMeshBacking == BlueprintMeshBacking::Conduit) + { + originalBlueprintMeshGroup->createNativeLayout(originalBlueprintMeshNode); + } #else SLIC_ERROR_ROOT("inline_mesh_blueprint requires Axom to be configured with Conduit."); #endif @@ -770,11 +790,22 @@ int main(int argc, char** argv) { // NOTE: The SamplingShaper requires Conduit + Bump for Blueprint support. #if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) - shaper = new quest::SamplingShaper(params.policy, - axom::policyToDefaultAllocatorID(params.policy), - params.shapeSet, - originalBlueprintMeshGroup, - "mesh"); + if(params.blueprintMeshBacking == BlueprintMeshBacking::Conduit) + { + shaper = new quest::SamplingShaper(params.policy, + axom::policyToDefaultAllocatorID(params.policy), + params.shapeSet, + originalBlueprintMeshNode, + "mesh"); + } + else + { + shaper = new quest::SamplingShaper(params.policy, + axom::policyToDefaultAllocatorID(params.policy), + params.shapeSet, + originalBlueprintMeshGroup, + "mesh"); + } #else SLIC_ERROR_ROOT( "Using inline_mesh_blueprint with SamplingShaper requires Axom to be configured with " @@ -796,11 +827,22 @@ int main(int argc, char** argv) { // NOTE: The IntersectionShaper requires Conduit for Blueprint support. #if defined(AXOM_USE_CONDUIT) - shaper = new quest::IntersectionShaper(params.policy, - axom::policyToDefaultAllocatorID(params.policy), - params.shapeSet, - originalBlueprintMeshGroup, - "mesh"); + if(params.blueprintMeshBacking == BlueprintMeshBacking::Conduit) + { + shaper = new quest::IntersectionShaper(params.policy, + axom::policyToDefaultAllocatorID(params.policy), + params.shapeSet, + originalBlueprintMeshNode, + "mesh"); + } + else + { + shaper = new quest::IntersectionShaper(params.policy, + axom::policyToDefaultAllocatorID(params.policy), + params.shapeSet, + originalBlueprintMeshGroup, + "mesh"); + } #else SLIC_ERROR_ROOT( "Using inline_mesh_blueprint with IntersectionShaper requires Axom to be configured with " diff --git a/src/axom/quest/tests/quest_initialize.cpp b/src/axom/quest/tests/quest_initialize.cpp index 2ea885afa4..522c918b22 100644 --- a/src/axom/quest/tests/quest_initialize.cpp +++ b/src/axom/quest/tests/quest_initialize.cpp @@ -152,6 +152,7 @@ TEST(quest_initialize, immediate_ug_reserve) contourMesh.reserveCells(10); // This may unexpectedly crash. } #endif + #endif int main(int argc, char** argv) diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index e8048371d6..6487820243 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2444,6 +2444,61 @@ piece = line(end=start) //----------------------------------------------------------------------------- +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) +TEST(SamplingShaperBlueprintTest, sidre_blueprint_quadrature_persists) +{ + sidre::DataStore dataStore; + auto* meshGroup = dataStore.getRoot()->createGroup("mesh"); + + const primal::BoundingBox bbox {{0., 0.}, {1., 1.}}; + const axom::NumericArray res {{2, 2}}; + quest::util::make_unstructured_blueprint_box_mesh_2d(meshGroup, bbox, res, "mesh", "coords"); + + constexpr axom::IndexType cellCount = 4; + auto* fieldGroup = meshGroup->createGroup("fields/vol_frac_background"); + fieldGroup->createViewString("association", "element"); + fieldGroup->createViewString("topology", "mesh"); + auto* valuesView = + fieldGroup->createViewAndAllocate("values", axom::sidre::DataTypeId::FLOAT64_ID, cellCount); + auto* values = static_cast(valuesView->getVoidPtr()); + for(axom::IndexType i = 0; i < cellCount; ++i) + { + values[i] = 1.; + } + + klee::ShapeSet shapeSet; + quest::SamplingShaper shaper(axom::runtime_policy::Policy::seq, + axom::policyToDefaultAllocatorID(axom::runtime_policy::Policy::seq), + shapeSet, + meshGroup, + "mesh"); + shaper.setSamplingResolution(2); + + auto* bpMeshNode = shaper.getBlueprintMeshNode(); + ASSERT_NE(bpMeshNode, nullptr); + + std::map initialVolumeFractions; + initialVolumeFractions["background"] = &bpMeshNode->fetch_existing("fields/vol_frac_background"); + shaper.importInitialVolumeFractions(initialVolumeFractions); + + EXPECT_TRUE(meshGroup->hasGroup("coordsets/quadrature_points")); + EXPECT_TRUE(meshGroup->hasGroup("topologies/quadrature_points")); + EXPECT_TRUE(meshGroup->hasGroup("fields/originalElements")); + EXPECT_TRUE(meshGroup->hasGroup("fields/quadratureWeights")); + EXPECT_TRUE(meshGroup->hasGroup("fields/mat_inout_background")); + + conduit::Node refreshedMesh; + meshGroup->createNativeLayout(refreshedMesh); + EXPECT_TRUE(refreshedMesh.has_path("coordsets/quadrature_points")); + EXPECT_TRUE(refreshedMesh.has_path("topologies/quadrature_points")); + EXPECT_TRUE(refreshedMesh.has_path("fields/originalElements/values")); + EXPECT_TRUE(refreshedMesh.has_path("fields/quadratureWeights/values")); + EXPECT_TRUE(refreshedMesh.has_path("fields/mat_inout_background/values")); +} +#endif + +//----------------------------------------------------------------------------- + TEST_F(SampleTester2D, invalid_quadrature_type_values_abort) { const std::string shape_template = R"( From 9e34740918eb9f0f7b1c6c7b1a4cf25e74b8d71b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 12 Jun 2026 18:04:25 -0700 Subject: [PATCH 44/72] Moved methods from hpp to cpp. --- src/axom/quest/Shaper.cpp | 29 +- .../shaping/shaping_helpers_blueprint.cpp | 211 +++++++++++++++ .../shaping/shaping_helpers_blueprint.hpp | 251 ++++++------------ 3 files changed, 298 insertions(+), 193 deletions(-) diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 5f4c7bd7c9..b9091224c5 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -388,34 +388,13 @@ void Shaper::ensureBlueprintMeshIsUnstructured() if(!m_bp_state->isSidreBacked()) { - SLIC_ERROR( - "Structured Blueprint meshes backed by conduit::Node are not yet supported for " - "in-place conversion to unstructured topology."); + m_bp_state->ensureUnstructured(m_execPolicy); + return; } AXOM_ANNOTATE_SCOPE("Shaper::convertStructured"); - - const std::string shapeType = getBlueprintCellShape(); - if(shapeType == "hex") - { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d( - m_bp_state->m_group_ptr, - m_bp_state->m_topology_name, - m_execPolicy); - } - else if(shapeType == "quad") - { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d( - m_bp_state->m_group_ptr, - m_bp_state->m_topology_name, - m_execPolicy); - } - else - { - SLIC_ERROR("Axom Internal error: Unhandled shape type."); - } - - refreshBlueprintMeshState(); + m_bp_state->ensureUnstructured(m_execPolicy); + m_cellCount = conduit::blueprint::mesh::topology::length(getBlueprintTopologyNode()); } #endif diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 783f1d8943..1157e829be 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -8,10 +8,12 @@ #if defined(AXOM_USE_CONDUIT) + #include "axom/quest/util/mesh_helpers.hpp" #include "conduit_blueprint_mesh.hpp" #if defined(AXOM_USE_BUMP) #include "axom/bump/GenerateQuadratureMesh.hpp" + #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/bump/views/dispatch_topology.hpp" #include "axom/bump/views/dispatch_unstructured_topology.hpp" #endif @@ -118,7 +120,216 @@ std::string getBlueprintCellShape(const conduit::Node& topoNode) return getBlueprintCellShapeImpl(topoNode); } +void BlueprintState::refreshBlueprintMeshNode() +{ + if(isSidreBacked()) + { + m_internal_node.reset(); + m_group_ptr->createNativeLayout(m_internal_node); + } +} + +int BlueprintState::meshDimension() const +{ + const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); + + if(shapeType == "quad") + { + return 2; + } + if(shapeType == "hex") + { + return 3; + } + + SLIC_ERROR(axom::fmt::format("Unsupported Blueprint cell shape '{}'.", shapeType)); + return -1; +} + +void BlueprintState::ensureUnstructured(axom::runtime_policy::Policy execPolicy) +{ + const std::string topoType = getBlueprintTopologyNode().fetch_existing("type").as_string(); + if(topoType != "structured") + { + return; + } + + if(!isSidreBacked()) + { + SLIC_ERROR( + "Structured Blueprint meshes backed by conduit::Node are not yet supported for " + "in-place conversion to unstructured topology."); + } + + const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); + if(shapeType == "hex") + { + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d(m_group_ptr, + m_topology_name, + execPolicy); + } + else if(shapeType == "quad") + { + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d(m_group_ptr, + m_topology_name, + execPolicy); + } + else + { + SLIC_ERROR("Axom Internal error: Unhandled shape type."); + } + + refreshBlueprintMeshNode(); +} + +void BlueprintState::deleteShapeFunction(const std::string& name) +{ + if(isSidreBacked()) + { + const std::string fieldPath = axom::fmt::format("fields/{}", name); + if(m_group_ptr->hasGroup(fieldPath)) + { + m_group_ptr->destroyGroupAndData(fieldPath); + refreshBlueprintMeshNode(); + } + return; + } + + conduit::Node& bpMeshNode = getBlueprintMeshNode(); + if(bpMeshNode.has_path("fields")) + { + conduit::Node& n_fields = bpMeshNode["fields"]; + if(n_fields.has_path(name)) + { + n_fields.remove(name); + } + } +} + +axom::ArrayView BlueprintState::createField(const std::string& name, + const std::string& topologyName, + axom::IndexType size, + bool addVolumeDependent, + bool volumeDependent) +{ + if(isSidreBacked()) + { + const std::string fieldPath = axom::fmt::format("fields/{}", name); + if(m_group_ptr->hasGroup(fieldPath)) + { + m_group_ptr->destroyGroupAndData(fieldPath); + } + + auto* fieldGrp = m_group_ptr->createGroup(fieldPath); + SLIC_ASSERT(fieldGrp != nullptr); + fieldGrp->createViewString("association", "element"); + fieldGrp->createViewString("topology", topologyName); + if(addVolumeDependent) + { + fieldGrp->createViewString("volume_dependent", volumeDependent ? "true" : "false"); + } + + auto* valuesView = + fieldGrp->createViewAndAllocate("values", axom::sidre::DataTypeId::FLOAT64_ID, size); + SLIC_ASSERT(valuesView != nullptr); + refreshBlueprintMeshNode(); + return axom::ArrayView(static_cast(valuesView->getVoidPtr()), size); + } + + conduit::Node& fieldNode = getBlueprintMeshNode()["fields/" + name]; + fieldNode.reset(); + fieldNode["association"] = "element"; + fieldNode["topology"] = topologyName; + if(addVolumeDependent) + { + fieldNode["volume_dependent"] = volumeDependent ? "true" : "false"; + } + + const auto conduitAllocatorId = axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); + conduit::Node& valuesNode = fieldNode["values"]; + valuesNode.set_allocator(conduitAllocatorId); + valuesNode.set(conduit::DataType::float64(size)); + return axom::bump::utilities::make_array_view(valuesNode); +} + #if defined(AXOM_USE_BUMP) +void BlueprintState::importQuadraturePointMesh(const conduit::Node& quadratureMesh) +{ + auto replaceSubtree = [&](const std::string& path, const conduit::Node& node) { + if(isSidreBacked()) + { + if(m_group_ptr->hasGroup(path)) + { + m_group_ptr->destroyGroupAndData(path); + } + + auto* group = m_group_ptr->createGroup(path); + SLIC_ERROR_IF(group == nullptr, + axom::fmt::format("Failed to create Sidre group for Blueprint path '{}'.", + path)); + const bool importSuccess = group->importConduitTree(node); + SLIC_ERROR_IF(!importSuccess, + axom::fmt::format("Failed to import Blueprint subtree '{}'.", path)); + return; + } + + conduit::Node& outputNode = getBlueprintMeshNode()[path]; + outputNode.reset(); + outputNode.update(node); + }; + + const std::string quadratureCoordsetPath = + axom::fmt::format("coordsets/{}", QUADRATURE_COORDSET_NAME); + const std::string quadratureTopologyPath = + axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME); + const std::string originalElementsPath = + axom::fmt::format("fields/{}", ORIGINAL_ELEMENTS_FIELD_NAME); + const std::string quadratureWeightsPath = + axom::fmt::format("fields/{}", QUADRATURE_WEIGHTS_FIELD_NAME); + const std::string quadraturePhysicalWeightsPath = + axom::fmt::format("fields/{}", QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME); + + SLIC_ERROR_IF(!quadratureMesh.has_path(quadratureCoordsetPath), + "Quadrature mesh is missing its Blueprint quadrature coordset."); + SLIC_ERROR_IF(!quadratureMesh.has_path(quadratureTopologyPath), + "Quadrature mesh is missing its Blueprint quadrature topology."); + + replaceSubtree(quadratureCoordsetPath, quadratureMesh.fetch_existing(quadratureCoordsetPath)); + replaceSubtree(quadratureTopologyPath, quadratureMesh.fetch_existing(quadratureTopologyPath)); + replaceSubtree(originalElementsPath, quadratureMesh.fetch_existing(originalElementsPath)); + replaceSubtree(quadratureWeightsPath, quadratureMesh.fetch_existing(quadratureWeightsPath)); + + if(quadratureMesh.has_path(quadraturePhysicalWeightsPath)) + { + replaceSubtree(quadraturePhysicalWeightsPath, + quadratureMesh.fetch_existing(quadraturePhysicalWeightsPath)); + } + + if(isSidreBacked()) + { + refreshBlueprintMeshNode(); + } +} + +conduit::Node* BlueprintState::createMaterialFunction(const std::string& name) +{ + SLIC_ERROR_IF(!getBlueprintMeshNode().has_path( + axom::fmt::format("coordsets/{}/values", QUADRATURE_COORDSET_NAME)), + std::string("Cannot create material function '") + name + "' without quadrature points."); + + const conduit::Node& values = + getBlueprintCoordsetNode(QUADRATURE_COORDSET_NAME).fetch_existing("values"); + const auto numValues = values.child(0).dtype().number_of_elements(); + + auto fieldValues = createField(name, QUADRATURE_TOPOLOGY_NAME, numValues); + for(axom::IndexType i = 0; i < fieldValues.size(); ++i) + { + fieldValues[i] = 0.; + } + + return &getField(name); +} + void printRegisteredFieldNames(const BlueprintState& bpState, const std::set& knownMaterials, VolFracSampling AXOM_UNUSED_PARAM(vfSampling), diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 17a8e2ea22..377dc644e0 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -14,7 +14,6 @@ #include "axom/fmt.hpp" #if defined(AXOM_USE_BUMP) - #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/bump/views/dispatch_coordset.hpp" #endif @@ -48,253 +47,169 @@ constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; #endif -/// A class that contains Blueprint mesh and field state for Shaper class. +/*! + * \brief Stores Blueprint mesh state and backend-specific access for Quest shapers. + * + * A `BlueprintState` may be backed either by a caller-owned `sidre::Group` + * or by a caller-owned `conduit::Node`. The helper methods on this struct + * provide a single access layer for reading and updating Blueprint mesh data + * without forcing the shaper code to know which storage backend is active. + */ struct BlueprintState { + /// Destructor. virtual ~BlueprintState() = default; + /// The caller-owned Sidre group backing the mesh, when Sidre-backed. axom::sidre::Group* m_group_ptr {nullptr}; + /// Allocator id used for newly-created Blueprint array data. int m_allocator_id {axom::getDefaultAllocatorID()}; + /// Name of the active Blueprint topology. std::string m_topology_name; + /// The caller-owned Blueprint mesh node, when Conduit-backed. conduit::Node* m_external_node_ptr {nullptr}; + /// Cached native Conduit layout for Sidre-backed meshes. conduit::Node m_internal_node; + /// Return whether the active Blueprint mesh is backed by Sidre storage. bool isSidreBacked() const { return m_group_ptr != nullptr; } + /// Return whether the active Blueprint mesh is backed by a Conduit node. bool isConduitBacked() const { return m_external_node_ptr != nullptr; } - void refreshBlueprintMeshNode() - { - if(isSidreBacked()) - { - m_internal_node.reset(); - m_group_ptr->createNativeLayout(m_internal_node); - } - } - - int meshDimension() const - { - const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); - - if(shapeType == "quad") - { - return 2; - } - if(shapeType == "hex") - { - return 3; - } - - SLIC_ERROR(axom::fmt::format("Unsupported Blueprint cell shape '{}'.", shapeType)); - return -1; - } - + /*! + * \brief Refresh the cached native Conduit layout for Sidre-backed meshes. + * + * This is a no-op for Conduit-backed meshes. + */ + void refreshBlueprintMeshNode(); + + /*! + * \brief Return the dimension implied by the active Blueprint cell shape. + * + * \return `2` for quadrilateral meshes or `3` for hexahedral meshes. + */ + int meshDimension() const; + + /*! + * \brief Convert a structured Blueprint mesh to unstructured topology in place. + * + * \param execPolicy Runtime policy used by the conversion helper. + */ + void ensureUnstructured(axom::runtime_policy::Policy execPolicy); + + /// Return the active Blueprint mesh node for the current backing store. conduit::Node& getBlueprintMeshNode() { return isConduitBacked() ? *m_external_node_ptr : m_internal_node; } + /// Return the active Blueprint mesh node for the current backing store. const conduit::Node& getBlueprintMeshNode() const { return isConduitBacked() ? *m_external_node_ptr : m_internal_node; } + /// Return the active Blueprint topology node. const conduit::Node& getBlueprintTopologyNode() const { return getBlueprintMeshNode().fetch_existing("topologies").fetch_existing(m_topology_name); } + /// Return a named Blueprint coordset node. conduit::Node& getBlueprintCoordsetNode(const std::string& name) { return getBlueprintMeshNode().fetch_existing("coordsets").fetch_existing(name); } + /// Return a named Blueprint coordset node. const conduit::Node& getBlueprintCoordsetNode(const std::string& name) const { return getBlueprintMeshNode().fetch_existing("coordsets").fetch_existing(name); } + /// Return whether a named Blueprint field is present. bool hasField(const std::string& name) const { return getBlueprintMeshNode().has_path(axom::fmt::format("fields/{}", name)); } + /// Return a named Blueprint field node. conduit::Node& getField(const std::string& name) { return getBlueprintMeshNode().fetch_existing("fields").fetch_existing(name); } + /// Return a named Blueprint field node. const conduit::Node& getField(const std::string& name) const { return getBlueprintMeshNode().fetch_existing("fields").fetch_existing(name); } + /// Return a shape in/out field, if present. conduit::Node* getShapeFunction(const std::string& name) { return hasField(name) ? &getField(name) : nullptr; } + /// Return a shape in/out field, if present. const conduit::Node* getShapeFunction(const std::string& name) const { return hasField(name) ? &getField(name) : nullptr; } - void deleteShapeFunction(const std::string& name) - { - if(isSidreBacked()) - { - const std::string fieldPath = axom::fmt::format("fields/{}", name); - if(m_group_ptr->hasGroup(fieldPath)) - { - m_group_ptr->destroyGroupAndData(fieldPath); - refreshBlueprintMeshNode(); - } - return; - } - - conduit::Node& bpMeshNode = getBlueprintMeshNode(); - if(bpMeshNode.has_path("fields")) - { - conduit::Node& n_fields = bpMeshNode["fields"]; - if(n_fields.has_path(name)) - { - n_fields.remove(name); - } - } - } + /*! + * \brief Remove a shape in/out field from the active Blueprint mesh. + * + * \param name The field name to remove. + */ + void deleteShapeFunction(const std::string& name); + /// Return a material in/out field, if present. conduit::Node* getMaterialFunction(const std::string& name) { return hasField(name) ? &getField(name) : nullptr; } + /// Return a material in/out field, if present. const conduit::Node* getMaterialFunction(const std::string& name) const { return hasField(name) ? &getField(name) : nullptr; } + /*! + * \brief Create or replace an element-associated Blueprint field. + * + * \param name The field name. + * \param topologyName The associated topology name. + * \param size The number of scalar values to allocate. + * \param addVolumeDependent Whether to add the `volume_dependent` metadata. + * \param volumeDependent Value to store in `volume_dependent` when requested. + * + * \return A writable view over the allocated field values. + */ axom::ArrayView createField(const std::string& name, const std::string& topologyName, axom::IndexType size, bool addVolumeDependent = false, - bool volumeDependent = false) - { - if(isSidreBacked()) - { - const std::string fieldPath = axom::fmt::format("fields/{}", name); - if(m_group_ptr->hasGroup(fieldPath)) - { - m_group_ptr->destroyGroupAndData(fieldPath); - } - - auto* fieldGrp = m_group_ptr->createGroup(fieldPath); - SLIC_ASSERT(fieldGrp != nullptr); - fieldGrp->createViewString("association", "element"); - fieldGrp->createViewString("topology", topologyName); - if(addVolumeDependent) - { - fieldGrp->createViewString("volume_dependent", volumeDependent ? "true" : "false"); - } - - auto* valuesView = - fieldGrp->createViewAndAllocate("values", axom::sidre::DataTypeId::FLOAT64_ID, size); - SLIC_ASSERT(valuesView != nullptr); - refreshBlueprintMeshNode(); - return axom::ArrayView(static_cast(valuesView->getVoidPtr()), size); - } - - conduit::Node& fieldNode = getBlueprintMeshNode()["fields/" + name]; - fieldNode.reset(); - fieldNode["association"] = "element"; - fieldNode["topology"] = topologyName; - if(addVolumeDependent) - { - fieldNode["volume_dependent"] = volumeDependent ? "true" : "false"; - } - - const auto conduitAllocatorId = axom::sidre::ConduitMemory::axomAllocIdToConduit(m_allocator_id); - conduit::Node& valuesNode = fieldNode["values"]; - valuesNode.set_allocator(conduitAllocatorId); - valuesNode.set(conduit::DataType::float64(size)); - return axom::bump::utilities::make_array_view(valuesNode); - } + bool volumeDependent = false); #if defined(AXOM_USE_BUMP) - void importQuadraturePointMesh(const conduit::Node& quadratureMesh) - { - auto replaceSubtree = [&](const std::string& path, const conduit::Node& node) { - if(isSidreBacked()) - { - if(m_group_ptr->hasGroup(path)) - { - m_group_ptr->destroyGroupAndData(path); - } - - auto* group = m_group_ptr->createGroup(path); - SLIC_ERROR_IF(group == nullptr, - axom::fmt::format("Failed to create Sidre group for Blueprint path '{}'.", - path)); - const bool importSuccess = group->importConduitTree(node); - SLIC_ERROR_IF(!importSuccess, - axom::fmt::format("Failed to import Blueprint subtree '{}'.", path)); - return; - } - - conduit::Node& outputNode = getBlueprintMeshNode()[path]; - outputNode.reset(); - outputNode.update(node); - }; - - const std::string quadratureCoordsetPath = - axom::fmt::format("coordsets/{}", QUADRATURE_COORDSET_NAME); - const std::string quadratureTopologyPath = - axom::fmt::format("topologies/{}", QUADRATURE_TOPOLOGY_NAME); - const std::string originalElementsPath = - axom::fmt::format("fields/{}", ORIGINAL_ELEMENTS_FIELD_NAME); - const std::string quadratureWeightsPath = - axom::fmt::format("fields/{}", QUADRATURE_WEIGHTS_FIELD_NAME); - const std::string quadraturePhysicalWeightsPath = - axom::fmt::format("fields/{}", QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME); - - SLIC_ERROR_IF(!quadratureMesh.has_path(quadratureCoordsetPath), - "Quadrature mesh is missing its Blueprint quadrature coordset."); - SLIC_ERROR_IF(!quadratureMesh.has_path(quadratureTopologyPath), - "Quadrature mesh is missing its Blueprint quadrature topology."); - - replaceSubtree(quadratureCoordsetPath, quadratureMesh.fetch_existing(quadratureCoordsetPath)); - replaceSubtree(quadratureTopologyPath, quadratureMesh.fetch_existing(quadratureTopologyPath)); - replaceSubtree(originalElementsPath, quadratureMesh.fetch_existing(originalElementsPath)); - replaceSubtree(quadratureWeightsPath, quadratureMesh.fetch_existing(quadratureWeightsPath)); - - if(quadratureMesh.has_path(quadraturePhysicalWeightsPath)) - { - replaceSubtree(quadraturePhysicalWeightsPath, - quadratureMesh.fetch_existing(quadraturePhysicalWeightsPath)); - } - - if(isSidreBacked()) - { - refreshBlueprintMeshNode(); - } - } - - conduit::Node* createMaterialFunction(const std::string& name) - { - SLIC_ERROR_IF( - !getBlueprintMeshNode().has_path(axom::fmt::format("coordsets/{}/values", QUADRATURE_COORDSET_NAME)), - std::string("Cannot create material function '") + name + "' without quadrature points."); - - const conduit::Node& values = - getBlueprintCoordsetNode(QUADRATURE_COORDSET_NAME).fetch_existing("values"); - const auto numValues = values.child(0).dtype().number_of_elements(); - - auto fieldValues = createField(name, QUADRATURE_TOPOLOGY_NAME, numValues); - for(axom::IndexType i = 0; i < fieldValues.size(); ++i) - { - fieldValues[i] = 0.; - } - - return &getField(name); - } + /*! + * \brief Import generated quadrature Blueprint objects into the active mesh. + * + * \param quadratureMesh A mesh node containing the generated quadrature + * coordset, topology, and support fields. + */ + void importQuadraturePointMesh(const conduit::Node& quadratureMesh); + + /*! + * \brief Create a zero-initialized material in/out field on the quadrature topology. + * + * \param name The field name to create. + * + * \return The created field node. + */ + conduit::Node* createMaterialFunction(const std::string& name); #endif }; From 80d9375c790700346288782005a14ff109eac52c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 12 Jun 2026 18:30:50 -0700 Subject: [PATCH 45/72] More refactoring for conduit+sidre support. make style. --- src/axom/quest/IntersectionShaper.hpp | 22 ++-- src/axom/quest/SamplingShaper.cpp | 19 ++- src/axom/quest/SamplingShaper.hpp | 3 +- src/axom/quest/Shaper.cpp | 12 +- .../quest/detail/shaping/shaping_helpers.cpp | 2 +- .../shaping/shaping_helpers_blueprint.cpp | 113 ++++++++---------- .../shaping/shaping_helpers_blueprint.hpp | 63 +++++++++- 7 files changed, 132 insertions(+), 102 deletions(-) diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index fd05c2f5f0..f30f90f4c6 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -1992,17 +1992,12 @@ class IntersectionShaper : public Shaper #if defined(AXOM_USE_CONDUIT) if(m_bp_state != nullptr) { - const conduit::Node& bpMeshNode = m_bp_state->getBlueprintMeshNode(); - if(bpMeshNode.has_path("fields")) + for(const auto& fieldName : m_bp_state->fieldNames()) { - const conduit::Node& fieldsNode = bpMeshNode.fetch_existing("fields"); - for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) + std::string materialName = fieldNameToMaterialName(fieldName); + if(!materialName.empty()) { - std::string materialName = fieldNameToMaterialName(fieldsNode.child(i).name()); - if(!materialName.empty()) - { - materialNames.emplace_back(materialName); - } + materialNames.emplace_back(materialName); } } } @@ -2581,7 +2576,7 @@ class IntersectionShaper : public Shaper { conduit::Node& fieldNode = m_bp_state->getField(fieldName); SLIC_ASSERT(fieldNode.fetch_existing("association").as_string() == std::string("element")); - SLIC_ASSERT(fieldNode.fetch_existing("topology").as_string() == m_bp_state->m_topology_name); + SLIC_ASSERT(fieldNode.fetch_existing("topology").as_string() == m_bp_state->topologyName()); conduit::Node& valuesNode = fieldNode.fetch_existing("values"); SLIC_ASSERT(valuesNode.dtype().id() == dtype.id()); @@ -2613,8 +2608,11 @@ class IntersectionShaper : public Shaper } else if(m_bp_state->isSidreBacked()) { - rval = - m_bp_state->createField(fieldName, m_bp_state->m_topology_name, m_cellCount, true, volumeDependent); + rval = m_bp_state->createField(fieldName, + m_bp_state->topologyName(), + m_cellCount, + true, + volumeDependent); } } } diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 69430196c7..d5a7386f9a 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -169,32 +169,27 @@ void SamplingShaper::saveQuadraturePoints(const std::string& filename) const { const conduit::Node& bpMesh = m_bp_state->getBlueprintMeshNode(); - if(!bpMesh.has_path(axom::fmt::format("coordsets/{}", - shaping::QUADRATURE_COORDSET_NAME)) || - !bpMesh.has_path(axom::fmt::format("topologies/{}", - shaping::QUADRATURE_TOPOLOGY_NAME))) + if(!bpMesh.has_path(axom::fmt::format("coordsets/{}", shaping::QUADRATURE_COORDSET_NAME)) || + !bpMesh.has_path(axom::fmt::format("topologies/{}", shaping::QUADRATURE_TOPOLOGY_NAME))) { SLIC_WARNING("No Blueprint quadrature point mesh is available to save."); return; } n_mesh["coordsets"][shaping::QUADRATURE_COORDSET_NAME].update( - bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", - shaping::QUADRATURE_COORDSET_NAME))); + bpMesh.fetch_existing(axom::fmt::format("coordsets/{}", shaping::QUADRATURE_COORDSET_NAME))); n_mesh["topologies"][shaping::QUADRATURE_TOPOLOGY_NAME].update( - bpMesh.fetch_existing(axom::fmt::format("topologies/{}", - shaping::QUADRATURE_TOPOLOGY_NAME))); + bpMesh.fetch_existing(axom::fmt::format("topologies/{}", shaping::QUADRATURE_TOPOLOGY_NAME))); if(bpMesh.has_path("fields")) { - const conduit::Node& fields = bpMesh.fetch_existing("fields"); - for(conduit::index_t i = 0; i < fields.number_of_children(); ++i) + for(const auto& fieldName : m_bp_state->fieldNames()) { - const conduit::Node& field = fields.child(i); + const conduit::Node& field = m_bp_state->getField(fieldName); if(field.has_path("topology") && field.fetch_existing("topology").as_string() == shaping::QUADRATURE_TOPOLOGY_NAME) { - n_mesh["fields"][field.name()].update(field); + n_mesh["fields"][fieldName].update(field); } } } diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 5f92333ace..9540585ae2 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -734,7 +734,8 @@ class SamplingShaper : public Shaper thisMatName, shouldReplace ? "yes" : "no")); - auto* otherMatFunc = meshState.getMaterialFunction(shaping::materialInOutFieldName(otherMatName)); + auto* otherMatFunc = + meshState.getMaterialFunction(shaping::materialInOutFieldName(otherMatName)); SLIC_ERROR_IF(otherMatFunc == nullptr, axom::fmt::format("Missing inout samples for material '{}' while applying " "replacement rules for shape '{}'.", diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index b9091224c5..970e57c102 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -104,12 +104,9 @@ Shaper::Shaper(RuntimePolicy execPolicy, { m_bp_state = createBlueprintState(); bpGrp->setDefaultArrayAllocator(m_allocatorId); - m_bp_state->m_group_ptr = bpGrp; - m_bp_state->m_allocator_id = m_allocatorId; - m_bp_state->m_topology_name = resolveBlueprintTopologyName(bpGrp, topo); - m_bp_state->m_external_node_ptr = nullptr; + m_bp_state->initialize(bpGrp, m_allocatorId, resolveBlueprintTopologyName(bpGrp, topo)); - SLIC_ASSERT(m_bp_state->m_group_ptr != nullptr); + SLIC_ASSERT(m_bp_state->isSidreBacked()); refreshBlueprintMeshState(); @@ -139,10 +136,7 @@ Shaper::Shaper(RuntimePolicy execPolicy, AXOM_ANNOTATE_SCOPE("Shaper::Shaper_Node"); m_bp_state = createBlueprintState(); - m_bp_state->m_group_ptr = nullptr; - m_bp_state->m_allocator_id = m_allocatorId; - m_bp_state->m_topology_name = resolveBlueprintTopologyName(bpNode, topo); - m_bp_state->m_external_node_ptr = &bpNode; + m_bp_state->initialize(&bpNode, m_allocatorId, resolveBlueprintTopologyName(bpNode, topo)); refreshBlueprintMeshState(); diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index c6883b2bc8..4667c193ce 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -22,7 +22,7 @@ constexpr const char* SHAPE_VOLUME_FRACTION_PREFIX = "shape_vol_frac_"; std::string extractSuffixedName(const std::string& fieldName, const std::string& prefix) { return axom::utilities::string::startsWith(fieldName, prefix) ? fieldName.substr(prefix.size()) - : std::string {}; + : std::string {}; } } // namespace diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 1157e829be..b045ddbb87 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -146,6 +146,29 @@ int BlueprintState::meshDimension() const return -1; } +std::vector BlueprintState::childNames(const std::string& path) const +{ + std::vector names; + const conduit::Node& bpMeshNode = getBlueprintMeshNode(); + if(!bpMeshNode.has_path(path)) + { + return names; + } + + const conduit::Node& node = bpMeshNode.fetch_existing(path); + if(!node.dtype().is_object()) + { + return names; + } + + names.reserve(node.number_of_children()); + for(conduit::index_t i = 0; i < node.number_of_children(); ++i) + { + names.push_back(node.child(i).name()); + } + return names; +} + void BlueprintState::ensureUnstructured(axom::runtime_policy::Policy execPolicy) { const std::string topoType = getBlueprintTopologyNode().fetch_existing("type").as_string(); @@ -165,14 +188,14 @@ void BlueprintState::ensureUnstructured(axom::runtime_policy::Policy execPolicy) if(shapeType == "hex") { axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d(m_group_ptr, - m_topology_name, - execPolicy); + topologyName(), + execPolicy); } else if(shapeType == "quad") { axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d(m_group_ptr, - m_topology_name, - execPolicy); + topologyName(), + execPolicy); } else { @@ -265,8 +288,7 @@ void BlueprintState::importQuadraturePointMesh(const conduit::Node& quadratureMe auto* group = m_group_ptr->createGroup(path); SLIC_ERROR_IF(group == nullptr, - axom::fmt::format("Failed to create Sidre group for Blueprint path '{}'.", - path)); + axom::fmt::format("Failed to create Sidre group for Blueprint path '{}'.", path)); const bool importSuccess = group->importConduitTree(node); SLIC_ERROR_IF(!importSuccess, axom::fmt::format("Failed to import Blueprint subtree '{}'.", path)); @@ -313,9 +335,10 @@ void BlueprintState::importQuadraturePointMesh(const conduit::Node& quadratureMe conduit::Node* BlueprintState::createMaterialFunction(const std::string& name) { - SLIC_ERROR_IF(!getBlueprintMeshNode().has_path( - axom::fmt::format("coordsets/{}/values", QUADRATURE_COORDSET_NAME)), - std::string("Cannot create material function '") + name + "' without quadrature points."); + SLIC_ERROR_IF( + !getBlueprintMeshNode().has_path( + axom::fmt::format("coordsets/{}/values", QUADRATURE_COORDSET_NAME)), + std::string("Cannot create material function '") + name + "' without quadrature points."); const conduit::Node& values = getBlueprintCoordsetNode(QUADRATURE_COORDSET_NAME).fetch_existing("values"); @@ -335,32 +358,13 @@ void printRegisteredFieldNames(const BlueprintState& bpState, VolFracSampling AXOM_UNUSED_PARAM(vfSampling), const std::string& initialMessage) { - auto extractChildren = [](const conduit::Node& node) { - std::vector names; - if(node.dtype().is_object()) - { - names.reserve(node.number_of_children()); - for(conduit::index_t i = 0; i < node.number_of_children(); ++i) - { - names.push_back(node.child(i).name()); - } - } - return names; - }; - auto extractMatchingFields = [&](const std::string& prefix) { std::vector names; - const conduit::Node& bpMeshNode = bpState.getBlueprintMeshNode(); - if(bpMeshNode.has_path("fields")) + for(const auto& name : bpState.fieldNames()) { - const conduit::Node& fieldsNode = bpMeshNode.fetch_existing("fields"); - for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) + if(axom::utilities::string::startsWith(name, prefix)) { - const std::string name = fieldsNode.child(i).name(); - if(axom::utilities::string::startsWith(name, prefix)) - { - names.push_back(name); - } + names.push_back(name); } } return names; @@ -368,34 +372,21 @@ void printRegisteredFieldNames(const BlueprintState& bpState, auto extractOtherFields = [&]() { std::vector names; - const conduit::Node& bpMeshNode = bpState.getBlueprintMeshNode(); - if(bpMeshNode.has_path("fields")) + for(const auto& name : bpState.fieldNames()) { - const conduit::Node& fieldsNode = bpMeshNode.fetch_existing("fields"); - for(conduit::index_t i = 0; i < fieldsNode.number_of_children(); ++i) + if(shaping::materialNameFromVolumeFractionFieldName(name).empty() && + shaping::materialNameFromMaterialInOutFieldName(name).empty() && + !axom::utilities::string::startsWith(name, "inout_")) { - const std::string name = fieldsNode.child(i).name(); - if(shaping::materialNameFromVolumeFractionFieldName(name).empty() && - shaping::materialNameFromMaterialInOutFieldName(name).empty() && - !axom::utilities::string::startsWith(name, "inout_")) - { - names.push_back(name); - } + names.push_back(name); } } return names; }; - const conduit::Node& bpMeshNode = bpState.getBlueprintMeshNode(); - const std::vector topologyNames = bpMeshNode.has_path("topologies") - ? extractChildren(bpMeshNode.fetch_existing("topologies")) - : std::vector {}; - const std::vector coordsetNames = bpMeshNode.has_path("coordsets") - ? extractChildren(bpMeshNode.fetch_existing("coordsets")) - : std::vector {}; - const std::vector fieldNames = bpMeshNode.has_path("fields") - ? extractChildren(bpMeshNode.fetch_existing("fields")) - : std::vector {}; + const std::vector topologyNames = bpState.topologyNames(); + const std::vector coordsetNames = bpState.coordsetNames(); + const std::vector fieldNames = bpState.fieldNames(); axom::fmt::memory_buffer out; axom::fmt::format_to(std::back_inserter(out), @@ -546,8 +537,8 @@ void generateSamplingPositions(BlueprintState& bpState, conduit::Node quadratureMesh; generateQuadraturePointMesh(bpMeshNode, quadratureMesh, - bpState.m_topology_name, - bpState.m_allocator_id, + bpState.topologyName(), + bpState.allocatorId(), sampleResolution, quadratureType); bpState.importQuadraturePointMesh(quadratureMesh); @@ -556,8 +547,8 @@ void generateSamplingPositions(BlueprintState& bpState, { generateQuadraturePointMesh(bpMeshNode, bpMeshNode, - bpState.m_topology_name, - bpState.m_allocator_id, + bpState.topologyName(), + bpState.allocatorId(), sampleResolution, quadratureType); } @@ -645,16 +636,14 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin !bpMeshNode.has_path(quadratureWeightsPath), "Missing Blueprint quadrature weight field for volume fraction projection."); - const conduit::Node& topoNode = - bpMeshNode.fetch_existing("topologies").fetch_existing(bpState.m_topology_name); + const conduit::Node& topoNode = bpState.getBlueprintTopologyNode(); const axom::IndexType numZones = conduit::blueprint::mesh::topology::length(topoNode); namespace utils = axom::bump::utilities; const auto originalElements = utils::make_array_view(bpMeshNode.fetch_existing(originalElementsPath)); - const conduit::Node& quadratureWeightsNode = - bpMeshNode.has_path(quadraturePhysicalWeightsPath) + const conduit::Node& quadratureWeightsNode = bpMeshNode.has_path(quadraturePhysicalWeightsPath) ? bpMeshNode.fetch_existing(quadraturePhysicalWeightsPath) : bpMeshNode.fetch_existing(quadratureWeightsPath); const auto quadratureWeights = utils::make_array_view(quadratureWeightsNode); @@ -664,8 +653,8 @@ void computeVolumeFractionsForMaterial(BlueprintState& bpState, const std::strin SLIC_ASSERT(originalElements.size() == inoutValues.size()); const std::string vfName = shaping::volumeFractionFieldName(materialName); - auto vfValues = bpState.createField(vfName, bpState.m_topology_name, numZones); - axom::Array totalWeights(numZones, numZones, bpState.m_allocator_id); + auto vfValues = bpState.createField(vfName, bpState.topologyName(), numZones); + axom::Array totalWeights(numZones, numZones, bpState.allocatorId()); auto totalWeightsView = totalWeights.view(); for(axom::IndexType zoneIdx = 0; zoneIdx < vfValues.size(); ++zoneIdx) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 377dc644e0..ac2c482367 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -39,13 +39,13 @@ namespace shaping */ std::string getBlueprintCellShape(const conduit::Node& topoNode); -#if defined(AXOM_USE_BUMP) + #if defined(AXOM_USE_BUMP) constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; -#endif + #endif /*! * \brief Stores Blueprint mesh state and backend-specific access for Quest shapers. @@ -76,6 +76,36 @@ struct BlueprintState /// Return whether the active Blueprint mesh is backed by a Conduit node. bool isConduitBacked() const { return m_external_node_ptr != nullptr; } + /*! + * \brief Initialize this state from a caller-owned Sidre mesh group. + * + * \param group The Sidre group that backs the Blueprint mesh. + * \param allocatorId Allocator id used for new array allocations. + * \param topologyName Name of the active topology. + */ + void initialize(axom::sidre::Group* group, int allocatorId, const std::string& topologyName) + { + m_group_ptr = group; + m_allocator_id = allocatorId; + m_topology_name = topologyName; + m_external_node_ptr = nullptr; + } + + /*! + * \brief Initialize this state from a caller-owned Blueprint node. + * + * \param node The Conduit node that backs the Blueprint mesh. + * \param allocatorId Allocator id used for new array allocations. + * \param topologyName Name of the active topology. + */ + void initialize(conduit::Node* node, int allocatorId, const std::string& topologyName) + { + m_group_ptr = nullptr; + m_allocator_id = allocatorId; + m_topology_name = topologyName; + m_external_node_ptr = node; + } + /*! * \brief Refresh the cached native Conduit layout for Sidre-backed meshes. * @@ -90,6 +120,12 @@ struct BlueprintState */ int meshDimension() const; + /// Return the allocator id used for new Blueprint array allocations. + int allocatorId() const { return m_allocator_id; } + + /// Return the name of the active Blueprint topology. + const std::string& topologyName() const { return m_topology_name; } + /*! * \brief Convert a structured Blueprint mesh to unstructured topology in place. * @@ -112,7 +148,7 @@ struct BlueprintState /// Return the active Blueprint topology node. const conduit::Node& getBlueprintTopologyNode() const { - return getBlueprintMeshNode().fetch_existing("topologies").fetch_existing(m_topology_name); + return getBlueprintMeshNode().fetch_existing("topologies").fetch_existing(topologyName()); } /// Return a named Blueprint coordset node. @@ -133,6 +169,24 @@ struct BlueprintState return getBlueprintMeshNode().has_path(axom::fmt::format("fields/{}", name)); } + /*! + * \brief Return the child names under a Blueprint object path. + * + * \param path The object path to inspect. + * + * \return A vector containing the child names in insertion order. + */ + std::vector childNames(const std::string& path) const; + + /// Return all registered Blueprint topology names. + std::vector topologyNames() const { return childNames("topologies"); } + + /// Return all registered Blueprint coordset names. + std::vector coordsetNames() const { return childNames("coordsets"); } + + /// Return all registered Blueprint field names. + std::vector fieldNames() const { return childNames("fields"); } + /// Return a named Blueprint field node. conduit::Node& getField(const std::string& name) { @@ -365,8 +419,7 @@ void sampleInOutField(const std::string& shapeName, if constexpr(CoordsetView::dimension() == FromDim) { numQueryPoints = coordsetView.size(); - auto inoutValues = - bpState.createField(inoutName, QUADRATURE_TOPOLOGY_NAME, numQueryPoints); + auto inoutValues = bpState.createField(inoutName, QUADRATURE_TOPOLOGY_NAME, numQueryPoints); for(axom::IndexType i = 0; i < numQueryPoints; ++i) { From 5071b2f3e5eba43504a916c32bf7231c253667ae Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 11:14:17 -0700 Subject: [PATCH 46/72] Added an include file --- src/axom/quest/SamplingShaper.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index d5a7386f9a..8e8cffe4a9 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -5,6 +5,9 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/quest/SamplingShaper.hpp" #include "axom/quest/detail/shaping/shaping_helpers.hpp" +#if defined(AXOM_USE_CONDUIT) + #include "axom/quest/detail/shaping/shaping_helpers_blueprint.hpp" +#endif namespace axom { From cddfcdedbc2526aed18c5e10ed4fcbde80a925be Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 12:25:49 -0700 Subject: [PATCH 47/72] Fixed a volume fraction problem in newer refactors. --- src/axom/quest/examples/shaping_driver.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 2ffce0bc96..5ebb180114 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -965,20 +965,15 @@ int main(int argc, char** argv) if(!params.backgroundMaterial.empty()) { auto material = params.backgroundMaterial; - auto name = axom::fmt::format("vol_frac_{}", material); + auto name = quest::shaping::volumeFractionFieldName(material); const auto num_elements = params.numberOfBoxMeshElements(); - conduit::Node* n_mesh = shaper->getBlueprintMeshNode(); - conduit::Node& n_field = n_mesh->fetch("fields/" + name); - n_field["topology"] = "topology"; - n_field["association"] = "element"; - n_field["values"].set(conduit::DataType::float64(num_elements)); - conduit::float64_array values = n_field["values"].value(); - for(conduit::index_t i = 0; i < num_elements; i++) + auto values = shaper->getBlueprintState()->createField(name, "mesh", num_elements); + for(axom::IndexType i = 0; i < num_elements; i++) { values[i] = 1.; } - + conduit::Node& n_field = shaper->getBlueprintState()->getField(name); std::map initial_grid_functions; initial_grid_functions[material] = &n_field; @@ -996,7 +991,7 @@ int main(int argc, char** argv) if(!params.backgroundMaterial.empty()) { auto material = params.backgroundMaterial; - auto name = axom::fmt::format("vol_frac_{}", material); + auto name = quest::shaping::volumeFractionFieldName(material); const int order = params.outputOrder; const int dim = shapingMesh->Dimension(); From fd90a05becd7939c806065937a1616779aab05c5 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 15:44:00 -0700 Subject: [PATCH 48/72] Changed a macro guard. --- src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index ac2c482367..7b88d54c66 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -39,13 +39,11 @@ namespace shaping */ std::string getBlueprintCellShape(const conduit::Node& topoNode); - #if defined(AXOM_USE_BUMP) constexpr const char* QUADRATURE_COORDSET_NAME = "quadrature_points"; constexpr const char* QUADRATURE_TOPOLOGY_NAME = "quadrature_points"; constexpr const char* ORIGINAL_ELEMENTS_FIELD_NAME = "originalElements"; constexpr const char* QUADRATURE_WEIGHTS_FIELD_NAME = "quadratureWeights"; constexpr const char* QUADRATURE_PHYSICAL_WEIGHTS_FIELD_NAME = "quadraturePhysicalWeights"; - #endif /*! * \brief Stores Blueprint mesh state and backend-specific access for Quest shapers. From 8965848aa8b153e4861835d0af424f13be3cb48a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 17:42:39 -0700 Subject: [PATCH 49/72] Added bump-based mesh and coordset conversion into the shaping helpers. --- src/axom/bump/CMakeLists.txt | 3 +- src/axom/bump/MakeExplicitCoordset.hpp | 114 ++++++++++++++++++ src/axom/quest/Shaper.cpp | 11 +- .../shaping/shaping_helpers_blueprint.cpp | 46 +++---- src/axom/quest/util/mesh_helpers.cpp | 63 ++++++++++ src/axom/quest/util/mesh_helpers.hpp | 16 +++ 6 files changed, 222 insertions(+), 31 deletions(-) create mode 100644 src/axom/bump/MakeExplicitCoordset.hpp diff --git a/src/axom/bump/CMakeLists.txt b/src/axom/bump/CMakeLists.txt index b9832fcf91..8308f7c5b3 100644 --- a/src/axom/bump/CMakeLists.txt +++ b/src/axom/bump/CMakeLists.txt @@ -77,13 +77,14 @@ set(bump_headers GenerateQuadratureMesh.hpp HashNaming.hpp IndexingPolicies.hpp - MappedZoneUtilities.hpp + MakeExplicitCoordset.hpp MakePointMesh.hpp MakePolyhedralTopology.hpp MakeUnstructured.hpp MakeZoneCenters.hpp MakeZoneVolumes.hpp MapBasedNaming.hpp + MappedZoneUtilities.hpp MatsetSlicer.hpp MergeCoordsetPoints.hpp MergeMeshes.hpp diff --git a/src/axom/bump/MakeExplicitCoordset.hpp b/src/axom/bump/MakeExplicitCoordset.hpp new file mode 100644 index 0000000000..c99feb7f22 --- /dev/null +++ b/src/axom/bump/MakeExplicitCoordset.hpp @@ -0,0 +1,114 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) +#ifndef AXOM_BUMP_MAKE_EXPLICIT_COORDSET_HPP_ +#define AXOM_BUMP_MAKE_EXPLICIT_COORDSET_HPP_ + +#include "axom/core.hpp" +#include "axom/bump/views/NodeArrayView.hpp" +#include "axom/bump/utilities/utilities.hpp" +#include "axom/bump/utilities/conduit_memory.hpp" +#include "axom/bump/views/dispatch_coordset.hpp" +#include "axom/sidre/core/ConduitMemory.hpp" + +#include + +namespace axom +{ +namespace bump +{ +/*! + * \brief Convert a coordset to an explicit coordset. + * + * \tparam ExecSpace The execution space where the conversion will execute. + */ +template +class MakeExplicitCoordset +{ +public: + /*! + * \brief Converts the supplied coordset into an explicit coordset if it is not already one. + * + * \param[inout] n_coordset The coordset to convert. + * \param allocator_id The allocator id to use when allocating new coordinate memory. + */ + static void execute(conduit::Node &n_coordset, + int allocator_id = axom::execution_space::allocatorID()) + { + const std::string cstype = n_coordset["type"].as_string(); + if(cstype != "explicit") + { + conduit::Node n_dest_coordset; + if(cstype == "uniform") + { + axom::bump::views::dispatch_uniform_coordset(n_coordset, [&](auto coordsetView) + { + convert(coordsetView, n_dest_coordset, allocator_id); + }); + } + else if(cstype == "rectilinear") + { + axom::bump::views::dispatch_rectilinear_coordset(n_coordset, [&](auto coordsetView) + { + convert(coordsetView, n_dest_coordset, allocator_id); + }); + } + else + { + SLIC_ERROR(axom::fmt::format("Unsupported coordset type {}.", cstype)); + } + + n_coordset["type"] = "explicit"; + n_coordset["values"].swap(n_dest_coordset["values"]); + } + } + +// The following members are private (unless using CUDA) +#if !defined(__CUDACC__) +private: +#endif + + /*! + * \brief Copy the coordinates from the input coordset view into values + * components in the supplied output coordset node. + * + * \param coordsetView A view for retrieving points from the source coordset. + * \param n_dest_coordset A Conduit node in which to construct the new coordset. + * \param allocator_id The allocator id to use when allocating new coordinate memory. + */ + template + static void convert(CoordsetView coordsetView, conduit::Node &n_dest_coordset, int allocator_id) + { + const auto conduitAllocatorId = axom::sidre::ConduitMemory::axomAllocIdToConduit(allocator_id); + + // Make new coordinate arrays + const char *names[] = {"x", "y", "z"}; + using value_type = typename CoordsetView::value_type; + namespace utils = axom::bump::utilities; + axom::ArrayView comps[3]; + conduit::Node &n_values = n_dest_coordset["values"]; + for(int c = 0; c < coordsetView.dimension(); c++) + { + conduit::Node &n_comp = n_values[names[c]]; + n_comp.set_allocator(conduitAllocatorId); + n_comp.set(conduit::DataType(utils::cpp2conduit::id, coordsetView.size())); + comps[c] = utils::make_array_view(n_comp); + } + + // Copy data from the view into the new coordinate array views. + axom::for_all(coordsetView.size(), AXOM_LAMBDA(axom::IndexType i) + { + const auto pt = coordsetView[i]; + for(int c = 0; c < coordsetView.dimension(); c++) + { + comps[c][i] = pt[c]; + } + }); + } +}; + +} // end namespace bump +} // end namespace axom +#endif diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 970e57c102..8378e6da5f 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -373,21 +373,14 @@ void Shaper::ensureBlueprintMeshIsUnstructured() return; } + AXOM_ANNOTATE_SCOPE("Shaper::convertStructured"); const conduit::Node& topoNode = getBlueprintTopologyNode(); const std::string topoType = topoNode.fetch_existing("type").as_string(); - if(topoType != "structured") - { - return; - } - if(!m_bp_state->isSidreBacked()) + if(topoType != "unstructured") { m_bp_state->ensureUnstructured(m_execPolicy); - return; } - - AXOM_ANNOTATE_SCOPE("Shaper::convertStructured"); - m_bp_state->ensureUnstructured(m_execPolicy); m_cellCount = conduit::blueprint::mesh::topology::length(getBlueprintTopologyNode()); } #endif diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index b045ddbb87..5ee1a6b4ce 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -172,37 +172,41 @@ std::vector BlueprintState::childNames(const std::string& path) con void BlueprintState::ensureUnstructured(axom::runtime_policy::Policy execPolicy) { const std::string topoType = getBlueprintTopologyNode().fetch_existing("type").as_string(); - if(topoType != "structured") + if(topoType == "unstructured") { return; } - if(!isSidreBacked()) + if(isSidreBacked()) { - SLIC_ERROR( - "Structured Blueprint meshes backed by conduit::Node are not yet supported for " - "in-place conversion to unstructured topology."); - } + // Sidre + const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); + if(shapeType == "hex") + { + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d(m_group_ptr, + topologyName(), + execPolicy); + } + else if(shapeType == "quad") + { + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d(m_group_ptr, + topologyName(), + execPolicy); + } + else + { + SLIC_ERROR("Axom Internal error: Unhandled shape type."); + } - const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); - if(shapeType == "hex") - { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_3d(m_group_ptr, - topologyName(), - execPolicy); - } - else if(shapeType == "quad") - { - axom::quest::util::convert_blueprint_structured_explicit_to_unstructured_2d(m_group_ptr, - topologyName(), - execPolicy); + refreshBlueprintMeshNode(); } else { - SLIC_ERROR("Axom Internal error: Unhandled shape type."); + // Conduit + axom::quest::util::convert_blueprint_structured_explicit_to_unstructured(getBlueprintMeshNode(), + topologyName(), + execPolicy); } - - refreshBlueprintMeshNode(); } void BlueprintState::deleteShapeFunction(const std::string& name) diff --git a/src/axom/quest/util/mesh_helpers.cpp b/src/axom/quest/util/mesh_helpers.cpp index b234bb0420..2fdd90776f 100644 --- a/src/axom/quest/util/mesh_helpers.cpp +++ b/src/axom/quest/util/mesh_helpers.cpp @@ -13,6 +13,10 @@ #if defined(AXOM_USE_CONDUIT) #include #endif +#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) + #include "axom/bump/MakeExplicitCoordset.hpp" + #include "axom/bump/MakeUnstructured.hpp" +#endif #include namespace axom @@ -748,6 +752,65 @@ void fill_cartesian_coords_2d_impl(const primal::BoundingBox& domainB } } +#if defined(AXOM_USE_CONDUIT) +#if defined(AXOM_USE_BUMP) +/// Convert a Blueprint topology and coordset stored as conduit::Node to unstructured+explicit +template +void convert_to_unstructured_impl(conduit::Node &n_topo, conduit::Node &n_coordset, const std::string &topologyName) +{ + // Make sure the coordset is explicit, or do nothing if it is already explicit. + axom::bump::MakeExplicitCoordset::execute(n_coordset); + + if(n_topo["type"].as_string() != "unstructured") + { + // Make an unstructured version of the topology. + conduit::Node newMesh; + axom::bump::MakeUnstructured::execute(n_topo, n_coordset, topologyName, newMesh); + + // Swap topology definitions so we keep the converted one. + conduit::Node &n_new_topo = newMesh.fetch_existing("topologies/" + topologyName); + n_topo.swap(n_new_topo); + } +} +#endif + +void convert_blueprint_structured_explicit_to_unstructured(conduit::Node &n_mesh, const std::string &topologyName, + axom::runtime_policy::Policy runtimePolicy) +{ +#if defined(AXOM_USE_BUMP) + SLIC_ERROR_IF(!n_mesh.has_path("topologies/" + topologyName), "Cannot find topology"); + conduit::Node &n_topo = n_mesh.fetch_existing("topologies/" + topologyName); + conduit::Node *n_coordset = const_cast(conduit::blueprint::mesh::utils::find_reference_node(n_topo, "coordset")); + SLIC_ERROR_IF(n_coordset == nullptr, "Cannot find coordset"); + + if(runtimePolicy == axom::runtime_policy::Policy::seq) + { + convert_to_unstructured_impl(n_topo, *n_coordset, topologyName); + } +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + if(runtimePolicy == axom::runtime_policy::Policy::omp) + { + convert_to_unstructured_impl(n_topo, *n_coordset, topologyName); + } +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + if(runtimePolicy == axom::runtime_policy::Policy::cuda) + { + convert_to_unstructured_impl>(n_topo, *n_coordset, topologyName); + } +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + if(runtimePolicy == axom::runtime_policy::Policy::hip) + { + convert_to_unstructured_impl>(n_topo, *n_coordset, topologyName); + } +#endif +#else + SLIC_ERROR("convert_blueprint_structured_explicit_to_unstructured requires Bump."); +#endif +} +#endif + } // namespace util } // namespace quest } // namespace axom diff --git a/src/axom/quest/util/mesh_helpers.hpp b/src/axom/quest/util/mesh_helpers.hpp index d0ebc9ebe1..428a1af1c0 100644 --- a/src/axom/quest/util/mesh_helpers.hpp +++ b/src/axom/quest/util/mesh_helpers.hpp @@ -184,6 +184,22 @@ void convert_blueprint_structured_explicit_to_unstructured_2d_impl(axom::sidre:: * \brief Check if blueprint mesh is valid. */ bool verifyBlueprintMesh(const axom::sidre::Group* meshGrp, conduit::Node info); + +/*! + * \brief Convert a structured explicit blueprint mesh to unstructured. + * \param n_mesh The conduit::Node that contains the coordsets and topologies. + * \param topologyName Name of the blueprint topoloyy to use. + * \param runtimePolicy Runtime policy, see axom::runtime_policy. + * Memory in \c meshGrp must be compatible with the + * specified policy. + * + * \note This function is similar to convert_blueprint_structured_explicit_to_unstructured_2d, + * convert_blueprint_structured_explicit_to_unstructured_3d but it operates on conduit::Node + * through axom::bump. + */ +void convert_blueprint_structured_explicit_to_unstructured(conduit::Node &n_mesh, + const std::string &topologyName, + axom::runtime_policy::Policy runtimePolicy); #endif #endif From b5d32746183232155337911a4dadb89d6ea06c9a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 17:52:54 -0700 Subject: [PATCH 50/72] Added a test for MakeExplicitCoordset. --- src/axom/bump/tests/CMakeLists.txt | 1 + .../tests/bump_make_explicit_coordset.cpp | 229 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 src/axom/bump/tests/bump_make_explicit_coordset.cpp diff --git a/src/axom/bump/tests/CMakeLists.txt b/src/axom/bump/tests/CMakeLists.txt index 937dfc4d31..742d3902fe 100644 --- a/src/axom/bump/tests/CMakeLists.txt +++ b/src/axom/bump/tests/CMakeLists.txt @@ -17,6 +17,7 @@ set(gtest_bump_tests bump_clipfield.cpp bump_cutfield.cpp bump_coordset_extents.cpp + bump_make_explicit_coordset.cpp bump_make_polyhedral_topology.cpp bump_mergemeshes.cpp bump_mesh_operations.cpp diff --git a/src/axom/bump/tests/bump_make_explicit_coordset.cpp b/src/axom/bump/tests/bump_make_explicit_coordset.cpp new file mode 100644 index 0000000000..0ebe5ebd4e --- /dev/null +++ b/src/axom/bump/tests/bump_make_explicit_coordset.cpp @@ -0,0 +1,229 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/bump/MakeExplicitCoordset.hpp" +#include "axom/bump/utilities/conduit_memory.hpp" +#include "axom/bump/views/dispatch_coordset.hpp" + +#include "conduit.hpp" + +namespace +{ + +using seq_exec = axom::SEQ_EXEC; + +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) +using omp_exec = axom::OMP_EXEC; +#else +using omp_exec = seq_exec; +#endif + +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) +constexpr int CUDA_BLOCK_SIZE = 256; +using cuda_exec = axom::CUDA_EXEC; +#else +using cuda_exec = seq_exec; +#endif + +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) +constexpr int HIP_BLOCK_SIZE = 64; +using hip_exec = axom::HIP_EXEC; +#else +using hip_exec = seq_exec; +#endif + +namespace bump = axom::bump; +namespace utils = axom::bump::utilities; +namespace views = axom::bump::views; + +struct CoordsetData +{ + int dimension {0}; + axom::IndexType size {0}; + axom::StackArray, 3> components; +}; + +CoordsetData collectCoordsetData(const conduit::Node& coordset) +{ + CoordsetData data; + views::dispatch_coordset(coordset, [&](auto coordsetView) { + data.dimension = coordsetView.dimension(); + data.size = coordsetView.size(); + + for(int dim = 0; dim < data.dimension; ++dim) + { + data.components[dim].resize(data.size); + } + + for(axom::IndexType i = 0; i < data.size; ++i) + { + const auto pt = coordsetView[i]; + for(int dim = 0; dim < data.dimension; ++dim) + { + data.components[dim][i] = pt[dim]; + } + } + }); + + return data; +} + +void expectExplicitCoordsetMatches(const conduit::Node& coordset, const CoordsetData& expected) +{ + ASSERT_TRUE(coordset.has_path("type")); + EXPECT_EQ(coordset["type"].as_string(), "explicit"); + + views::dispatch_explicit_coordset(coordset, [&](auto coordsetView) { + EXPECT_EQ(coordsetView.dimension(), expected.dimension); + EXPECT_EQ(coordsetView.size(), expected.size); + + for(axom::IndexType i = 0; i < expected.size; ++i) + { + const auto pt = coordsetView[i]; + for(int dim = 0; dim < expected.dimension; ++dim) + { + EXPECT_NEAR(pt[dim], expected.components[dim][i], 1e-12); + } + } + }); +} + +template +void checkMakeExplicitCoordset(const char* yaml) +{ + conduit::Node hostCoordset; + hostCoordset.parse(yaml); + const CoordsetData expected = collectCoordsetData(hostCoordset); + + conduit::Node deviceCoordset; + utils::copy(deviceCoordset, hostCoordset); + + bump::MakeExplicitCoordset::execute(deviceCoordset); + + conduit::Node convertedCoordset; + utils::copy(convertedCoordset, deviceCoordset); + expectExplicitCoordsetMatches(convertedCoordset, expected); +} + +template +struct test_make_explicit_coordset +{ + static void uniform_2d() + { + const char* yaml = R"xx( +type: uniform +dims: + i: 4 + j: 3 +origin: + x: 1.5 + y: -2. +spacing: + dx: 0.25 + dy: 1.5 +)xx"; + + checkMakeExplicitCoordset(yaml); + } + + static void rectilinear_3d() + { + const char* yaml = R"xx( +type: rectilinear +values: + x: [-1., 0.5, 2.] + y: [2., 5.] + z: [0., 1.5, 3.5] +)xx"; + + checkMakeExplicitCoordset(yaml); + } + + static void explicit_noop() + { + const char* yaml = R"xx( +type: explicit +values: + x: [0., 1., 3.] + y: [2., 4., 8.] + z: [-1., -2., -4.] +)xx"; + + checkMakeExplicitCoordset(yaml); + } +}; + +TEST(bump_make_explicit_coordset, uniform_2d_seq) +{ + test_make_explicit_coordset::uniform_2d(); +} + +TEST(bump_make_explicit_coordset, rectilinear_3d_seq) +{ + test_make_explicit_coordset::rectilinear_3d(); +} + +TEST(bump_make_explicit_coordset, explicit_noop_seq) +{ + test_make_explicit_coordset::explicit_noop(); +} + +#if defined(AXOM_USE_OPENMP) +TEST(bump_make_explicit_coordset, uniform_2d_omp) +{ + test_make_explicit_coordset::uniform_2d(); +} + +TEST(bump_make_explicit_coordset, rectilinear_3d_omp) +{ + test_make_explicit_coordset::rectilinear_3d(); +} + +TEST(bump_make_explicit_coordset, explicit_noop_omp) +{ + test_make_explicit_coordset::explicit_noop(); +} +#endif + +#if defined(AXOM_USE_CUDA) +TEST(bump_make_explicit_coordset, uniform_2d_cuda) +{ + test_make_explicit_coordset::uniform_2d(); +} + +TEST(bump_make_explicit_coordset, rectilinear_3d_cuda) +{ + test_make_explicit_coordset::rectilinear_3d(); +} + +TEST(bump_make_explicit_coordset, explicit_noop_cuda) +{ + test_make_explicit_coordset::explicit_noop(); +} +#endif + +#if defined(AXOM_USE_HIP) +TEST(bump_make_explicit_coordset, uniform_2d_hip) +{ + test_make_explicit_coordset::uniform_2d(); +} + +TEST(bump_make_explicit_coordset, rectilinear_3d_hip) +{ + test_make_explicit_coordset::rectilinear_3d(); +} + +TEST(bump_make_explicit_coordset, explicit_noop_hip) +{ + test_make_explicit_coordset::explicit_noop(); +} +#endif + +} // namespace From cc5bc0aa84b0f93d1bbb0d016e17548f6baa4bbe Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 18:11:41 -0700 Subject: [PATCH 51/72] Some refactoring in IntersectionShaper to help it create fields via BlueprintState. --- src/axom/quest/IntersectionShaper.hpp | 62 ++++--------------- src/axom/quest/Shaper.cpp | 26 +++----- .../shaping/shaping_helpers_blueprint.cpp | 16 ++++- .../shaping/shaping_helpers_blueprint.hpp | 34 ++++++++++ 4 files changed, 68 insertions(+), 70 deletions(-) diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index f30f90f4c6..60f6ea787c 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -2570,50 +2570,17 @@ class IntersectionShaper : public Shaper #if defined(AXOM_USE_CONDUIT) if(m_bp_state != nullptr) { - std::string fieldPath = "fields/" + fieldName; - auto dtype = conduit::DataType::float64(m_cellCount); if(m_bp_state->hasField(fieldName)) { - conduit::Node& fieldNode = m_bp_state->getField(fieldName); - SLIC_ASSERT(fieldNode.fetch_existing("association").as_string() == std::string("element")); - SLIC_ASSERT(fieldNode.fetch_existing("topology").as_string() == m_bp_state->topologyName()); - - conduit::Node& valuesNode = fieldNode.fetch_existing("values"); - SLIC_ASSERT(valuesNode.dtype().id() == dtype.id()); - SLIC_ASSERT(valuesNode.dtype().number_of_elements() == m_cellCount); - rval = axom::ArrayView(valuesNode.as_double_ptr(), m_cellCount); + rval = m_bp_state->getScalarFieldView(fieldName, m_cellCount); } else { - if(m_bp_state->isConduitBacked()) - { - /* - If the computational mesh is an external conduit::Node, it - must have all necessary fields. We will only generate - fields for meshes in sidre::Group, where the user can set - the allocator id for only array data. conduit::Node doesn't - have this capability. - */ - SLIC_WARNING_IF(m_bp_state->isConduitBacked(), - "For a computational mesh in a conduit::Node, all" - " output fields must be preallocated before shaping." - " IntersectionShaper will NOT contravene the user's" - " memory management. The cell-centered field '" + - fieldPath + - "' is missing. Please pre-allocate" - " this output memory, or to have IntersectionShaper" - " allocate it, construct the IntersectionShaper" - " with the mesh as a sidre::Group with your" - " specific allocator id."); - } - else if(m_bp_state->isSidreBacked()) - { - rval = m_bp_state->createField(fieldName, - m_bp_state->topologyName(), - m_cellCount, - true, - volumeDependent); - } + rval = m_bp_state->createField(fieldName, + m_bp_state->topologyName(), + m_cellCount, + true, + volumeDependent); } } #endif @@ -2735,12 +2702,10 @@ class IntersectionShaper : public Shaper // m_group_ptr->createNativeLayout(m_internal_node); const conduit::Node& topoNode = m_bp_state->getBlueprintTopologyNode(); - const std::string coordsetName = topoNode.fetch_existing("coordset").as_string(); // Assume unstructured and hexahedral - SLIC_ERROR_IF(topoNode["type"].as_string() != "unstructured", - "topology type must be 'unstructured'"); - SLIC_ERROR_IF(topoNode["elements/shape"].as_string() != "quad", "element shape must be 'quad'"); + SLIC_ERROR_IF(m_bp_state->topologyType() != "unstructured", "topology type must be 'unstructured'"); + SLIC_ERROR_IF(m_bp_state->cellShape() != "quad", "element shape must be 'quad'"); const auto& connNode = topoNode["elements/connectivity"]; SLIC_ERROR_IF( @@ -2754,7 +2719,7 @@ class IntersectionShaper : public Shaper const auto* connPtr = static_cast(connNode.data_ptr()); axom::ArrayView conn(connPtr, m_cellCount, NUM_VERTS_PER_QUAD); - const conduit::Node& coordNode = m_bp_state->getBlueprintCoordsetNode(coordsetName); + const conduit::Node& coordNode = m_bp_state->getBlueprintCoordsetNode(); const conduit::Node& coordValues = coordNode.fetch_existing("values"); axom::IndexType vertexCount = coordValues["x"].dtype().number_of_elements(); bool isInterleaved = conduit::blueprint::mcarray::is_interleaved(coordValues); @@ -2807,13 +2772,10 @@ class IntersectionShaper : public Shaper // m_group_ptr->createNativeLayout(m_internal_node); const conduit::Node& topoNode = m_bp_state->getBlueprintTopologyNode(); - const conduit::Node& topoCoordsetNode = topoNode.fetch_existing("coordset"); - const std::string coordsetName = topoCoordsetNode.as_string(); // Assume unstructured and hexahedral - SLIC_ERROR_IF(topoNode["type"].as_string() != "unstructured", - "topology type must be 'unstructured'"); - SLIC_ERROR_IF(topoNode["elements/shape"].as_string() != "hex", "element shape must be 'hex'"); + SLIC_ERROR_IF(m_bp_state->topologyType() != "unstructured", "topology type must be 'unstructured'"); + SLIC_ERROR_IF(m_bp_state->cellShape() != "hex", "element shape must be 'hex'"); const auto& connNode = topoNode["elements/connectivity"]; SLIC_ERROR_IF( @@ -2827,7 +2789,7 @@ class IntersectionShaper : public Shaper const auto* connPtr = static_cast(connNode.data_ptr()); axom::ArrayView conn(connPtr, m_cellCount, NUM_VERTS_PER_HEX); - const conduit::Node& coordNode = m_bp_state->getBlueprintCoordsetNode(coordsetName); + const conduit::Node& coordNode = m_bp_state->getBlueprintCoordsetNode(); const conduit::Node& coordValues = coordNode.fetch_existing("values"); axom::IndexType vertexCount = coordValues["x"].dtype().number_of_elements(); bool isInterleaved = conduit::blueprint::mcarray::is_interleaved(coordValues); diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 8378e6da5f..1433a7aa06 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -306,8 +306,7 @@ const conduit::Node& Shaper::getBlueprintTopologyNode() const const conduit::Node& Shaper::getBlueprintCoordsetNode() const { SLIC_ASSERT(m_bp_state != nullptr); - const std::string coordsetName = getBlueprintTopologyNode().fetch_existing("coordset").as_string(); - return m_bp_state->getBlueprintCoordsetNode(coordsetName); + return m_bp_state->getBlueprintCoordsetNode(); } std::string Shaper::getBlueprintCellShape() const @@ -317,18 +316,8 @@ std::string Shaper::getBlueprintCellShape() const int Shaper::getBlueprintMeshDimension() const { - const std::string shapeType = getBlueprintCellShape(); - if(shapeType == "quad") - { - return 2; - } - if(shapeType == "hex") - { - return 3; - } - - SLIC_ERROR(axom::fmt::format("Unsupported Blueprint cell shape '{}'.", shapeType)); - return -1; + SLIC_ASSERT(m_bp_state != nullptr); + return m_bp_state->meshDimension(); } bool Shaper::verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(std::string& whyBad) const @@ -344,19 +333,19 @@ bool Shaper::verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(std::string& w rval = conduit::blueprint::mesh::verify(m_bp_state->getBlueprintMeshNode(), info); if(rval) { - const std::string topoType = getBlueprintTopologyNode().fetch_existing("type").as_string(); + const std::string topoType = m_bp_state->topologyType(); rval = topoType == "unstructured" || topoType == "structured"; info[0].set_string("Topology is not structured or unstructured."); } if(rval) { - const std::string elemShape = getBlueprintCellShape(); + const std::string elemShape = m_bp_state->cellShape(); rval = (elemShape == "hex") || (elemShape == "quad"); info[0].set_string("Topology elements are not hex or quad."); } if(rval) { - const std::string coordsetType = getBlueprintCoordsetNode().fetch_existing("type").as_string(); + const std::string coordsetType = m_bp_state->getBlueprintCoordsetNode().fetch_existing("type").as_string(); rval = coordsetType == "explicit"; info[0].set_string("Coordset is not explicit."); } @@ -374,8 +363,7 @@ void Shaper::ensureBlueprintMeshIsUnstructured() } AXOM_ANNOTATE_SCOPE("Shaper::convertStructured"); - const conduit::Node& topoNode = getBlueprintTopologyNode(); - const std::string topoType = topoNode.fetch_existing("type").as_string(); + const std::string topoType = m_bp_state->topologyType(); if(topoType != "unstructured") { diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 5ee1a6b4ce..7e92a5d03d 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -131,7 +131,7 @@ void BlueprintState::refreshBlueprintMeshNode() int BlueprintState::meshDimension() const { - const std::string shapeType = shaping::getBlueprintCellShape(getBlueprintTopologyNode()); + const std::string shapeType = cellShape(); if(shapeType == "quad") { @@ -279,6 +279,20 @@ axom::ArrayView BlueprintState::createField(const std::string& name, return axom::bump::utilities::make_array_view(valuesNode); } +axom::ArrayView BlueprintState::getScalarFieldView(const std::string& name, + axom::IndexType size) +{ + conduit::Node& fieldNode = getField(name); + SLIC_ASSERT(fieldNode.fetch_existing("association").as_string() == std::string("element")); + SLIC_ASSERT(fieldNode.fetch_existing("topology").as_string() == topologyName()); + + conduit::Node& valuesNode = fieldNode.fetch_existing("values"); + SLIC_ASSERT(valuesNode.dtype().id() == conduit::DataType::float64(size).id()); + SLIC_ASSERT(valuesNode.dtype().number_of_elements() == size); + + return axom::ArrayView(valuesNode.as_double_ptr(), size); +} + #if defined(AXOM_USE_BUMP) void BlueprintState::importQuadraturePointMesh(const conduit::Node& quadratureMesh) { diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 7b88d54c66..a9607fd0c3 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -149,6 +149,30 @@ struct BlueprintState return getBlueprintMeshNode().fetch_existing("topologies").fetch_existing(topologyName()); } + /// Return the Blueprint topology type for the active topology. + std::string topologyType() const + { + return getBlueprintTopologyNode().fetch_existing("type").as_string(); + } + + /// Return the Blueprint cell shape for the active topology. + std::string cellShape() const { return shaping::getBlueprintCellShape(getBlueprintTopologyNode()); } + + /// Return the coordset name referenced by the active Blueprint topology. + std::string coordsetName() const + { + return getBlueprintTopologyNode().fetch_existing("coordset").as_string(); + } + + /// Return the coordset referenced by the active Blueprint topology. + conduit::Node& getBlueprintCoordsetNode() { return getBlueprintCoordsetNode(coordsetName()); } + + /// Return the coordset referenced by the active Blueprint topology. + const conduit::Node& getBlueprintCoordsetNode() const + { + return getBlueprintCoordsetNode(coordsetName()); + } + /// Return a named Blueprint coordset node. conduit::Node& getBlueprintCoordsetNode(const std::string& name) { @@ -197,6 +221,16 @@ struct BlueprintState return getBlueprintMeshNode().fetch_existing("fields").fetch_existing(name); } + /*! + * \brief Return a writable view over a scalar element-associated Blueprint field. + * + * \param name The field name. + * \param size Expected number of values. + * + * \return A writable view over the field values. + */ + axom::ArrayView getScalarFieldView(const std::string& name, axom::IndexType size); + /// Return a shape in/out field, if present. conduit::Node* getShapeFunction(const std::string& name) { From 495c8fb639a0987379d3aeda15ae1e8dc792a72a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 18:20:37 -0700 Subject: [PATCH 52/72] Use the field naming utility functions in the rest of the code/tests. --- src/axom/quest/IntersectionShaper.hpp | 4 ++-- src/axom/quest/SamplingShaper.hpp | 4 ++-- .../quest/detail/shaping/shaping_helpers.cpp | 20 +++++++++++++++++++ .../quest/detail/shaping/shaping_helpers.hpp | 5 +++++ .../shaping/shaping_helpers_blueprint.cpp | 12 +++++------ .../tests/quest_intersection_shaper_utils.hpp | 11 ++++++---- .../quest/tests/quest_sampling_shaper.cpp | 12 +++++++---- 7 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 60f6ea787c..d64988ca71 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -294,8 +294,8 @@ AXOM_HOST_DEVICE inline void TempArrayView::finalize() * Replacement rules for Blueprint meshes is not yet supported. * The following comments apply to replacement rules. * - * Volume fractions are represented in the input mesh as a GridFunction with a special prefix, - * currently "vol_frac_", followed by a material name. Volume fractions + * Volume fractions are represented in the input mesh as fields named by + * shaping::volumeFractionFieldName(), one per material. Volume fractions * can be present in the input data collection prior to shaping and the * IntersectionShaper will augment them when changes are needed such as when * a material overwrites them. If a new material is not yet represented in diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 9540585ae2..7a51e7c4db 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -775,8 +775,8 @@ class SamplingShaper : public Shaper /** * \brief Compute volume fractions for a given material using its associated quadrature function. - * - * The generated grid function will be registered in the data collection and prefixed by `vol_frac_` + * + * The generated field uses shaping::volumeFractionFieldName() for its name. * * \param [in] matField The name of the material */ diff --git a/src/axom/quest/detail/shaping/shaping_helpers.cpp b/src/axom/quest/detail/shaping/shaping_helpers.cpp index 4667c193ce..ea4301896f 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.cpp @@ -46,6 +46,26 @@ std::string shapeVolumeFractionFieldName(const std::string& shapeName) return axom::fmt::format("{}{}", SHAPE_VOLUME_FRACTION_PREFIX, shapeName); } +bool isShapeInOutFieldName(const std::string& fieldName) +{ + return axom::utilities::string::startsWith(fieldName, SHAPE_INOUT_PREFIX); +} + +bool isMaterialInOutFieldName(const std::string& fieldName) +{ + return !materialNameFromMaterialInOutFieldName(fieldName).empty(); +} + +bool isVolumeFractionFieldName(const std::string& fieldName) +{ + return !materialNameFromVolumeFractionFieldName(fieldName).empty(); +} + +bool isShapeVolumeFractionFieldName(const std::string& fieldName) +{ + return axom::utilities::string::startsWith(fieldName, SHAPE_VOLUME_FRACTION_PREFIX); +} + std::string materialNameFromMaterialInOutFieldName(const std::string& fieldName) { return extractSuffixedName(fieldName, MATERIAL_INOUT_PREFIX); diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index e181980adf..a2eb84b7c8 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -131,6 +131,11 @@ std::string materialInOutFieldName(const std::string& materialName); std::string volumeFractionFieldName(const std::string& materialName); std::string shapeVolumeFractionFieldName(const std::string& shapeName); +bool isShapeInOutFieldName(const std::string& fieldName); +bool isMaterialInOutFieldName(const std::string& fieldName); +bool isVolumeFractionFieldName(const std::string& fieldName); +bool isShapeVolumeFractionFieldName(const std::string& fieldName); + std::string materialNameFromMaterialInOutFieldName(const std::string& fieldName); std::string materialNameFromVolumeFractionFieldName(const std::string& fieldName); diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 7e92a5d03d..bf264f5c18 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -392,9 +392,9 @@ void printRegisteredFieldNames(const BlueprintState& bpState, std::vector names; for(const auto& name : bpState.fieldNames()) { - if(shaping::materialNameFromVolumeFractionFieldName(name).empty() && - shaping::materialNameFromMaterialInOutFieldName(name).empty() && - !axom::utilities::string::startsWith(name, "inout_")) + if(!shaping::isVolumeFractionFieldName(name) && + !shaping::isMaterialInOutFieldName(name) && + !shaping::isShapeInOutFieldName(name)) { names.push_back(name); } @@ -422,9 +422,9 @@ void printRegisteredFieldNames(const BlueprintState& bpState, axom::fmt::join(coordsetNames, ", "), axom::fmt::join(fieldNames, ", "), axom::fmt::join(knownMaterials, ", "), - axom::fmt::join(extractMatchingFields("inout_"), ", "), - axom::fmt::join(extractMatchingFields("mat_inout_"), ", "), - axom::fmt::join(extractMatchingFields("vol_frac_"), ", "), + axom::fmt::join(extractMatchingFields(shaping::shapeInOutFieldName("")), ", "), + axom::fmt::join(extractMatchingFields(shaping::materialInOutFieldName("")), ", "), + axom::fmt::join(extractMatchingFields(shaping::volumeFractionFieldName("")), ", "), axom::fmt::join(extractOtherFields(), ", ")); SLIC_INFO_ROOT(axom::fmt::to_string(out)); diff --git a/src/axom/quest/tests/quest_intersection_shaper_utils.hpp b/src/axom/quest/tests/quest_intersection_shaper_utils.hpp index 46d902bd55..61246e99f5 100644 --- a/src/axom/quest/tests/quest_intersection_shaper_utils.hpp +++ b/src/axom/quest/tests/quest_intersection_shaper_utils.hpp @@ -143,7 +143,8 @@ void saveVisIt(const std::string &path, const std::string &filename, sidre::MFEM vdc.SetFormat(mfem::DataCollection::SERIAL_FORMAT); for(auto it : dc.GetFieldMap()) { - if(it.first.find("vol_frac_") != std::string::npos) + if(quest::shaping::isVolumeFractionFieldName(it.first) || + quest::shaping::isShapeVolumeFractionFieldName(it.first)) { vdc.RegisterField(it.first, it.second); } @@ -161,7 +162,8 @@ void loadVisIt(mfem::VisItDataCollection &vdc, sidre::MFEMSidreDataCollection &d dc.SetMesh(vdc.GetMesh()); for(auto it : vdc.GetFieldMap()) { - if(it.first.find("vol_frac_") != std::string::npos) + if(quest::shaping::isVolumeFractionFieldName(it.first) || + quest::shaping::isShapeVolumeFractionFieldName(it.first)) { dc.RegisterField(it.first, it.second); } @@ -173,8 +175,9 @@ void dcToConduit(sidre::MFEMSidreDataCollection &dc, conduit::Node &n) { for(auto it : dc.GetFieldMap()) { - // Just compare vol_frac_ grid functions. - if(it.first.find("vol_frac_") != std::string::npos) + // Just compare material and per-shape volume-fraction grid functions. + if(quest::shaping::isVolumeFractionFieldName(it.first) || + quest::shaping::isShapeVolumeFractionFieldName(it.first)) { n[it.first].set(it.second->GetData(), it.second->Size()); } diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 6487820243..385422fd44 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2455,7 +2455,9 @@ TEST(SamplingShaperBlueprintTest, sidre_blueprint_quadrature_persists) quest::util::make_unstructured_blueprint_box_mesh_2d(meshGroup, bbox, res, "mesh", "coords"); constexpr axom::IndexType cellCount = 4; - auto* fieldGroup = meshGroup->createGroup("fields/vol_frac_background"); + const std::string backgroundVolFracName = quest::shaping::volumeFractionFieldName("background"); + const std::string backgroundMatInOutName = quest::shaping::materialInOutFieldName("background"); + auto* fieldGroup = meshGroup->createGroup(axom::fmt::format("fields/{}", backgroundVolFracName)); fieldGroup->createViewString("association", "element"); fieldGroup->createViewString("topology", "mesh"); auto* valuesView = @@ -2478,14 +2480,15 @@ TEST(SamplingShaperBlueprintTest, sidre_blueprint_quadrature_persists) ASSERT_NE(bpMeshNode, nullptr); std::map initialVolumeFractions; - initialVolumeFractions["background"] = &bpMeshNode->fetch_existing("fields/vol_frac_background"); + initialVolumeFractions["background"] = + &bpMeshNode->fetch_existing(axom::fmt::format("fields/{}", backgroundVolFracName)); shaper.importInitialVolumeFractions(initialVolumeFractions); EXPECT_TRUE(meshGroup->hasGroup("coordsets/quadrature_points")); EXPECT_TRUE(meshGroup->hasGroup("topologies/quadrature_points")); EXPECT_TRUE(meshGroup->hasGroup("fields/originalElements")); EXPECT_TRUE(meshGroup->hasGroup("fields/quadratureWeights")); - EXPECT_TRUE(meshGroup->hasGroup("fields/mat_inout_background")); + EXPECT_TRUE(meshGroup->hasGroup(axom::fmt::format("fields/{}", backgroundMatInOutName))); conduit::Node refreshedMesh; meshGroup->createNativeLayout(refreshedMesh); @@ -2493,7 +2496,8 @@ TEST(SamplingShaperBlueprintTest, sidre_blueprint_quadrature_persists) EXPECT_TRUE(refreshedMesh.has_path("topologies/quadrature_points")); EXPECT_TRUE(refreshedMesh.has_path("fields/originalElements/values")); EXPECT_TRUE(refreshedMesh.has_path("fields/quadratureWeights/values")); - EXPECT_TRUE(refreshedMesh.has_path("fields/mat_inout_background/values")); + EXPECT_TRUE( + refreshedMesh.has_path(axom::fmt::format("fields/{}/values", backgroundMatInOutName))); } #endif From 491886e7bbd9f2d9ffa47724c528d4d3975f2dff Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 18:51:18 -0700 Subject: [PATCH 53/72] Updated RELEASE-NOTES.md --- RELEASE-NOTES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index bdd9a1919c..f9f4e0ab13 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -36,7 +36,10 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ removed in a future version of Axom. - Core: Adds Durand-Kerner polynomial solver which returns the complex roots of a univariate polynomial - Core: Adds `axom::Array::pop_back` for API compatibility with `std::vector` -- Quest: Enhanced `SamplingShaper` so it can operate on Blueprint quad/hex meshes. +- Quest: Enhanced `SamplingShaper` so it can operate on Blueprint quad/hex meshes. Pass `inline_mesh_blueprint` to the + `quest_shaping_driver_ex` example program instead of `input_mesh` when a Blueprint mesh is desired. +- Quest: `quest_shaping_driver_ex` now lets `inline_mesh_blueprint` runs choose the Blueprint backing + store with `--backing sidre|conduit`, which enables the program to operate on either Sidre or Conduit meshes. - Primal: Adds KnotVector constructors that skip validity assertion checks, allowing the user to call `isValid()` and handle the error appropriately. - Inlet: Added the ability to have collections (array and dictionary) with variant values. From 8744560ab6cac199ae08564524af9d291162919b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 18:53:56 -0700 Subject: [PATCH 54/72] Added doxygen comments. --- src/axom/quest/Shaper.hpp | 5 ++ .../quest/detail/shaping/shaping_helpers.hpp | 77 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index 499f432583..a38e540e18 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -137,11 +137,16 @@ class Shaper #endif #if defined(AXOM_USE_CONDUIT) + /// \brief Return the active Blueprint state, if this shaper is using Blueprint input. shaping::BlueprintState* getBlueprintState() { return m_bp_state.get(); } + + /// \brief Return the active Blueprint mesh node, if this shaper is using Blueprint input. conduit::Node* getBlueprintMeshNode() { return m_bp_state != nullptr ? &m_bp_state->getBlueprintMeshNode() : nullptr; } + + /// \brief Return the active Blueprint mesh node, if this shaper is using Blueprint input. const conduit::Node* getBlueprintMeshNode() const { return m_bp_state != nullptr ? &m_bp_state->getBlueprintMeshNode() : nullptr; diff --git a/src/axom/quest/detail/shaping/shaping_helpers.hpp b/src/axom/quest/detail/shaping/shaping_helpers.hpp index a2eb84b7c8..0ac0871c6b 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers.hpp @@ -126,17 +126,94 @@ enum class VolFracSampling : int SAMPLE_AT_QPTS }; +/*! + * \brief Return the registered shape in/out field name for a shape. + * + * \param shapeName The shape name. + * + * \return The corresponding shape in/out field name. + */ std::string shapeInOutFieldName(const std::string& shapeName); + +/*! + * \brief Return the registered material in/out field name for a material. + * + * \param materialName The material name. + * + * \return The corresponding material in/out field name. + */ std::string materialInOutFieldName(const std::string& materialName); + +/*! + * \brief Return the registered volume-fraction field name for a material. + * + * \param materialName The material name. + * + * \return The corresponding volume-fraction field name. + */ std::string volumeFractionFieldName(const std::string& materialName); + +/*! + * \brief Return the per-shape volume-fraction field name for a shape. + * + * \param shapeName The shape name. + * + * \return The corresponding per-shape volume-fraction field name. + */ std::string shapeVolumeFractionFieldName(const std::string& shapeName); +/*! + * \brief Return whether a field name is a registered shape in/out field name. + * + * \param fieldName The field name to inspect. + * + * \return True if the field name belongs to the shape in/out family. + */ bool isShapeInOutFieldName(const std::string& fieldName); + +/*! + * \brief Return whether a field name is a registered material in/out field name. + * + * \param fieldName The field name to inspect. + * + * \return True if the field name belongs to the material in/out family. + */ bool isMaterialInOutFieldName(const std::string& fieldName); + +/*! + * \brief Return whether a field name is a registered material volume-fraction field name. + * + * \param fieldName The field name to inspect. + * + * \return True if the field name belongs to the material volume-fraction family. + */ bool isVolumeFractionFieldName(const std::string& fieldName); + +/*! + * \brief Return whether a field name is a registered per-shape volume-fraction field name. + * + * \param fieldName The field name to inspect. + * + * \return True if the field name belongs to the per-shape volume-fraction family. + */ bool isShapeVolumeFractionFieldName(const std::string& fieldName); +/*! + * \brief Extract the material name from a material in/out field name. + * + * \param fieldName The field name to inspect. + * + * \return The material name, or an empty string if the field name does not match. + */ std::string materialNameFromMaterialInOutFieldName(const std::string& fieldName); + +/*! + * \brief Extract the material name from a volume-fraction field name. + * + * \param fieldName The field name to inspect. + * + * \return The material name, or an empty string if the field name does not match. + */ std::string materialNameFromVolumeFractionFieldName(const std::string& fieldName); template From 4bab5b0374b0792e35affd7f7ff098fdb5b78993 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 19:11:17 -0700 Subject: [PATCH 55/72] Separated a blueprint test into its own file. Fix some macro guards. --- src/axom/quest/Shaper.cpp | 18 +-- src/axom/quest/Shaper.hpp | 12 +- src/axom/quest/tests/CMakeLists.txt | 19 ++++ .../quest/tests/quest_sampling_shaper.cpp | 59 ---------- .../tests/quest_sampling_shaper_blueprint.cpp | 104 ++++++++++++++++++ 5 files changed, 139 insertions(+), 73 deletions(-) create mode 100644 src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 1433a7aa06..70b1d1301e 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -13,16 +13,18 @@ #include "axom/quest/Shaper.hpp" #include "axom/quest/DiscreteShape.hpp" #include "axom/quest/util/mesh_helpers.hpp" -#include "conduit_blueprint_mesh.hpp" #include "axom/fmt.hpp" -#include "conduit/conduit_relay_io.hpp" -#ifdef CONDUIT_RELAY_IO_HDF5_ENABLED - #ifdef CONDUIT_RELAY_MPI_ENABLED - #include "conduit/conduit_relay_mpi_io_blueprint.hpp" - #else - #include "conduit/conduit_relay_io_blueprint.hpp" +#if defined(AXOM_USE_CONDUIT) + #include "conduit_blueprint_mesh.hpp" + #include "conduit/conduit_relay_io.hpp" + #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED + #ifdef CONDUIT_RELAY_MPI_ENABLED + #include "conduit/conduit_relay_mpi_io_blueprint.hpp" + #else + #include "conduit/conduit_relay_io_blueprint.hpp" + #endif #endif #endif @@ -398,7 +400,7 @@ bool Shaper::verifyMFEMInputMesh(std::string& whyBad) const void Shaper::saveResults(bool AXOM_UNUSED_PARAM(extra)) { -#ifdef MFEM_USE_MPI +#if defined(AXOM_USE_MFEM) // If the target mesh was MFEM, save it. if(getDC() != nullptr) { diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index a38e540e18..36af150ba0 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -18,6 +18,10 @@ #error Shaping functionality requires Axom to be configured with the Klee component #endif +#ifndef AXOM_USE_SIDRE + #error Shaping functionality requires Axom to be configured with the Sidre component +#endif + #if !defined(AXOM_USE_MFEM) && !defined(AXOM_USE_CONDUIT) #error Shaping functionality requires Axom to be configured with Conduit or MFEM #endif @@ -45,7 +49,8 @@ namespace quest /** * Abstract base class for shaping material volume fractions * - * Shaper requires Axom to be configured with Conduit or MFEM or both. + * Shaper requires Axom to be configured with Sidre and with Conduit or MFEM + * or both. */ class Shaper { @@ -74,11 +79,6 @@ class Shaper /*! * @brief Construct Shaper to operate on a blueprint-formatted mesh * stored in a conduit Node. - * - * Because \c conduit::Node doesn't support application-specified - * allocator id for (only) arrays, the incoming \c bpNode must have - * all arrays pre-allocated in a space accessible by the runtime - * policy. Any needed-but-missing space would lead to an exception. */ Shaper(RuntimePolicy execPolicy, int allocatorId, diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index 66b3d792be..74f021664b 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -128,6 +128,25 @@ if(MFEM_FOUND) endif() endif() +#------------------------------------------------------------------------------ +# Blueprint SamplingShaper tests require Conduit, Bump, Sidre, and Klee +#------------------------------------------------------------------------------ +if(CONDUIT_FOUND AND AXOM_ENABLE_BUMP AND AXOM_ENABLE_SIDRE AND AXOM_ENABLE_KLEE) + axom_add_executable( + NAME quest_sampling_shaper_blueprint_test + SOURCES quest_sampling_shaper_blueprint.cpp + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + DEPENDS_ON ${quest_tests_depends} conduit::conduit + FOLDER axom/quest/tests + ) + + axom_add_test( + NAME quest_sampling_shaper_blueprint + COMMAND quest_sampling_shaper_blueprint_test + NUM_MPI_TASKS 1 + ) +endif() + #------------------------------------------------------------------------------ # Tests that use MPI when available diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index 385422fd44..e8048371d6 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2444,65 +2444,6 @@ piece = line(end=start) //----------------------------------------------------------------------------- -#if defined(AXOM_USE_CONDUIT) && defined(AXOM_USE_BUMP) -TEST(SamplingShaperBlueprintTest, sidre_blueprint_quadrature_persists) -{ - sidre::DataStore dataStore; - auto* meshGroup = dataStore.getRoot()->createGroup("mesh"); - - const primal::BoundingBox bbox {{0., 0.}, {1., 1.}}; - const axom::NumericArray res {{2, 2}}; - quest::util::make_unstructured_blueprint_box_mesh_2d(meshGroup, bbox, res, "mesh", "coords"); - - constexpr axom::IndexType cellCount = 4; - const std::string backgroundVolFracName = quest::shaping::volumeFractionFieldName("background"); - const std::string backgroundMatInOutName = quest::shaping::materialInOutFieldName("background"); - auto* fieldGroup = meshGroup->createGroup(axom::fmt::format("fields/{}", backgroundVolFracName)); - fieldGroup->createViewString("association", "element"); - fieldGroup->createViewString("topology", "mesh"); - auto* valuesView = - fieldGroup->createViewAndAllocate("values", axom::sidre::DataTypeId::FLOAT64_ID, cellCount); - auto* values = static_cast(valuesView->getVoidPtr()); - for(axom::IndexType i = 0; i < cellCount; ++i) - { - values[i] = 1.; - } - - klee::ShapeSet shapeSet; - quest::SamplingShaper shaper(axom::runtime_policy::Policy::seq, - axom::policyToDefaultAllocatorID(axom::runtime_policy::Policy::seq), - shapeSet, - meshGroup, - "mesh"); - shaper.setSamplingResolution(2); - - auto* bpMeshNode = shaper.getBlueprintMeshNode(); - ASSERT_NE(bpMeshNode, nullptr); - - std::map initialVolumeFractions; - initialVolumeFractions["background"] = - &bpMeshNode->fetch_existing(axom::fmt::format("fields/{}", backgroundVolFracName)); - shaper.importInitialVolumeFractions(initialVolumeFractions); - - EXPECT_TRUE(meshGroup->hasGroup("coordsets/quadrature_points")); - EXPECT_TRUE(meshGroup->hasGroup("topologies/quadrature_points")); - EXPECT_TRUE(meshGroup->hasGroup("fields/originalElements")); - EXPECT_TRUE(meshGroup->hasGroup("fields/quadratureWeights")); - EXPECT_TRUE(meshGroup->hasGroup(axom::fmt::format("fields/{}", backgroundMatInOutName))); - - conduit::Node refreshedMesh; - meshGroup->createNativeLayout(refreshedMesh); - EXPECT_TRUE(refreshedMesh.has_path("coordsets/quadrature_points")); - EXPECT_TRUE(refreshedMesh.has_path("topologies/quadrature_points")); - EXPECT_TRUE(refreshedMesh.has_path("fields/originalElements/values")); - EXPECT_TRUE(refreshedMesh.has_path("fields/quadratureWeights/values")); - EXPECT_TRUE( - refreshedMesh.has_path(axom::fmt::format("fields/{}/values", backgroundMatInOutName))); -} -#endif - -//----------------------------------------------------------------------------- - TEST_F(SampleTester2D, invalid_quadrature_type_values_abort) { const std::string shape_template = R"( diff --git a/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp b/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp new file mode 100644 index 0000000000..8051048883 --- /dev/null +++ b/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp @@ -0,0 +1,104 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/*! + * \brief Blueprint-focused unit tests for quest's SamplingShaper class. + */ + +#include "gtest/gtest.h" + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/klee.hpp" +#include "axom/primal.hpp" +#include "axom/quest.hpp" +#include "axom/quest/SamplingShaper.hpp" +#include "axom/quest/util/mesh_helpers.hpp" +#include "axom/sidre.hpp" +#include "axom/slic.hpp" + +#if !defined(AXOM_USE_CONDUIT) || !defined(AXOM_USE_BUMP) || !defined(AXOM_USE_SIDRE) + #error "Quest's Blueprint SamplingShaper tests require Conduit, Bump, and Sidre." +#endif + +#ifdef AXOM_USE_MPI + #include +#endif + +#include +#include + +namespace klee = axom::klee; +namespace primal = axom::primal; +namespace quest = axom::quest; +namespace sidre = axom::sidre; +namespace slic = axom::slic; + +TEST(SamplingShaperBlueprintTest, sidre_blueprint_quadrature_persists) +{ + sidre::DataStore dataStore; + auto* meshGroup = dataStore.getRoot()->createGroup("mesh"); + + const primal::BoundingBox bbox {{0., 0.}, {1., 1.}}; + const axom::NumericArray res {{2, 2}}; + quest::util::make_unstructured_blueprint_box_mesh_2d(meshGroup, bbox, res, "mesh", "coords"); + + constexpr axom::IndexType cellCount = 4; + const std::string backgroundVolFracName = quest::shaping::volumeFractionFieldName("background"); + const std::string backgroundMatInOutName = quest::shaping::materialInOutFieldName("background"); + auto* fieldGroup = meshGroup->createGroup(axom::fmt::format("fields/{}", backgroundVolFracName)); + fieldGroup->createViewString("association", "element"); + fieldGroup->createViewString("topology", "mesh"); + auto* valuesView = + fieldGroup->createViewAndAllocate("values", axom::sidre::DataTypeId::FLOAT64_ID, cellCount); + auto* values = static_cast(valuesView->getVoidPtr()); + for(axom::IndexType i = 0; i < cellCount; ++i) + { + values[i] = 1.; + } + + klee::ShapeSet shapeSet; + quest::SamplingShaper shaper(axom::runtime_policy::Policy::seq, + axom::policyToDefaultAllocatorID(axom::runtime_policy::Policy::seq), + shapeSet, + meshGroup, + "mesh"); + shaper.setSamplingResolution(2); + + auto* bpMeshNode = shaper.getBlueprintMeshNode(); + ASSERT_NE(bpMeshNode, nullptr); + + std::map initialVolumeFractions; + initialVolumeFractions["background"] = + &bpMeshNode->fetch_existing(axom::fmt::format("fields/{}", backgroundVolFracName)); + shaper.importInitialVolumeFractions(initialVolumeFractions); + + EXPECT_TRUE(meshGroup->hasGroup("coordsets/quadrature_points")); + EXPECT_TRUE(meshGroup->hasGroup("topologies/quadrature_points")); + EXPECT_TRUE(meshGroup->hasGroup("fields/originalElements")); + EXPECT_TRUE(meshGroup->hasGroup("fields/quadratureWeights")); + EXPECT_TRUE(meshGroup->hasGroup(axom::fmt::format("fields/{}", backgroundMatInOutName))); + + conduit::Node refreshedMesh; + meshGroup->createNativeLayout(refreshedMesh); + EXPECT_TRUE(refreshedMesh.has_path("coordsets/quadrature_points")); + EXPECT_TRUE(refreshedMesh.has_path("topologies/quadrature_points")); + EXPECT_TRUE(refreshedMesh.has_path("fields/originalElements/values")); + EXPECT_TRUE(refreshedMesh.has_path("fields/quadratureWeights/values")); + EXPECT_TRUE( + refreshedMesh.has_path(axom::fmt::format("fields/{}/values", backgroundMatInOutName))); +} + +int main(int argc, char* argv[]) +{ + axom::utilities::raii::MPIWrapper mpi_raii_wrapper(argc, argv); + + ::testing::InitGoogleTest(&argc, argv); + slic::SimpleLogger logger(slic::message::Info); + + const int result = RUN_ALL_TESTS(); + return result; +} From cb7efc1e0eb4e042a7cf6d8705d70d30e98d2716 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 15 Jun 2026 19:12:35 -0700 Subject: [PATCH 56/72] make style --- src/axom/bump/MakeExplicitCoordset.hpp | 23 +++++----- src/axom/quest/IntersectionShaper.hpp | 6 ++- src/axom/quest/Shaper.cpp | 3 +- .../shaping/shaping_helpers_blueprint.cpp | 42 +++++++++---------- .../shaping/shaping_helpers_blueprint.hpp | 5 ++- src/axom/quest/util/mesh_helpers.cpp | 38 +++++++++-------- src/axom/quest/util/mesh_helpers.hpp | 4 +- 7 files changed, 65 insertions(+), 56 deletions(-) diff --git a/src/axom/bump/MakeExplicitCoordset.hpp b/src/axom/bump/MakeExplicitCoordset.hpp index c99feb7f22..afa5725035 100644 --- a/src/axom/bump/MakeExplicitCoordset.hpp +++ b/src/axom/bump/MakeExplicitCoordset.hpp @@ -43,15 +43,13 @@ class MakeExplicitCoordset conduit::Node n_dest_coordset; if(cstype == "uniform") { - axom::bump::views::dispatch_uniform_coordset(n_coordset, [&](auto coordsetView) - { + axom::bump::views::dispatch_uniform_coordset(n_coordset, [&](auto coordsetView) { convert(coordsetView, n_dest_coordset, allocator_id); }); } else if(cstype == "rectilinear") { - axom::bump::views::dispatch_rectilinear_coordset(n_coordset, [&](auto coordsetView) - { + axom::bump::views::dispatch_rectilinear_coordset(n_coordset, [&](auto coordsetView) { convert(coordsetView, n_dest_coordset, allocator_id); }); } @@ -98,14 +96,15 @@ class MakeExplicitCoordset } // Copy data from the view into the new coordinate array views. - axom::for_all(coordsetView.size(), AXOM_LAMBDA(axom::IndexType i) - { - const auto pt = coordsetView[i]; - for(int c = 0; c < coordsetView.dimension(); c++) - { - comps[c][i] = pt[c]; - } - }); + axom::for_all( + coordsetView.size(), + AXOM_LAMBDA(axom::IndexType i) { + const auto pt = coordsetView[i]; + for(int c = 0; c < coordsetView.dimension(); c++) + { + comps[c][i] = pt[c]; + } + }); } }; diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index d64988ca71..38a4102306 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -2704,7 +2704,8 @@ class IntersectionShaper : public Shaper const conduit::Node& topoNode = m_bp_state->getBlueprintTopologyNode(); // Assume unstructured and hexahedral - SLIC_ERROR_IF(m_bp_state->topologyType() != "unstructured", "topology type must be 'unstructured'"); + SLIC_ERROR_IF(m_bp_state->topologyType() != "unstructured", + "topology type must be 'unstructured'"); SLIC_ERROR_IF(m_bp_state->cellShape() != "quad", "element shape must be 'quad'"); const auto& connNode = topoNode["elements/connectivity"]; @@ -2774,7 +2775,8 @@ class IntersectionShaper : public Shaper const conduit::Node& topoNode = m_bp_state->getBlueprintTopologyNode(); // Assume unstructured and hexahedral - SLIC_ERROR_IF(m_bp_state->topologyType() != "unstructured", "topology type must be 'unstructured'"); + SLIC_ERROR_IF(m_bp_state->topologyType() != "unstructured", + "topology type must be 'unstructured'"); SLIC_ERROR_IF(m_bp_state->cellShape() != "hex", "element shape must be 'hex'"); const auto& connNode = topoNode["elements/connectivity"]; diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 70b1d1301e..0fd5153fda 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -347,7 +347,8 @@ bool Shaper::verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(std::string& w } if(rval) { - const std::string coordsetType = m_bp_state->getBlueprintCoordsetNode().fetch_existing("type").as_string(); + const std::string coordsetType = + m_bp_state->getBlueprintCoordsetNode().fetch_existing("type").as_string(); rval = coordsetType == "explicit"; info[0].set_string("Coordset is not explicit."); } diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index bf264f5c18..0947cdb69c 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -392,8 +392,7 @@ void printRegisteredFieldNames(const BlueprintState& bpState, std::vector names; for(const auto& name : bpState.fieldNames()) { - if(!shaping::isVolumeFractionFieldName(name) && - !shaping::isMaterialInOutFieldName(name) && + if(!shaping::isVolumeFractionFieldName(name) && !shaping::isMaterialInOutFieldName(name) && !shaping::isShapeInOutFieldName(name)) { names.push_back(name); @@ -407,25 +406,26 @@ void printRegisteredFieldNames(const BlueprintState& bpState, const std::vector fieldNames = bpState.fieldNames(); axom::fmt::memory_buffer out; - axom::fmt::format_to(std::back_inserter(out), - "List of registered fields in the SamplingShaper {}" - "\n\t* Blueprint topologies: {}" - "\n\t* Blueprint coordsets: {}" - "\n\t* Blueprint fields: {}" - "\n\t* Known materials: {}" - "\n\t* Shape inout fields: {}" - "\n\t* Mat inout fields: {}" - "\n\t* Volume fraction fields: {}" - "\n\t* Other Blueprint fields: {}", - initialMessage, - axom::fmt::join(topologyNames, ", "), - axom::fmt::join(coordsetNames, ", "), - axom::fmt::join(fieldNames, ", "), - axom::fmt::join(knownMaterials, ", "), - axom::fmt::join(extractMatchingFields(shaping::shapeInOutFieldName("")), ", "), - axom::fmt::join(extractMatchingFields(shaping::materialInOutFieldName("")), ", "), - axom::fmt::join(extractMatchingFields(shaping::volumeFractionFieldName("")), ", "), - axom::fmt::join(extractOtherFields(), ", ")); + axom::fmt::format_to( + std::back_inserter(out), + "List of registered fields in the SamplingShaper {}" + "\n\t* Blueprint topologies: {}" + "\n\t* Blueprint coordsets: {}" + "\n\t* Blueprint fields: {}" + "\n\t* Known materials: {}" + "\n\t* Shape inout fields: {}" + "\n\t* Mat inout fields: {}" + "\n\t* Volume fraction fields: {}" + "\n\t* Other Blueprint fields: {}", + initialMessage, + axom::fmt::join(topologyNames, ", "), + axom::fmt::join(coordsetNames, ", "), + axom::fmt::join(fieldNames, ", "), + axom::fmt::join(knownMaterials, ", "), + axom::fmt::join(extractMatchingFields(shaping::shapeInOutFieldName("")), ", "), + axom::fmt::join(extractMatchingFields(shaping::materialInOutFieldName("")), ", "), + axom::fmt::join(extractMatchingFields(shaping::volumeFractionFieldName("")), ", "), + axom::fmt::join(extractOtherFields(), ", ")); SLIC_INFO_ROOT(axom::fmt::to_string(out)); } diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index a9607fd0c3..7e7d87fec7 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -156,7 +156,10 @@ struct BlueprintState } /// Return the Blueprint cell shape for the active topology. - std::string cellShape() const { return shaping::getBlueprintCellShape(getBlueprintTopologyNode()); } + std::string cellShape() const + { + return shaping::getBlueprintCellShape(getBlueprintTopologyNode()); + } /// Return the coordset name referenced by the active Blueprint topology. std::string coordsetName() const diff --git a/src/axom/quest/util/mesh_helpers.cpp b/src/axom/quest/util/mesh_helpers.cpp index 2fdd90776f..73af4bbe4d 100644 --- a/src/axom/quest/util/mesh_helpers.cpp +++ b/src/axom/quest/util/mesh_helpers.cpp @@ -753,10 +753,12 @@ void fill_cartesian_coords_2d_impl(const primal::BoundingBox& domainB } #if defined(AXOM_USE_CONDUIT) -#if defined(AXOM_USE_BUMP) + #if defined(AXOM_USE_BUMP) /// Convert a Blueprint topology and coordset stored as conduit::Node to unstructured+explicit template -void convert_to_unstructured_impl(conduit::Node &n_topo, conduit::Node &n_coordset, const std::string &topologyName) +void convert_to_unstructured_impl(conduit::Node& n_topo, + conduit::Node& n_coordset, + const std::string& topologyName) { // Make sure the coordset is explicit, or do nothing if it is already explicit. axom::bump::MakeExplicitCoordset::execute(n_coordset); @@ -768,46 +770,48 @@ void convert_to_unstructured_impl(conduit::Node &n_topo, conduit::Node &n_coords axom::bump::MakeUnstructured::execute(n_topo, n_coordset, topologyName, newMesh); // Swap topology definitions so we keep the converted one. - conduit::Node &n_new_topo = newMesh.fetch_existing("topologies/" + topologyName); + conduit::Node& n_new_topo = newMesh.fetch_existing("topologies/" + topologyName); n_topo.swap(n_new_topo); } } -#endif + #endif -void convert_blueprint_structured_explicit_to_unstructured(conduit::Node &n_mesh, const std::string &topologyName, - axom::runtime_policy::Policy runtimePolicy) +void convert_blueprint_structured_explicit_to_unstructured(conduit::Node& n_mesh, + const std::string& topologyName, + axom::runtime_policy::Policy runtimePolicy) { -#if defined(AXOM_USE_BUMP) + #if defined(AXOM_USE_BUMP) SLIC_ERROR_IF(!n_mesh.has_path("topologies/" + topologyName), "Cannot find topology"); - conduit::Node &n_topo = n_mesh.fetch_existing("topologies/" + topologyName); - conduit::Node *n_coordset = const_cast(conduit::blueprint::mesh::utils::find_reference_node(n_topo, "coordset")); + conduit::Node& n_topo = n_mesh.fetch_existing("topologies/" + topologyName); + conduit::Node* n_coordset = const_cast( + conduit::blueprint::mesh::utils::find_reference_node(n_topo, "coordset")); SLIC_ERROR_IF(n_coordset == nullptr, "Cannot find coordset"); if(runtimePolicy == axom::runtime_policy::Policy::seq) { convert_to_unstructured_impl(n_topo, *n_coordset, topologyName); } -#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + #if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) if(runtimePolicy == axom::runtime_policy::Policy::omp) { convert_to_unstructured_impl(n_topo, *n_coordset, topologyName); } -#endif -#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + #endif + #if defined(AXOM_RUNTIME_POLICY_USE_CUDA) if(runtimePolicy == axom::runtime_policy::Policy::cuda) { convert_to_unstructured_impl>(n_topo, *n_coordset, topologyName); } -#endif -#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + #endif + #if defined(AXOM_RUNTIME_POLICY_USE_HIP) if(runtimePolicy == axom::runtime_policy::Policy::hip) { convert_to_unstructured_impl>(n_topo, *n_coordset, topologyName); } -#endif -#else + #endif + #else SLIC_ERROR("convert_blueprint_structured_explicit_to_unstructured requires Bump."); -#endif + #endif } #endif diff --git a/src/axom/quest/util/mesh_helpers.hpp b/src/axom/quest/util/mesh_helpers.hpp index 428a1af1c0..e4a6ca82bc 100644 --- a/src/axom/quest/util/mesh_helpers.hpp +++ b/src/axom/quest/util/mesh_helpers.hpp @@ -197,8 +197,8 @@ bool verifyBlueprintMesh(const axom::sidre::Group* meshGrp, conduit::Node info); * convert_blueprint_structured_explicit_to_unstructured_3d but it operates on conduit::Node * through axom::bump. */ -void convert_blueprint_structured_explicit_to_unstructured(conduit::Node &n_mesh, - const std::string &topologyName, +void convert_blueprint_structured_explicit_to_unstructured(conduit::Node& n_mesh, + const std::string& topologyName, axom::runtime_policy::Policy runtimePolicy); #endif From 058acbebb637486c35959837d86f326dd2eb365a Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 09:47:09 -0700 Subject: [PATCH 57/72] Compilation and warnings --- src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp | 2 +- src/axom/quest/util/mesh_helpers.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index 0947cdb69c..a36338ee8f 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -276,7 +276,7 @@ axom::ArrayView BlueprintState::createField(const std::string& name, conduit::Node& valuesNode = fieldNode["values"]; valuesNode.set_allocator(conduitAllocatorId); valuesNode.set(conduit::DataType::float64(size)); - return axom::bump::utilities::make_array_view(valuesNode); + return axom::ArrayView(valuesNode.as_double_ptr(), valuesNode.dtype().number_of_elements()); } axom::ArrayView BlueprintState::getScalarFieldView(const std::string& name, diff --git a/src/axom/quest/util/mesh_helpers.cpp b/src/axom/quest/util/mesh_helpers.cpp index 73af4bbe4d..9064241e7f 100644 --- a/src/axom/quest/util/mesh_helpers.cpp +++ b/src/axom/quest/util/mesh_helpers.cpp @@ -810,6 +810,9 @@ void convert_blueprint_structured_explicit_to_unstructured(conduit::Node& n_mesh } #endif #else + AXOM_UNUSED_VAR(n_mesh); + AXOM_UNUSED_VAR(topologyName); + AXOM_UNUSED_VAR(runtimePolicy); SLIC_ERROR("convert_blueprint_structured_explicit_to_unstructured requires Bump."); #endif } From cbf2d06a093d8769151441987d7cb1d36b884276 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 09:55:18 -0700 Subject: [PATCH 58/72] Revert a CI change. --- .github/workflows/ci-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index d49e765d66..0371952443 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -105,7 +105,7 @@ jobs: echo "compiler_image ${{ matrix.config.compiler_image }}" echo "host_config ${{ matrix.config.host_config }}" - name: Build and Test ${{ matrix.build_type }} - ${{ matrix.config.job_name }} - timeout-minutes: 100 + timeout-minutes: 80 run: | DO_BUILD=${{ matrix.config.do_build }} \ DO_BENCHMARKS=${{ matrix.config.do_benchmarks }} \ From f423427742517867b1ce8fda1d64a83183ddfad6 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 10:24:06 -0700 Subject: [PATCH 59/72] make style --- src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp b/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp index 8051048883..246379ae38 100644 --- a/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp @@ -88,8 +88,7 @@ TEST(SamplingShaperBlueprintTest, sidre_blueprint_quadrature_persists) EXPECT_TRUE(refreshedMesh.has_path("topologies/quadrature_points")); EXPECT_TRUE(refreshedMesh.has_path("fields/originalElements/values")); EXPECT_TRUE(refreshedMesh.has_path("fields/quadratureWeights/values")); - EXPECT_TRUE( - refreshedMesh.has_path(axom::fmt::format("fields/{}/values", backgroundMatInOutName))); + EXPECT_TRUE(refreshedMesh.has_path(axom::fmt::format("fields/{}/values", backgroundMatInOutName))); } int main(int argc, char* argv[]) From fe659474eca6583ff4956e1ea84264076c97ea63 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 10:56:48 -0700 Subject: [PATCH 60/72] Removed Shaper::getBlueprintMeshNode. --- src/axom/quest/SamplingShaper.cpp | 8 ++++++++ src/axom/quest/SamplingShaper.hpp | 11 ----------- src/axom/quest/Shaper.hpp | 12 ------------ .../quest/tests/quest_sampling_shaper_blueprint.cpp | 7 +++---- 4 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 8e8cffe4a9..933ed963fb 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -7,6 +7,14 @@ #include "axom/quest/detail/shaping/shaping_helpers.hpp" #if defined(AXOM_USE_CONDUIT) #include "axom/quest/detail/shaping/shaping_helpers_blueprint.hpp" + #include "conduit/conduit_relay_io.hpp" + #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED + #ifdef CONDUIT_RELAY_MPI_ENABLED + #include "conduit/conduit_relay_mpi_io_blueprint.hpp" + #else + #include "conduit/conduit_relay_io_blueprint.hpp" + #endif + #endif #endif namespace axom diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 7a51e7c4db..4dd67cb263 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -38,17 +38,6 @@ #include "mfem/linalg/dtensor.hpp" #endif -#if defined(AXOM_USE_CONDUIT) - #include "conduit/conduit_relay_io.hpp" - #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED - #ifdef CONDUIT_RELAY_MPI_ENABLED - #include "conduit/conduit_relay_mpi_io_blueprint.hpp" - #else - #include "conduit/conduit_relay_io_blueprint.hpp" - #endif - #endif -#endif - #include "axom/fmt.hpp" #include diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index 36af150ba0..d173cc43c1 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -139,18 +139,6 @@ class Shaper #if defined(AXOM_USE_CONDUIT) /// \brief Return the active Blueprint state, if this shaper is using Blueprint input. shaping::BlueprintState* getBlueprintState() { return m_bp_state.get(); } - - /// \brief Return the active Blueprint mesh node, if this shaper is using Blueprint input. - conduit::Node* getBlueprintMeshNode() - { - return m_bp_state != nullptr ? &m_bp_state->getBlueprintMeshNode() : nullptr; - } - - /// \brief Return the active Blueprint mesh node, if this shaper is using Blueprint input. - const conduit::Node* getBlueprintMeshNode() const - { - return m_bp_state != nullptr ? &m_bp_state->getBlueprintMeshNode() : nullptr; - } #endif /*! diff --git a/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp b/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp index 246379ae38..aa804b2947 100644 --- a/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp @@ -68,12 +68,11 @@ TEST(SamplingShaperBlueprintTest, sidre_blueprint_quadrature_persists) "mesh"); shaper.setSamplingResolution(2); - auto* bpMeshNode = shaper.getBlueprintMeshNode(); - ASSERT_NE(bpMeshNode, nullptr); + auto* bpState = shaper.getBlueprintState(); + ASSERT_NE(bpState, nullptr); std::map initialVolumeFractions; - initialVolumeFractions["background"] = - &bpMeshNode->fetch_existing(axom::fmt::format("fields/{}", backgroundVolFracName)); + initialVolumeFractions["background"] = &bpState->getField(backgroundVolFracName); shaper.importInitialVolumeFractions(initialVolumeFractions); EXPECT_TRUE(meshGroup->hasGroup("coordsets/quadrature_points")); From c87cb42199613b938c28189abbfdb1c661fd20f4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 11:28:54 -0700 Subject: [PATCH 61/72] Simplify MFEMState in shapers. --- src/axom/quest/SamplingShaper.cpp | 8 +-- src/axom/quest/SamplingShaper.hpp | 50 +++---------------- src/axom/quest/Shaper.cpp | 2 +- src/axom/quest/Shaper.hpp | 6 --- .../quest/detail/shaping/InOutSampler.hpp | 8 +-- .../quest/detail/shaping/PrimitiveSampler.hpp | 6 +-- .../detail/shaping/WindingNumberSampler.hpp | 10 ++-- .../detail/shaping/shaping_helpers_mfem.cpp | 8 +-- .../detail/shaping/shaping_helpers_mfem.hpp | 30 +++++------ .../quest/tests/quest_sampling_shaper.cpp | 2 +- 10 files changed, 42 insertions(+), 88 deletions(-) diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 933ed963fb..fd38c46cbc 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -141,7 +141,7 @@ void SamplingShaper::saveQuadraturePoints(const std::string& filename) const #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - auto* positions = samplingMFEMState().shapeQFuncs().Get("positions"); + auto* positions = m_mfem_state->shapeQFuncs().Get("positions"); if(positions == nullptr) { SLIC_WARNING("No MFEM quadrature positions are available to save."); @@ -376,7 +376,7 @@ void SamplingShaper::importInitialVolumeFractions( SLIC_ERROR_IF(m_mfem_state == nullptr, "This method requires MFEM inputs."); - auto& mfemState = samplingMFEMState(); + auto& mfemState = *m_mfem_state; auto* mesh = mfemState.m_dc->GetMesh(); // Generate the quadrature points. if(m_vfSampling == shaping::VolFracSampling::SAMPLE_AT_QPTS) @@ -394,7 +394,7 @@ void SamplingShaper::printRegisteredFieldNames(const std::string& initialMessage #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - shaping::printRegisteredFieldNames(samplingMFEMState(), + shaping::printRegisteredFieldNames(*m_mfem_state, m_knownMaterials, m_vfSampling, initialMessage); @@ -428,7 +428,7 @@ void SamplingShaper::computeVolumeFractionsForMaterial(const std::string& matFie // NOTE: We pass the m_samplingResolution and m_quadratureType values to this // version of the function so we can detect whether we have anisotropic // sampling, which is handled differently. - shaping::computeVolumeFractionsForMaterial(samplingMFEMState(), + shaping::computeVolumeFractionsForMaterial(*m_mfem_state, matField, m_volfracOrder, m_samplingResolution, diff --git a/src/axom/quest/SamplingShaper.hpp b/src/axom/quest/SamplingShaper.hpp index 4dd67cb263..3fc5417d33 100644 --- a/src/axom/quest/SamplingShaper.hpp +++ b/src/axom/quest/SamplingShaper.hpp @@ -140,7 +140,6 @@ class SamplingShaper : public Shaper sidre::MFEMSidreDataCollection* dc) : Shaper(execPolicy, allocatorId, shapeSet, dc) { - initializeSamplingMFEMState(); initializeSamplingResolution(); } #endif @@ -254,12 +253,14 @@ class SamplingShaper : public Shaper /// Returns a pointer to the quadrature function associated with shape \a name if it exists, else nullptr mfem::QuadratureFunction* getShapeQFunction(const std::string& name) const { - return samplingMFEMState().shapeQFuncs().Get(name); + SLIC_ASSERT(m_mfem_state != nullptr); + return m_mfem_state->shapeQFuncs().Get(name); } /// Returns a pointer to the quadrature function associated with material \a name if it exists, else nullptr mfem::QuadratureFunction* getMaterialQFunction(const std::string& name) const { - return samplingMFEMState().materialQFuncs().Get(name); + SLIC_ASSERT(m_mfem_state != nullptr); + return m_mfem_state->materialQFuncs().Get(name); } #endif protected: @@ -295,41 +296,6 @@ class SamplingShaper : public Shaper */ void saveQuadraturePoints(const std::string& filename) const; -#if defined(AXOM_USE_MFEM) - /// Create the internal MFEM state. This is called by the Shaper::Shaper MFEM constructor. - std::unique_ptr createMFEMState() override - { - return std::make_unique(); - } - - /// Finish initializing the MFEM state. - void initializeSamplingMFEMState() - { - // Shaper constructs its MFEM state in the base constructor, so upgrade it - // here rather than relying on virtual dispatch during base construction. - auto samplingState = std::make_unique(); - if(m_mfem_state != nullptr) - { - samplingState->m_dc = m_mfem_state->m_dc; - } - m_mfem_state = std::move(samplingState); - } - - /// Get a reference to the MFEM state as a SamplingMFEMState. - shaping::SamplingMFEMState& samplingMFEMState() - { - SLIC_ASSERT(m_mfem_state != nullptr); - return static_cast(*m_mfem_state); - } - - /// Get a reference to the MFEM state as a SamplingMFEMState. - const shaping::SamplingMFEMState& samplingMFEMState() const - { - SLIC_ASSERT(m_mfem_state != nullptr); - return static_cast(*m_mfem_state); - } -#endif - bool hasValidSampler() const { return !std::holds_alternative(m_sampler); } klee::Dimensions getShapeDimension() const @@ -417,7 +383,7 @@ class SamplingShaper : public Shaper #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - applyReplacementRulesImpl(samplingMFEMState(), shape); + applyReplacementRulesImpl(*m_mfem_state, shape); return; } #endif @@ -575,7 +541,7 @@ class SamplingShaper : public Shaper #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - runShapeQueryImplSampler(samplingMFEMState(), sampler); + runShapeQueryImplSampler(*m_mfem_state, sampler); return; } #endif @@ -596,7 +562,7 @@ class SamplingShaper : public Shaper #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - runShapeQueryImplSampler(samplingMFEMState(), sampler); + runShapeQueryImplSampler(*m_mfem_state, sampler); return; } #endif @@ -651,7 +617,7 @@ class SamplingShaper : public Shaper #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - runImpl(samplingMFEMState()); + runImpl(*m_mfem_state); return; } #endif diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 0fd5153fda..7b1d738acf 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -73,7 +73,7 @@ Shaper::Shaper(RuntimePolicy execPolicy, , m_bp_state() #endif { - m_mfem_state = createMFEMState(); + m_mfem_state = std::make_unique(); m_mfem_state->m_dc = dc; #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index d173cc43c1..ff6f79cbb1 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -326,12 +326,6 @@ class Shaper bool verifyMFEMInputMesh(std::string& whyBad) const; #endif -#if defined(AXOM_USE_MFEM) - virtual std::unique_ptr createMFEMState() - { - return std::make_unique(); - } -#endif #if defined(AXOM_USE_CONDUIT) virtual std::unique_ptr createBlueprintState() { diff --git a/src/axom/quest/detail/shaping/InOutSampler.hpp b/src/axom/quest/detail/shaping/InOutSampler.hpp index 09d0035939..75b6071ee1 100644 --- a/src/axom/quest/detail/shaping/InOutSampler.hpp +++ b/src/axom/quest/detail/shaping/InOutSampler.hpp @@ -114,7 +114,7 @@ class InOutSampler * \note \a ToDim must be equal to \a DIM, the dimension of the spatial index */ template - std::enable_if_t sampleInOutField(shaping::SamplingMFEMState& mfemState, + std::enable_if_t sampleInOutField(shaping::MFEMState& mfemState, PointProjector projector = {}) { using PointType = primal::Point; @@ -129,7 +129,7 @@ class InOutSampler * defined to support various callback specializations for the \a PointProjector. */ template - std::enable_if_t sampleInOutField(shaping::SamplingMFEMState&, + std::enable_if_t sampleInOutField(shaping::MFEMState&, PointProjector) { static_assert(ToDim != DIM, @@ -143,7 +143,7 @@ class InOutSampler */ template std::enable_if_t computeVolumeFractionsBaseline( - shaping::SamplingMFEMState& mfemState, + shaping::MFEMState& mfemState, int outputOrder, PointProjector projector = {}) { @@ -163,7 +163,7 @@ class InOutSampler */ template std::enable_if_t computeVolumeFractionsBaseline( - shaping::SamplingMFEMState& AXOM_UNUSED_PARAM(mfemState), + shaping::MFEMState& AXOM_UNUSED_PARAM(mfemState), int AXOM_UNUSED_PARAM(outputOrder), PointProjector AXOM_UNUSED_PARAM(projector)) { diff --git a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp index 6815d65d83..9dc20d749d 100644 --- a/src/axom/quest/detail/shaping/PrimitiveSampler.hpp +++ b/src/axom/quest/detail/shaping/PrimitiveSampler.hpp @@ -170,7 +170,7 @@ class PrimitiveSampler * \note \a ToDim must be equal to \a DIM, the dimension of the spatial index */ template - std::enable_if_t sampleInOutField(shaping::SamplingMFEMState& mfemState, + std::enable_if_t sampleInOutField(shaping::MFEMState& mfemState, PointProjector projector = {}) { using FromPoint = primal::Point; @@ -276,7 +276,7 @@ class PrimitiveSampler * defined to support various callback specializations for the \a PointProjector. */ template - std::enable_if_t sampleInOutField(shaping::SamplingMFEMState&, + std::enable_if_t sampleInOutField(shaping::MFEMState&, PointProjector) { static_assert(ToDim != DIM, @@ -290,7 +290,7 @@ class PrimitiveSampler * \warning Not yet implemented */ template - void computeVolumeFractionsBaseline(shaping::SamplingMFEMState& AXOM_UNUSED_PARAM(mfemState), + void computeVolumeFractionsBaseline(shaping::MFEMState& AXOM_UNUSED_PARAM(mfemState), int AXOM_UNUSED_PARAM(outputOrder), PointProjector AXOM_UNUSED_PARAM(projector)) { diff --git a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp index f2a8f5441d..2f7d97c4ca 100644 --- a/src/axom/quest/detail/shaping/WindingNumberSampler.hpp +++ b/src/axom/quest/detail/shaping/WindingNumberSampler.hpp @@ -130,7 +130,7 @@ class WindingNumberSampler * * \tparam FromDim The dimension of points from the input mesh * \tparam ToDim The dimension of points on the indexed shape - * \param [in] mfemState The SamplingMFEMState object that contains the data collection containing + * \param [in] mfemState The MFEMState object that contains the data collection containing * the mesh and associated query points. It also contains a collection of * quadrature functions for the shape and material inout samples. * \param [in] projector A callback function to apply to points from the input mesh @@ -141,7 +141,7 @@ class WindingNumberSampler * \note \a ToDim must be equal to \a DIM, the dimension of the spatial index */ template - std::enable_if_t sampleInOutField(shaping::SamplingMFEMState& mfemState, + std::enable_if_t sampleInOutField(shaping::MFEMState& mfemState, PointProjector projector = {}) { static_assert(axom::execution_space::onDevice() == false, @@ -253,7 +253,7 @@ class WindingNumberSampler * defined to support various callback specializations for the \a PointProjector. */ template - std::enable_if_t sampleInOutField(shaping::SamplingMFEMState&, + std::enable_if_t sampleInOutField(shaping::MFEMState&, PointProjector) { static_assert(ToDim != DIM, @@ -267,7 +267,7 @@ class WindingNumberSampler */ template std::enable_if_t computeVolumeFractionsBaseline( - shaping::SamplingMFEMState& mfemState, + shaping::MFEMState& mfemState, int outputOrder, PointProjector projector = {}) { @@ -297,7 +297,7 @@ class WindingNumberSampler */ template std::enable_if_t computeVolumeFractionsBaseline( - shaping::SamplingMFEMState& AXOM_UNUSED_PARAM(mfemState), + shaping::MFEMState& AXOM_UNUSED_PARAM(mfemState), int AXOM_UNUSED_PARAM(outputOrder), PointProjector AXOM_UNUSED_PARAM(projector)) { diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp index 2a8edce228..ee95110f0c 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.cpp @@ -196,7 +196,7 @@ mfem::QuadratureFunction* cloneInOutFunction(const mfem::QuadratureFunction* qfu return new mfem::QuadratureFunction(*qfunc); } -void printRegisteredFieldNames(const SamplingMFEMState& mfemState, +void printRegisteredFieldNames(const MFEMState& mfemState, const std::set& knownMaterials, VolFracSampling vfSampling, const std::string& initialMessage) @@ -431,7 +431,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, inoutQFuncs.Register("positions", pos_coef, true); } -void generateSamplingPositions(SamplingMFEMState& mfemState, +void generateSamplingPositions(MFEMState& mfemState, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType) { @@ -450,7 +450,7 @@ void generateSamplingPositions(SamplingMFEMState& mfemState, quadratureType); } -void importInitialVolumeFractions(SamplingMFEMState& mfemState, +void importInitialVolumeFractions(MFEMState& mfemState, const std::map& initialGridFunctions, bool anisotropic) { @@ -506,7 +506,7 @@ void importInitialVolumeFractions(SamplingMFEMState& mfemState, } } -void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, +void computeVolumeFractionsForMaterial(MFEMState& mfemState, const std::string& matField, int volfracOrder, axom::ArrayView sampleResolution, diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp index a3864dc45b..6caff6dd88 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp @@ -33,20 +33,10 @@ using QFunctionCollection = mfem::NamedFieldsMap; using DenseTensorCollection = mfem::NamedFieldsMap; using MFEMArrayCollection = mfem::NamedFieldsMap>; -/// Base class that contains MFEM state for Shaper classes. +/// MFEM state shared by Quest shapers and MFEM-backed sampling helpers. struct MFEMState { - virtual ~MFEMState() = default; - - int meshDimension() const { return m_dc->GetMesh()->Dimension(); } - - sidre::MFEMSidreDataCollection* m_dc {nullptr}; -}; - -/// Derived class that contains additional state for SamplingShaper class. -struct SamplingMFEMState : public MFEMState -{ - ~SamplingMFEMState() override + ~MFEMState() { m_inoutShapeQFuncs.DeleteData(true); m_inoutShapeQFuncs.clear(); @@ -61,6 +51,10 @@ struct SamplingMFEMState : public MFEMState m_inoutArrays.clear(); } + int meshDimension() const { return m_dc->GetMesh()->Dimension(); } + + sidre::MFEMSidreDataCollection* m_dc {nullptr}; + mfem::QuadratureFunction* getShapeFunction(const std::string& name) { return m_inoutShapeQFuncs.Get(name); @@ -125,7 +119,7 @@ struct SamplingMFEMState : public MFEMState * \param vfSampling The type of volume fraction sampling being performed. * \param initialMessage A string to prepend to the printed message. */ -void printRegisteredFieldNames(const SamplingMFEMState& mfemState, +void printRegisteredFieldNames(const MFEMState& mfemState, const std::set& knownMaterials, VolFracSampling vfSampling, const std::string& initialMessage); @@ -201,7 +195,7 @@ void generatePositionsQFunction(mfem::Mesh* mesh, * * \note The sample points are stored as a function corresponding to the mesh positions */ -void generateSamplingPositions(SamplingMFEMState& mfemState, +void generateSamplingPositions(MFEMState& mfemState, axom::ArrayView sampleResolution, axom::numerics::QuadratureType quadratureType); @@ -215,7 +209,7 @@ void generateSamplingPositions(SamplingMFEMState& mfemState, * points. * \param anisotropic Whether the quadrature points are anisotropic. */ -void importInitialVolumeFractions(SamplingMFEMState& mfemState, +void importInitialVolumeFractions(MFEMState& mfemState, const std::map& initialVolumeFractions, bool anisotropic); @@ -229,7 +223,7 @@ void importInitialVolumeFractions(SamplingMFEMState& mfemState, * \param sampleResolution The number of samples in each mesh dimension. * \param quadratureType The quadrature type that determines the sample point locations. */ -void computeVolumeFractionsForMaterial(SamplingMFEMState& mfemState, +void computeVolumeFractionsForMaterial(MFEMState& mfemState, const std::string& matField, int volfracOrder, axom::ArrayView sampleResolution, @@ -277,7 +271,7 @@ bool usesAnisotropicCustomTensorQuadrature(const mfem::Mesh& mesh, */ template void sampleInOutField(const std::string shapeName, - shaping::SamplingMFEMState& mfemState, + shaping::MFEMState& mfemState, InsideFunc&& checkInside, PointProjector projector = {}) { @@ -364,7 +358,7 @@ void sampleInOutField(const std::string shapeName, */ template void computeVolumeFractionsBaseline(const std::string& shapeName, - shaping::SamplingMFEMState& mfemState, + shaping::MFEMState& mfemState, int outputOrder, InsideFunc&& checkInside, PointProjector projector = {}) diff --git a/src/axom/quest/tests/quest_sampling_shaper.cpp b/src/axom/quest/tests/quest_sampling_shaper.cpp index e8048371d6..54bf8fce8a 100644 --- a/src/axom/quest/tests/quest_sampling_shaper.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper.cpp @@ -2625,7 +2625,7 @@ TEST_F(CurvedSampleTester2D, positions_match_curved_mesh_for_anisotropic_custom_ TEST_F(CurvedSampleTester2D, generate_sampling_positions_is_idempotent) { - quest::shaping::SamplingMFEMState mfemState; + quest::shaping::MFEMState mfemState; mfemState.m_dc = &this->getDC(); int sampleRes[] = {3, 2}; From 0bcff1d40c9f6163d7693a03fdb03978b3074eac Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 11:37:47 -0700 Subject: [PATCH 62/72] Simplify creation of Blueprint state. --- src/axom/quest/Shaper.cpp | 4 ++-- src/axom/quest/Shaper.hpp | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index 7b1d738acf..bc41797153 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -104,7 +104,7 @@ Shaper::Shaper(RuntimePolicy execPolicy, , m_comm(MPI_COMM_WORLD) #endif { - m_bp_state = createBlueprintState(); + m_bp_state = std::make_unique(); bpGrp->setDefaultArrayAllocator(m_allocatorId); m_bp_state->initialize(bpGrp, m_allocatorId, resolveBlueprintTopologyName(bpGrp, topo)); @@ -137,7 +137,7 @@ Shaper::Shaper(RuntimePolicy execPolicy, { AXOM_ANNOTATE_SCOPE("Shaper::Shaper_Node"); - m_bp_state = createBlueprintState(); + m_bp_state = std::make_unique(); m_bp_state->initialize(&bpNode, m_allocatorId, resolveBlueprintTopologyName(bpNode, topo)); refreshBlueprintMeshState(); diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index ff6f79cbb1..f8189f9a40 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -326,13 +326,6 @@ class Shaper bool verifyMFEMInputMesh(std::string& whyBad) const; #endif -#if defined(AXOM_USE_CONDUIT) - virtual std::unique_ptr createBlueprintState() - { - return std::make_unique(); - } -#endif - protected: RuntimePolicy m_execPolicy; int m_allocatorId; From 2c4465868e00cc6fc871b5efb9641cb0b510b7a8 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 11:45:39 -0700 Subject: [PATCH 63/72] Remove obsolete methods. --- src/axom/quest/IntersectionShaper.hpp | 2 +- src/axom/quest/Shaper.cpp | 74 ++----------------- src/axom/quest/Shaper.hpp | 37 ---------- .../shaping/shaping_helpers_blueprint.hpp | 25 ++++++- 4 files changed, 29 insertions(+), 109 deletions(-) diff --git a/src/axom/quest/IntersectionShaper.hpp b/src/axom/quest/IntersectionShaper.hpp index 38a4102306..aa68a0f852 100644 --- a/src/axom/quest/IntersectionShaper.hpp +++ b/src/axom/quest/IntersectionShaper.hpp @@ -2948,7 +2948,7 @@ class IntersectionShaper : public Shaper #if defined(AXOM_USE_CONDUIT) if(m_bp_state != nullptr) { - dim = getBlueprintMeshDimension(); + dim = m_bp_state->meshDimension(); } #endif diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index bc41797153..d87433da97 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -106,11 +106,12 @@ Shaper::Shaper(RuntimePolicy execPolicy, { m_bp_state = std::make_unique(); bpGrp->setDefaultArrayAllocator(m_allocatorId); - m_bp_state->initialize(bpGrp, m_allocatorId, resolveBlueprintTopologyName(bpGrp, topo)); + m_bp_state->initialize(bpGrp, m_allocatorId, topo); SLIC_ASSERT(m_bp_state->isSidreBacked()); - refreshBlueprintMeshState(); + m_bp_state->refreshBlueprintMeshNode(); + m_cellCount = conduit::blueprint::mesh::topology::length(m_bp_state->getBlueprintTopologyNode()); setFilePath(shapeSet.getPath()); } @@ -138,9 +139,10 @@ Shaper::Shaper(RuntimePolicy execPolicy, AXOM_ANNOTATE_SCOPE("Shaper::Shaper_Node"); m_bp_state = std::make_unique(); - m_bp_state->initialize(&bpNode, m_allocatorId, resolveBlueprintTopologyName(bpNode, topo)); + m_bp_state->initialize(&bpNode, m_allocatorId, topo); - refreshBlueprintMeshState(); + m_bp_state->refreshBlueprintMeshNode(); + m_cellCount = conduit::blueprint::mesh::topology::length(m_bp_state->getBlueprintTopologyNode()); setFilePath(shapeSet.getPath()); } @@ -260,68 +262,6 @@ void Shaper::loadShapeInternal(const klee::Shape& shape, double percentError, do bool Shaper::verifyInputMesh(std::string& whyBad) const { return verifyInputMeshImpl(whyBad); } #if defined(AXOM_USE_CONDUIT) -std::string Shaper::resolveBlueprintTopologyName(const sidre::Group* bpMesh, - const std::string& topo) const -{ - SLIC_ASSERT(bpMesh != nullptr); - auto* topologiesGrp = bpMesh->getGroup("topologies"); - SLIC_ERROR_IF(topologiesGrp == nullptr, "Blueprint mesh is missing a 'topologies' group."); - - const std::string topologyName = topo.empty() ? topologiesGrp->getGroupName(0) : topo; - SLIC_ERROR_IF(topologyName == sidre::InvalidName, - "Blueprint mesh does not contain any topology groups."); - SLIC_ERROR_IF(!topologiesGrp->hasGroup(topologyName), - axom::fmt::format("Blueprint mesh does not contain topology '{}'.", topologyName)); - - return topologyName; -} - -std::string Shaper::resolveBlueprintTopologyName(const conduit::Node& bpMesh, - const std::string& topo) const -{ - SLIC_ERROR_IF(!bpMesh.has_path("topologies"), "Blueprint mesh is missing a 'topologies' node."); - - const conduit::Node& topologies = bpMesh.fetch_existing("topologies"); - SLIC_ERROR_IF(topologies.number_of_children() == 0, - "Blueprint mesh does not contain any topology nodes."); - - const std::string topologyName = topo.empty() ? topologies.child(0).name() : topo; - SLIC_ERROR_IF(!topologies.has_child(topologyName), - axom::fmt::format("Blueprint mesh does not contain topology '{}'.", topologyName)); - - return topologyName; -} - -void Shaper::refreshBlueprintMeshState() -{ - SLIC_ASSERT(m_bp_state != nullptr); - m_bp_state->refreshBlueprintMeshNode(); - m_cellCount = conduit::blueprint::mesh::topology::length(getBlueprintTopologyNode()); -} - -const conduit::Node& Shaper::getBlueprintTopologyNode() const -{ - SLIC_ASSERT(m_bp_state != nullptr); - return m_bp_state->getBlueprintTopologyNode(); -} - -const conduit::Node& Shaper::getBlueprintCoordsetNode() const -{ - SLIC_ASSERT(m_bp_state != nullptr); - return m_bp_state->getBlueprintCoordsetNode(); -} - -std::string Shaper::getBlueprintCellShape() const -{ - return shaping::getBlueprintCellShape(getBlueprintTopologyNode()); -} - -int Shaper::getBlueprintMeshDimension() const -{ - SLIC_ASSERT(m_bp_state != nullptr); - return m_bp_state->meshDimension(); -} - bool Shaper::verifyBlueprintMeshIsStructuredOrUnstructuredQuadHex(std::string& whyBad) const { bool rval = true; @@ -372,7 +312,7 @@ void Shaper::ensureBlueprintMeshIsUnstructured() { m_bp_state->ensureUnstructured(m_execPolicy); } - m_cellCount = conduit::blueprint::mesh::topology::length(getBlueprintTopologyNode()); + m_cellCount = conduit::blueprint::mesh::topology::length(m_bp_state->getBlueprintTopologyNode()); } #endif diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index f8189f9a40..d189e538c6 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -261,43 +261,6 @@ class Shaper virtual bool verifyInputMeshImpl(std::string& whyBad) const = 0; #if defined(AXOM_USE_CONDUIT) - /*! - * \brief Selects the Blueprint topology name to use and verifies it exists. - */ - std::string resolveBlueprintTopologyName(const sidre::Group* bpMesh, const std::string& topo) const; - - /*! - * \brief Selects the Blueprint topology name to use and verifies it exists. - */ - std::string resolveBlueprintTopologyName(const conduit::Node& bpMesh, const std::string& topo) const; - - /*! - * \brief Rebuilds the internal Conduit view and cached cell count from the - * current Sidre-owned Blueprint mesh. - */ - void refreshBlueprintMeshState(); - - /*! - * \brief Returns the active Blueprint topology node. - */ - const conduit::Node& getBlueprintTopologyNode() const; - - /*! - * \brief Returns the active Blueprint coordset node. - */ - const conduit::Node& getBlueprintCoordsetNode() const; - - /*! - * \brief Returns the active Blueprint cell shape name. - */ - std::string getBlueprintCellShape() const; - - /*! - * \brief Returns the active Blueprint mesh dimension for supported quad/hex - * meshes. - */ - int getBlueprintMeshDimension() const; - /*! * \brief Helper for Blueprint meshes supported directly by sampling or by * lazy conversion in the intersection backend. diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 7e7d87fec7..01ce4ec02a 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -79,13 +79,21 @@ struct BlueprintState * * \param group The Sidre group that backs the Blueprint mesh. * \param allocatorId Allocator id used for new array allocations. - * \param topologyName Name of the active topology. + * \param topologyName Requested topology name, or empty to select the first topology. */ void initialize(axom::sidre::Group* group, int allocatorId, const std::string& topologyName) { m_group_ptr = group; m_allocator_id = allocatorId; - m_topology_name = topologyName; + auto* topologiesGrp = group->getGroup("topologies"); + SLIC_ERROR_IF(topologiesGrp == nullptr, "Blueprint mesh is missing a 'topologies' group."); + + m_topology_name = topologyName.empty() ? topologiesGrp->getGroupName(0) : topologyName; + SLIC_ERROR_IF(m_topology_name == sidre::InvalidName, + "Blueprint mesh does not contain any topology groups."); + SLIC_ERROR_IF(!topologiesGrp->hasGroup(m_topology_name), + axom::fmt::format("Blueprint mesh does not contain topology '{}'.", + m_topology_name)); m_external_node_ptr = nullptr; } @@ -94,13 +102,22 @@ struct BlueprintState * * \param node The Conduit node that backs the Blueprint mesh. * \param allocatorId Allocator id used for new array allocations. - * \param topologyName Name of the active topology. + * \param topologyName Requested topology name, or empty to select the first topology. */ void initialize(conduit::Node* node, int allocatorId, const std::string& topologyName) { m_group_ptr = nullptr; m_allocator_id = allocatorId; - m_topology_name = topologyName; + SLIC_ERROR_IF(!node->has_path("topologies"), "Blueprint mesh is missing a 'topologies' node."); + + const conduit::Node& topologies = node->fetch_existing("topologies"); + SLIC_ERROR_IF(topologies.number_of_children() == 0, + "Blueprint mesh does not contain any topology nodes."); + + m_topology_name = topologyName.empty() ? topologies.child(0).name() : topologyName; + SLIC_ERROR_IF(!topologies.has_child(m_topology_name), + axom::fmt::format("Blueprint mesh does not contain topology '{}'.", + m_topology_name)); m_external_node_ptr = node; } From 38a68a8d3c034c0000ba955314c13b8445d3f2c0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 16 Jun 2026 11:46:25 -0700 Subject: [PATCH 64/72] make style --- src/axom/quest/SamplingShaper.cpp | 5 +---- .../detail/shaping/shaping_helpers_blueprint.hpp | 12 ++++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index fd38c46cbc..f186d60ad5 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -394,10 +394,7 @@ void SamplingShaper::printRegisteredFieldNames(const std::string& initialMessage #if defined(AXOM_USE_MFEM) if(m_mfem_state != nullptr) { - shaping::printRegisteredFieldNames(*m_mfem_state, - m_knownMaterials, - m_vfSampling, - initialMessage); + shaping::printRegisteredFieldNames(*m_mfem_state, m_knownMaterials, m_vfSampling, initialMessage); return; } #endif diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 01ce4ec02a..9308602d30 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -91,9 +91,9 @@ struct BlueprintState m_topology_name = topologyName.empty() ? topologiesGrp->getGroupName(0) : topologyName; SLIC_ERROR_IF(m_topology_name == sidre::InvalidName, "Blueprint mesh does not contain any topology groups."); - SLIC_ERROR_IF(!topologiesGrp->hasGroup(m_topology_name), - axom::fmt::format("Blueprint mesh does not contain topology '{}'.", - m_topology_name)); + SLIC_ERROR_IF( + !topologiesGrp->hasGroup(m_topology_name), + axom::fmt::format("Blueprint mesh does not contain topology '{}'.", m_topology_name)); m_external_node_ptr = nullptr; } @@ -115,9 +115,9 @@ struct BlueprintState "Blueprint mesh does not contain any topology nodes."); m_topology_name = topologyName.empty() ? topologies.child(0).name() : topologyName; - SLIC_ERROR_IF(!topologies.has_child(m_topology_name), - axom::fmt::format("Blueprint mesh does not contain topology '{}'.", - m_topology_name)); + SLIC_ERROR_IF( + !topologies.has_child(m_topology_name), + axom::fmt::format("Blueprint mesh does not contain topology '{}'.", m_topology_name)); m_external_node_ptr = node; } From 79584adbb670714fd01f4cf4cae98c4cef0d3682 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 17 Jun 2026 17:43:04 -0700 Subject: [PATCH 65/72] Undo a reader change. --- .../quest/interface/internal/QuestHelpers.cpp | 105 ++---------------- 1 file changed, 8 insertions(+), 97 deletions(-) diff --git a/src/axom/quest/interface/internal/QuestHelpers.cpp b/src/axom/quest/interface/internal/QuestHelpers.cpp index b343c70c93..9e8a7993e6 100644 --- a/src/axom/quest/interface/internal/QuestHelpers.cpp +++ b/src/axom/quest/interface/internal/QuestHelpers.cpp @@ -18,9 +18,10 @@ #endif #if defined(AXOM_USE_C2C) - #include "axom/quest/io/C2CReader.hpp" #if defined(AXOM_USE_MPI) #include "axom/quest/io/PC2CReader.hpp" + #else + #include "axom/quest/io/C2CReader.hpp" #endif #endif @@ -41,30 +42,6 @@ namespace internal { /// Mesh I/O methods -#ifdef AXOM_USE_MPI -namespace -{ -bool mpiIsActive() -{ - int initialized = 0; - MPI_Initialized(&initialized); - if(!initialized) - { - return false; - } - - int finalized = 0; - MPI_Finalized(&finalized); - return finalized == 0; -} - -bool useParallelReader(MPI_Comm comm) -{ - return mpiIsActive() && comm != MPI_COMM_NULL && comm != MPI_COMM_SELF; -} -} // namespace -#endif - #if defined(AXOM_USE_UMPIRE_SHARED_MEMORY) /*! * \brief Deallocates the specified MPI communicator object. @@ -343,28 +320,11 @@ int read_stl_mesh(const std::string& file, mint::Mesh*& m, MPI_Comm comm) m = new TriangleMesh(DIMENSION, mint::TRIANGLE); // STEP 2: construct STL reader - quest::STLReader reader; #ifdef AXOM_USE_MPI - if(useParallelReader(comm)) - { - quest::PSTLReader preader(comm); - preader.setFileName(file); - int rc = preader.read(); - if(rc == READ_SUCCESS) - { - preader.getMesh(static_cast(m)); - } - else - { - SLIC_WARNING("reading STL file failed, setting mesh to NULL"); - delete m; - m = nullptr; - } - - return rc; - } + quest::PSTLReader reader(comm); #else AXOM_UNUSED_VAR(comm); + quest::STLReader reader; #endif // STEP 3: read the mesh from the STL file @@ -411,43 +371,11 @@ int read_c2c_mesh(const std::string& file, } // STEP 2: construct C2C reader - quest::C2CReader reader; #if defined(AXOM_USE_MPI) && defined(AXOM_USE_C2C) - if(useParallelReader(comm)) - { - quest::PC2CReader preader(comm); - preader.setFileName(file); - int rc = preader.read(); - if(rc == READ_SUCCESS) - { - m = new SegmentMesh(DIMENSION, mint::SEGMENT); - - LinearizeCurves lin; - lin.setVertexWeldingThreshold(vertexWeldThreshold); - if(uniform) - { - lin.getLinearMeshUniform(preader.getCurvesView(), - static_cast(m), - segmentsPerPiece); - } - else - { - lin.getLinearMeshNonUniform(preader.getCurvesView(), - static_cast(m), - percentError); - } - revolvedVolume = lin.getRevolvedVolume(preader.getCurvesView(), transform); - } - else - { - SLIC_WARNING("reading C2C file failed, setting mesh to NULL"); - m = nullptr; - } - - return rc; - } + quest::PC2CReader reader(comm); #else AXOM_UNUSED_VAR(comm); + quest::C2CReader reader; #endif // STEP 3: read the mesh from the input file @@ -500,28 +428,11 @@ int read_pro_e_mesh(const std::string& file, mint::Mesh*& m, MPI_Comm comm) m = new TetMesh(DIMENSION, mint::TET); // STEP 2: construct Pro/E reader - quest::ProEReader reader; #ifdef AXOM_USE_MPI - if(useParallelReader(comm)) - { - quest::PProEReader preader(comm); - preader.setFileName(file); - int rc = preader.read(); - if(rc == READ_SUCCESS) - { - preader.getMesh(static_cast(m)); - } - else - { - SLIC_WARNING("reading Pro/E file failed, setting mesh to NULL"); - delete m; - m = nullptr; - } - - return rc; - } + quest::PProEReader reader(comm); #else AXOM_UNUSED_VAR(comm); + quest::ProEReader reader; #endif // STEP 3: read the mesh from the Pro/E file From 5bbec91dccb454d821293a9bfb7a57ae3131d745 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 25 Jun 2026 12:03:27 -0700 Subject: [PATCH 66/72] Implement missing quadrature types. --- src/axom/core/numerics/quadrature.cpp | 199 ++++++++++++++++-- src/axom/core/numerics/quadrature.hpp | 98 ++++++++- src/axom/core/tests/numerics_quadrature.hpp | 107 +++++++++- src/axom/quest/SamplingShaper.cpp | 28 +-- .../shaping/shaping_helpers_blueprint.cpp | 4 - .../tests/quest_sampling_shaper_blueprint.cpp | 53 +++++ 6 files changed, 424 insertions(+), 65 deletions(-) diff --git a/src/axom/core/numerics/quadrature.cpp b/src/axom/core/numerics/quadrature.cpp index b419f487d9..b361dedb39 100644 --- a/src/axom/core/numerics/quadrature.cpp +++ b/src/axom/core/numerics/quadrature.cpp @@ -40,28 +40,22 @@ bool is_valid_quadrature_type(int quadratureType) } } -bool is_supported_quadrature_type(QuadratureType quadratureType) -{ - switch(quadratureType) - { - case QuadratureType::Invalid: - case QuadratureType::GaussLegendre: - case QuadratureType::OpenUniform: - case QuadratureType::ClosedUniform: - return true; - case QuadratureType::GaussLobatto: - case QuadratureType::OpenHalfUniform: - case QuadratureType::ClosedGL: - return false; - } - - return false; -} - void compute_gauss_legendre_data(int npts, axom::Array& nodes, axom::Array& weights, int allocatorID); +void compute_gauss_lobatto_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID); +void compute_open_half_uniform_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID); +void compute_closed_gl_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID); namespace { @@ -272,6 +266,77 @@ void compute_open_uniform_data(int npts, compute_interpolatory_weights(nodes, weights, allocatorID); } +void compute_gauss_lobatto_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + + nodes = axom::Array(npts, npts, allocatorID); + weights = axom::Array(npts, npts, allocatorID); + + if(npts == 1) + { + nodes[0] = 0.5; + weights[0] = 1.0; + return; + } + + nodes[0] = 0.0; + nodes[npts - 1] = 1.0; + weights[0] = weights[npts - 1] = 1.0 / (npts * (npts - 1.0)); + + constexpr int MaxIterations = 16; + const double tol = axom::numeric_limits::epsilon(); + + for(int i = 1; i <= (npts - 1) / 2; ++i) + { + double x = std::sin(M_PI * (static_cast(i) / (npts - 1) - 0.5)); + double pNm2 = 1.0; + double pNm1 = x; + + for(int iter = 0; iter < MaxIterations; ++iter) + { + pNm2 = 1.0; + pNm1 = x; + for(int l = 1; l < (npts - 1); ++l) + { + const double p = ((2 * l + 1) * x * pNm1 - l * pNm2) / (l + 1); + pNm2 = pNm1; + pNm1 = p; + } + + const double dx = (x * pNm1 - pNm2) / (npts * pNm1); + x -= dx; + + if(std::fabs(dx) <= tol * (1.0 + std::fabs(x))) + { + break; + } + + assert("Gauss-Lobatto Newton iteration did not converge." && iter + 1 < MaxIterations); + } + + pNm2 = 1.0; + pNm1 = x; + for(int l = 1; l < (npts - 1); ++l) + { + const double p = ((2 * l + 1) * x * pNm1 - l * pNm2) / (l + 1); + pNm2 = pNm1; + pNm1 = p; + } + + const double node = 0.5 * (1.0 + x); + const double weight = 1.0 / (npts * (npts - 1.0) * pNm1 * pNm1); + + nodes[i] = node; + nodes[npts - 1 - i] = 1.0 - node; + weights[i] = weight; + weights[npts - 1 - i] = weight; + } +} + void compute_closed_uniform_data(int npts, axom::Array& nodes, axom::Array& weights, @@ -296,6 +361,56 @@ void compute_closed_uniform_data(int npts, compute_interpolatory_weights(nodes, weights, allocatorID); } +void compute_open_half_uniform_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + + nodes = axom::Array(npts, npts, allocatorID); + for(int i = 0; i < npts; ++i) + { + nodes[i] = static_cast(2 * i + 1) / static_cast(2 * npts); + } + + compute_interpolatory_weights(nodes, weights, allocatorID); +} + +void compute_closed_gl_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + + nodes = axom::Array(npts, npts, allocatorID); + if(npts == 1) + { + nodes[0] = 0.5; + weights = axom::Array(1, 1, allocatorID); + weights[0] = 1.0; + return; + } + + nodes[0] = 0.0; + nodes[npts - 1] = 1.0; + + if(npts > 2) + { + axom::Array glNodes; + axom::Array glWeights; + compute_gauss_legendre_data(npts - 1, glNodes, glWeights, allocatorID); + + for(int i = 1; i < npts - 1; ++i) + { + nodes[i] = 0.5 * (glNodes[i - 1] + glNodes[i]); + } + } + + compute_interpolatory_weights(nodes, weights, allocatorID); +} + /*! * \brief Computes or accesses a precomputed 1D quadrature rule of Gauss-Legendre points * @@ -326,28 +441,43 @@ QuadratureRule get_gauss_legendre(int npts, int allocatorID) return QuadratureRule {storage.nodes.view(), storage.weights.view()}; } +QuadratureRule get_gauss_lobatto(int npts, int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + + static axom::FlatMap rule_library(64); + static std::mutex rule_library_mutex; + auto& storage = get_cached_rule_storage(npts, + allocatorID, + rule_library, + rule_library_mutex, + compute_gauss_lobatto_data); + return QuadratureRule {storage.nodes.view(), storage.weights.view()}; +} + QuadratureRule get_quadrature_rule(QuadratureType quadratureType, int npts, int allocatorID) { assert("Invalid Axom quadrature type." && is_valid_quadrature_type(static_cast(quadratureType))); - assert("Unsupported Axom quadrature type." && is_supported_quadrature_type(quadratureType)); switch(quadratureType) { case QuadratureType::Invalid: case QuadratureType::GaussLegendre: return get_gauss_legendre(npts, allocatorID); + case QuadratureType::GaussLobatto: + return get_gauss_lobatto(npts, allocatorID); case QuadratureType::OpenUniform: return get_open_uniform(npts, allocatorID); case QuadratureType::ClosedUniform: return get_closed_uniform(npts, allocatorID); - case QuadratureType::GaussLobatto: case QuadratureType::OpenHalfUniform: + return get_open_half_uniform(npts, allocatorID); case QuadratureType::ClosedGL: - break; + return get_closed_gl(npts, allocatorID); } - assert("Unsupported Axom quadrature type." && false); + assert("Unhandled Axom quadrature type." && false); return get_gauss_legendre(npts, allocatorID); } @@ -379,5 +509,30 @@ QuadratureRule get_closed_uniform(int npts, int allocatorID) return QuadratureRule {storage.nodes.view(), storage.weights.view()}; } +QuadratureRule get_open_half_uniform(int npts, int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + + static axom::FlatMap rule_library(64); + static std::mutex rule_library_mutex; + auto& storage = get_cached_rule_storage(npts, + allocatorID, + rule_library, + rule_library_mutex, + compute_open_half_uniform_data); + return QuadratureRule {storage.nodes.view(), storage.weights.view()}; +} + +QuadratureRule get_closed_gl(int npts, int allocatorID) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + + static axom::FlatMap rule_library(64); + static std::mutex rule_library_mutex; + auto& storage = + get_cached_rule_storage(npts, allocatorID, rule_library, rule_library_mutex, compute_closed_gl_data); + return QuadratureRule {storage.nodes.view(), storage.weights.view()}; +} + } /* end namespace numerics */ } /* end namespace axom */ diff --git a/src/axom/core/numerics/quadrature.hpp b/src/axom/core/numerics/quadrature.hpp index e4405e4127..a22f5456dc 100644 --- a/src/axom/core/numerics/quadrature.hpp +++ b/src/axom/core/numerics/quadrature.hpp @@ -41,14 +41,6 @@ enum class QuadratureType : int */ bool is_valid_quadrature_type(int quadratureType); -/*! - * \brief Returns true when the supplied quadrature family is currently - * implemented in Axom core numerics. - * - * \note Families may be valid enum values but not yet implemented. - */ -bool is_supported_quadrature_type(QuadratureType quadratureType); - /*! * \class QuadratureRule * @@ -58,8 +50,11 @@ class QuadratureRule { // Define friend functions so rules can only be created via get_rule() methods friend QuadratureRule get_gauss_legendre(int, int); + friend QuadratureRule get_gauss_lobatto(int, int); friend QuadratureRule get_open_uniform(int, int); friend QuadratureRule get_closed_uniform(int, int); + friend QuadratureRule get_open_half_uniform(int, int); + friend QuadratureRule get_closed_gl(int, int); public: //! \brief Accessor for the full array of quadrature nodes @@ -127,6 +122,32 @@ void compute_gauss_legendre_data(int npts, */ QuadratureRule get_gauss_legendre(int npts, int allocatorID = axom::getDefaultAllocatorID()); +/*! + * \brief Computes a 1D quadrature rule of Gauss-Lobatto points. + * + * \param [in] npts The number of points in the rule + * \param [out] nodes The array of 1D nodes + * \param [out] weights The array of weights + * + * A Gauss-Lobatto rule with \a npts points can exactly integrate + * polynomials of order `2 * npts - 3` for `npts > 1`. + */ +void compute_gauss_lobatto_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID = axom::getDefaultAllocatorID()); + +/*! + * \brief Computes or accesses a precomputed 1D quadrature rule of + * Gauss-Lobatto points. + * + * \param [in] npts The number of points in the rule + * + * \return The `QuadratureRule` object which contains axom::ArrayView's + * of stored nodes and weights + */ +QuadratureRule get_gauss_lobatto(int npts, int allocatorID = axom::getDefaultAllocatorID()); + /*! * \brief Returns an Axom quadrature rule by family. * @@ -134,8 +155,7 @@ QuadratureRule get_gauss_legendre(int npts, int allocatorID = axom::getDefaultAl * \param [in] npts The number of quadrature points in the rule. * * \note `QuadratureType::Invalid` selects Axom's default rule, which is - * currently Gauss-Legendre. Only currently-supported Axom quadrature - * families may be passed here. + * currently Gauss-Legendre. */ QuadratureRule get_quadrature_rule(QuadratureType quadratureType, int npts, @@ -198,6 +218,64 @@ void compute_closed_uniform_data(int npts, */ QuadratureRule get_closed_uniform(int npts, int allocatorID = axom::getDefaultAllocatorID()); +/*! + * \brief Computes a 1D quadrature rule of open-half uniform Newton-Cotes + * points. + * + * \param [in] npts The number of points in the rule + * \param [out] nodes The array of 1D nodes + * \param [out] weights The array of weights + * + * The points are placed at `x_i = (2 * i + 1) / (2 * npts)` for + * `i = 0, ..., npts - 1`, matching MFEM's `OpenHalfUniform`. + * + * The rule order matches MFEM's convention: `npts - 1 + npts % 2`. + */ +void compute_open_half_uniform_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID = axom::getDefaultAllocatorID()); + +/*! + * \brief Computes or accesses a precomputed 1D quadrature rule of open-half + * uniform Newton-Cotes points. + * + * \param [in] npts The number of points in the rule + * + * \return The `QuadratureRule` object which contains axom::ArrayView's + * of stored nodes and weights + */ +QuadratureRule get_open_half_uniform(int npts, int allocatorID = axom::getDefaultAllocatorID()); + +/*! + * \brief Computes a 1D quadrature rule of closed Gauss-Legendre points. + * + * \param [in] npts The number of points in the rule + * \param [out] nodes The array of 1D nodes + * \param [out] weights The array of weights + * + * For `npts > 2`, the rule uses the interval endpoints together with the + * midpoints between adjacent `(npts - 1)`-point Gauss-Legendre nodes, + * matching MFEM's `ClosedGL`. + * + * The rule order matches MFEM's convention: `npts - 1 + npts % 2`. + */ +void compute_closed_gl_data(int npts, + axom::Array& nodes, + axom::Array& weights, + int allocatorID = axom::getDefaultAllocatorID()); + +/*! + * \brief Computes or accesses a precomputed 1D quadrature rule of closed + * Gauss-Legendre points. + * + * \param [in] npts The number of points in the rule + * + * \return The `QuadratureRule` object which contains axom::ArrayView's + * of stored nodes and weights + */ +QuadratureRule get_closed_gl(int npts, int allocatorID = axom::getDefaultAllocatorID()); + } /* end namespace numerics */ } /* end namespace axom */ diff --git a/src/axom/core/tests/numerics_quadrature.hpp b/src/axom/core/tests/numerics_quadrature.hpp index b99632796b..e6bd711b06 100644 --- a/src/axom/core/tests/numerics_quadrature.hpp +++ b/src/axom/core/tests/numerics_quadrature.hpp @@ -140,13 +140,13 @@ TEST(numerics_quadrature, get_nodes_hip) { test_device_quadrature -void check_polynomial_exactness(RuleGetter&& getRule, int maxNpts) +template +void check_polynomial_exactness(RuleGetter&& getRule, ExactDegreeGetter&& getExactDegree, int maxNpts) { for(int npts = 1; npts <= maxNpts; ++npts) { const auto rule = getRule(npts); - const int exactDegree = npts - 1 + npts % 2; + const int exactDegree = getExactDegree(npts); axom::Array coeffs(exactDegree + 1, exactDegree + 1); for(int j = 0; j <= exactDegree; ++j) @@ -204,6 +204,25 @@ TEST(numerics_quadrature, open_uniform_small_rules) EXPECT_DOUBLE_EQ(rule.weight(2), 2.0 / 3.0); } +TEST(numerics_quadrature, gauss_lobatto_small_rules) +{ + auto rule = axom::numerics::get_gauss_lobatto(1); + ASSERT_EQ(rule.getNumPoints(), 1); + EXPECT_DOUBLE_EQ(rule.node(0), 0.5); + EXPECT_DOUBLE_EQ(rule.weight(0), 1.0); + + rule = axom::numerics::get_gauss_lobatto(4); + ASSERT_EQ(rule.getNumPoints(), 4); + EXPECT_DOUBLE_EQ(rule.node(0), 0.0); + EXPECT_NEAR(rule.node(1), 0.27639320225002103, 1e-15); + EXPECT_NEAR(rule.node(2), 0.7236067977499789, 1e-15); + EXPECT_DOUBLE_EQ(rule.node(3), 1.0); + EXPECT_DOUBLE_EQ(rule.weight(0), 1.0 / 12.0); + EXPECT_DOUBLE_EQ(rule.weight(1), 5.0 / 12.0); + EXPECT_DOUBLE_EQ(rule.weight(2), 5.0 / 12.0); + EXPECT_DOUBLE_EQ(rule.weight(3), 1.0 / 12.0); +} + TEST(numerics_quadrature, closed_uniform_small_rules) { auto rule = axom::numerics::get_closed_uniform(1); @@ -221,6 +240,43 @@ TEST(numerics_quadrature, closed_uniform_small_rules) EXPECT_DOUBLE_EQ(rule.weight(2), 1.0 / 6.0); } +TEST(numerics_quadrature, open_half_uniform_small_rules) +{ + auto rule = axom::numerics::get_open_half_uniform(1); + ASSERT_EQ(rule.getNumPoints(), 1); + EXPECT_DOUBLE_EQ(rule.node(0), 0.5); + EXPECT_DOUBLE_EQ(rule.weight(0), 1.0); + + rule = axom::numerics::get_open_half_uniform(2); + ASSERT_EQ(rule.getNumPoints(), 2); + EXPECT_DOUBLE_EQ(rule.node(0), 0.25); + EXPECT_DOUBLE_EQ(rule.node(1), 0.75); + EXPECT_DOUBLE_EQ(rule.weight(0), 0.5); + EXPECT_DOUBLE_EQ(rule.weight(1), 0.5); +} + +TEST(numerics_quadrature, closed_gl_small_rules) +{ + auto rule = axom::numerics::get_closed_gl(1); + ASSERT_EQ(rule.getNumPoints(), 1); + EXPECT_DOUBLE_EQ(rule.node(0), 0.5); + EXPECT_DOUBLE_EQ(rule.weight(0), 1.0); + + rule = axom::numerics::get_closed_gl(4); + ASSERT_EQ(rule.getNumPoints(), 4); + EXPECT_DOUBLE_EQ(rule.node(0), 0.0); + EXPECT_NEAR(rule.node(1), 0.30635083268962916, 1e-15); + EXPECT_NEAR(rule.node(2), 0.6936491673103709, 1e-15); + EXPECT_DOUBLE_EQ(rule.node(3), 1.0); + + double weightSum = 0.0; + for(int i = 0; i < rule.getNumPoints(); ++i) + { + weightSum += rule.weight(i); + } + EXPECT_NEAR(weightSum, 1.0, 1e-15); +} + TEST(numerics_quadrature, quadrature_type_dispatch) { using axom::numerics::QuadratureType; @@ -242,14 +298,55 @@ TEST(numerics_quadrature, quadrature_type_dispatch) EXPECT_DOUBLE_EQ(rule.node(0), 0.0); EXPECT_DOUBLE_EQ(rule.node(1), 0.5); EXPECT_DOUBLE_EQ(rule.node(2), 1.0); + + rule = axom::numerics::get_quadrature_rule(QuadratureType::GaussLobatto, 4); + EXPECT_DOUBLE_EQ(rule.node(0), 0.0); + EXPECT_NEAR(rule.node(1), 0.27639320225002103, 1e-15); + EXPECT_NEAR(rule.node(2), 0.7236067977499789, 1e-15); + EXPECT_DOUBLE_EQ(rule.node(3), 1.0); + + rule = axom::numerics::get_quadrature_rule(QuadratureType::OpenHalfUniform, 2); + EXPECT_DOUBLE_EQ(rule.node(0), 0.25); + EXPECT_DOUBLE_EQ(rule.node(1), 0.75); + + rule = axom::numerics::get_quadrature_rule(QuadratureType::ClosedGL, 4); + EXPECT_DOUBLE_EQ(rule.node(0), 0.0); + EXPECT_NEAR(rule.node(1), 0.30635083268962916, 1e-15); + EXPECT_NEAR(rule.node(2), 0.6936491673103709, 1e-15); + EXPECT_DOUBLE_EQ(rule.node(3), 1.0); } TEST(numerics_quadrature, open_uniform_exactness) { - check_polynomial_exactness([](int npts) { return axom::numerics::get_open_uniform(npts); }, 10); + check_polynomial_exactness([](int npts) { return axom::numerics::get_open_uniform(npts); }, + [](int npts) { return npts - 1 + npts % 2; }, + 10); } TEST(numerics_quadrature, closed_uniform_exactness) { - check_polynomial_exactness([](int npts) { return axom::numerics::get_closed_uniform(npts); }, 10); + check_polynomial_exactness([](int npts) { return axom::numerics::get_closed_uniform(npts); }, + [](int npts) { return npts - 1 + npts % 2; }, + 10); +} + +TEST(numerics_quadrature, gauss_lobatto_exactness) +{ + check_polynomial_exactness([](int npts) { return axom::numerics::get_gauss_lobatto(npts); }, + [](int npts) { return npts == 1 ? 1 : 2 * npts - 3; }, + 10); +} + +TEST(numerics_quadrature, open_half_uniform_exactness) +{ + check_polynomial_exactness([](int npts) { return axom::numerics::get_open_half_uniform(npts); }, + [](int npts) { return npts - 1 + npts % 2; }, + 10); +} + +TEST(numerics_quadrature, closed_gl_exactness) +{ + check_polynomial_exactness([](int npts) { return axom::numerics::get_closed_gl(npts); }, + [](int npts) { return npts - 1 + npts % 2; }, + 10); } diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index f186d60ad5..42ee312bfa 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -24,34 +24,14 @@ namespace quest void SamplingShaper::setQuadratureType(axom::numerics::QuadratureType qtype) { -#if defined(AXOM_USE_CONDUIT) - if(m_bp_state != nullptr) + if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) { - // For Blueprint, we rely on Axom quadrature types and not all are implemented yet. - if(axom::numerics::is_supported_quadrature_type(qtype)) - { - m_quadratureType = qtype; - } - else - { - SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); - } + m_quadratureType = qtype; } -#endif -#if defined(AXOM_USE_MFEM) - if(m_mfem_state != nullptr) + else { - // Check that the value is valid. - if(axom::numerics::is_valid_quadrature_type(static_cast(qtype))) - { - m_quadratureType = qtype; - } - else - { - SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); - } + SLIC_ERROR(axom::fmt::format("Invalid quadrature type value {}", static_cast(qtype))); } -#endif } void SamplingShaper::setSamplingResolution(int sampleRes) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp index a36338ee8f..d56a883f8f 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.cpp @@ -68,10 +68,6 @@ numerics::QuadratureRule getBlueprintQuadratureRule(axom::numerics::QuadratureTy int allocatorID) { SLIC_ERROR_IF(npts < 1, axom::fmt::format("Invalid sample resolution {}.", npts)); - SLIC_ERROR_IF( - !axom::numerics::is_supported_quadrature_type(quadratureType), - axom::fmt::format("Quadrature type {} is not yet supported for Blueprint quadrature meshes.", - static_cast(quadratureType))); return numerics::get_quadrature_rule(quadratureType, npts, allocatorID); } diff --git a/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp b/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp index aa804b2947..aea0b67d2a 100644 --- a/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp +++ b/src/axom/quest/tests/quest_sampling_shaper_blueprint.cpp @@ -90,6 +90,59 @@ TEST(SamplingShaperBlueprintTest, sidre_blueprint_quadrature_persists) EXPECT_TRUE(refreshedMesh.has_path(axom::fmt::format("fields/{}/values", backgroundMatInOutName))); } +TEST(SamplingShaperBlueprintTest, sidre_blueprint_accepts_all_axom_quadratures) +{ + const axom::numerics::QuadratureType quadratures[] = { + axom::numerics::QuadratureType::GaussLobatto, + axom::numerics::QuadratureType::OpenHalfUniform, + axom::numerics::QuadratureType::ClosedGL}; + + for(const auto quadrature : quadratures) + { + sidre::DataStore dataStore; + auto* meshGroup = dataStore.getRoot()->createGroup("mesh"); + + const primal::BoundingBox bbox {{0., 0.}, {1., 1.}}; + const axom::NumericArray res {{2, 2}}; + quest::util::make_unstructured_blueprint_box_mesh_2d(meshGroup, bbox, res, "mesh", "coords"); + + constexpr axom::IndexType cellCount = 4; + const std::string backgroundVolFracName = quest::shaping::volumeFractionFieldName("background"); + auto* fieldGroup = meshGroup->createGroup(axom::fmt::format("fields/{}", backgroundVolFracName)); + fieldGroup->createViewString("association", "element"); + fieldGroup->createViewString("topology", "mesh"); + auto* valuesView = + fieldGroup->createViewAndAllocate("values", axom::sidre::DataTypeId::FLOAT64_ID, cellCount); + auto* values = static_cast(valuesView->getVoidPtr()); + for(axom::IndexType i = 0; i < cellCount; ++i) + { + values[i] = 1.; + } + + klee::ShapeSet shapeSet; + quest::SamplingShaper shaper(axom::runtime_policy::Policy::seq, + axom::policyToDefaultAllocatorID(axom::runtime_policy::Policy::seq), + shapeSet, + meshGroup, + "mesh"); + shaper.setSamplingResolution(3); + shaper.setQuadratureType(quadrature); + + auto* bpState = shaper.getBlueprintState(); + ASSERT_NE(bpState, nullptr); + + std::map initialVolumeFractions; + initialVolumeFractions["background"] = &bpState->getField(backgroundVolFracName); + EXPECT_NO_THROW(shaper.importInitialVolumeFractions(initialVolumeFractions)); + + conduit::Node refreshedMesh; + meshGroup->createNativeLayout(refreshedMesh); + EXPECT_TRUE(refreshedMesh.has_path("coordsets/quadrature_points")); + EXPECT_TRUE(refreshedMesh.has_path("topologies/quadrature_points")); + EXPECT_TRUE(refreshedMesh.has_path("fields/quadratureWeights/values")); + } +} + int main(int argc, char* argv[]) { axom::utilities::raii::MPIWrapper mpi_raii_wrapper(argc, argv); From 45dc46573a1709d4b0c0bda5712f8ca91c018d68 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 25 Jun 2026 18:22:35 -0700 Subject: [PATCH 67/72] Switch to using helper functions instead of looking for vol_frac_ string. Also move some times and add a new one. --- src/axom/quest/examples/shaping_driver.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/axom/quest/examples/shaping_driver.cpp b/src/axom/quest/examples/shaping_driver.cpp index 5ebb180114..69bb020681 100644 --- a/src/axom/quest/examples/shaping_driver.cpp +++ b/src/axom/quest/examples/shaping_driver.cpp @@ -1073,6 +1073,7 @@ int main(int argc, char** argv) SLIC_INFO(axom::fmt::format("{:=^80}", "Generating volume fraction fields for materials")); shaper->adjustVolumeFractions(); + AXOM_ANNOTATE_END("adjust"); //--------------------------------------------------------------------------- // Compute and print volumes of each material's volume fraction @@ -1093,7 +1094,6 @@ int main(int argc, char** argv) printSummaryMFEM(shaper); } #endif - AXOM_ANNOTATE_END("adjust"); //--------------------------------------------------------------------------- // Save meshes and fields @@ -1192,14 +1192,14 @@ void printSummaryBlueprint(axom::quest::SamplingShaper* shaper) const auto measure = axom::bump::utilities::make_array_view(n_fields.fetch_existing("measure/values")); - // Compute the volumes for all of the "vol_frac_" fields. + // Compute the volumes for all material volume-fraction fields. for(conduit::index_t i = 0; i < n_fields.number_of_children(); i++) { conduit::Node& n_field = n_fields[i]; const std::string name = n_field.name(); - if(axom::utilities::string::startsWith(name, "vol_frac_")) + if(quest::shaping::isVolumeFractionFieldName(name)) { - const auto mat_name = name.substr(9); + const auto mat_name = quest::shaping::materialNameFromVolumeFractionFieldName(name); const auto values = axom::bump::utilities::make_array_view(n_field.fetch_existing("values")); @@ -1226,11 +1226,13 @@ void printSummaryBlueprint(axom::quest::SamplingShaper* shaper) */ void printSummaryMFEM(axom::quest::Shaper* shaper) { + AXOM_ANNOTATE_SCOPE("printSummaryMFEM"); + for(auto& kv : shaper->getDC()->GetFieldMap()) { - if(axom::utilities::string::startsWith(kv.first, "vol_frac_")) + if(quest::shaping::isVolumeFractionFieldName(kv.first)) { - const auto mat_name = kv.first.substr(9); + const auto mat_name = quest::shaping::materialNameFromVolumeFractionFieldName(kv.first); auto* gf = kv.second; mfem::ConstantCoefficient one(1.0); From 9ea68e64de38078c2eedcb058faeeb91da4fedf2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 9 Jul 2026 11:42:01 -0700 Subject: [PATCH 68/72] Simplified code, fixed a comment. --- src/axom/quest/Shaper.cpp | 35 ----------------------------------- src/axom/quest/Shaper.hpp | 2 +- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/src/axom/quest/Shaper.cpp b/src/axom/quest/Shaper.cpp index d87433da97..d27984a5c4 100644 --- a/src/axom/quest/Shaper.cpp +++ b/src/axom/quest/Shaper.cpp @@ -33,25 +33,6 @@ namespace axom namespace quest { -#if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) -namespace -{ -bool mpiIsActive() -{ - int initialized = 0; - MPI_Initialized(&initialized); - if(!initialized) - { - return false; - } - - int finalized = 0; - MPI_Finalized(&finalized); - return finalized == 0; -} -} // namespace -#endif - // These were needed for linking - but why? They are constexpr. constexpr int Shaper::DEFAULT_SAMPLES_PER_KNOT_SPAN; constexpr double Shaper::MINIMUM_PERCENT_ERROR; @@ -371,10 +352,6 @@ void Shaper::saveResults(bool AXOM_UNUSED_PARAM(extra)) int Shaper::getRank() const { #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) - if(!mpiIsActive()) - { - return 0; - } int rank = -1; MPI_Comm_rank(m_comm, &rank); return rank; @@ -385,10 +362,6 @@ int Shaper::getRank() const double Shaper::allReduceSum(double val) const { #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) - if(!mpiIsActive()) - { - return val; - } double global; MPI_Allreduce(&val, &global, 1, MPI_DOUBLE, MPI_SUM, m_comm); return global; @@ -400,10 +373,6 @@ double Shaper::allReduceSum(double val) const double Shaper::allReduceMin(double val) const { #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) - if(!mpiIsActive()) - { - return val; - } double global; MPI_Allreduce(&val, &global, 1, MPI_DOUBLE, MPI_MIN, m_comm); return global; @@ -415,10 +384,6 @@ double Shaper::allReduceMin(double val) const double Shaper::allReduceMax(double val) const { #if defined(AXOM_USE_MPI) && defined(MFEM_USE_MPI) - if(!mpiIsActive()) - { - return val; - } double global; MPI_Allreduce(&val, &global, 1, MPI_DOUBLE, MPI_MAX, m_comm); return global; diff --git a/src/axom/quest/Shaper.hpp b/src/axom/quest/Shaper.hpp index d189e538c6..373a0b1730 100644 --- a/src/axom/quest/Shaper.hpp +++ b/src/axom/quest/Shaper.hpp @@ -99,7 +99,7 @@ class Shaper /// Refinement type. using RefinementType = DiscreteShape::RefinementType; - //! @brief Verify the input mesh is okay for this backend to work with. + //! @brief Verify the input mesh is okay for this class to work with. bool verifyInputMesh(std::string& whyBad) const; ///@{ From fde13402eae177aa089a63593bbb9eb390af39d4 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 14 Jul 2026 10:23:20 -0700 Subject: [PATCH 69/72] Convert new headers to pragma once --- src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp | 5 +---- src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp index 9308602d30..c669081e4b 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_blueprint.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_SHAPING_HELPERS_BLUEPRINT__HPP_ -#define AXOM_QUEST_SHAPING_HELPERS_BLUEPRINT__HPP_ +#pragma once #include "shaping_helpers.hpp" @@ -504,5 +503,3 @@ void sampleInOutField(const std::string& shapeName, } // end namespace axom #endif // defined(AXOM_USE_CONDUIT) - -#endif // AXOM_QUEST_SHAPING_HELPERS_BLUEPRINT__HPP_ diff --git a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp index 6caff6dd88..a3d766410d 100644 --- a/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp +++ b/src/axom/quest/detail/shaping/shaping_helpers_mfem.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_QUEST_SHAPING_HELPERS_MFEM__HPP_ -#define AXOM_QUEST_SHAPING_HELPERS_MFEM__HPP_ +#pragma once #include "shaping_helpers.hpp" @@ -454,5 +453,3 @@ void FCT_correct(const double* M, } // end namespace axom #endif // defined(AXOM_USE_MFEM) - -#endif // AXOM_QUEST_SHAPING_HELPERS_MFEM__HPP_ From d8a9215a2e2907dafa446d2a90005e466a3200e2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 14 Jul 2026 10:30:35 -0700 Subject: [PATCH 70/72] Convert some newer headers to pragma once --- src/axom/bump/ComputeMeasure.hpp | 5 +---- src/axom/bump/GenerateQuadratureMesh.hpp | 5 +---- src/axom/bump/MakeExplicitCoordset.hpp | 4 +--- src/axom/bump/MappedZoneUtilities.hpp | 5 +---- 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/axom/bump/ComputeMeasure.hpp b/src/axom/bump/ComputeMeasure.hpp index 07480433f0..496d681545 100644 --- a/src/axom/bump/ComputeMeasure.hpp +++ b/src/axom/bump/ComputeMeasure.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_COMPUTE_MEASURE_HPP_ -#define AXOM_BUMP_COMPUTE_MEASURE_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -108,5 +107,3 @@ class ComputeMeasure } // end namespace bump } // end namespace axom - -#endif diff --git a/src/axom/bump/GenerateQuadratureMesh.hpp b/src/axom/bump/GenerateQuadratureMesh.hpp index 85cc470477..85cd50b043 100644 --- a/src/axom/bump/GenerateQuadratureMesh.hpp +++ b/src/axom/bump/GenerateQuadratureMesh.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_GENERATE_QUADRATURE_MESH_HPP_ -#define AXOM_BUMP_GENERATE_QUADRATURE_MESH_HPP_ +#pragma once #include "axom/config.hpp" @@ -292,5 +291,3 @@ class GenerateQuadratureMesh } // namespace bump } // namespace axom - -#endif diff --git a/src/axom/bump/MakeExplicitCoordset.hpp b/src/axom/bump/MakeExplicitCoordset.hpp index afa5725035..cb8353ba9f 100644 --- a/src/axom/bump/MakeExplicitCoordset.hpp +++ b/src/axom/bump/MakeExplicitCoordset.hpp @@ -3,8 +3,7 @@ // files for dates and other details. // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_MAKE_EXPLICIT_COORDSET_HPP_ -#define AXOM_BUMP_MAKE_EXPLICIT_COORDSET_HPP_ +#pragma once #include "axom/core.hpp" #include "axom/bump/views/NodeArrayView.hpp" @@ -110,4 +109,3 @@ class MakeExplicitCoordset } // end namespace bump } // end namespace axom -#endif diff --git a/src/axom/bump/MappedZoneUtilities.hpp b/src/axom/bump/MappedZoneUtilities.hpp index 73b2c6106f..e58101549e 100644 --- a/src/axom/bump/MappedZoneUtilities.hpp +++ b/src/axom/bump/MappedZoneUtilities.hpp @@ -4,8 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef AXOM_BUMP_MAPPED_ZONE_UTILITIES_HPP_ -#define AXOM_BUMP_MAPPED_ZONE_UTILITIES_HPP_ +#pragma once #include "axom/config.hpp" #include "axom/core.hpp" @@ -229,5 +228,3 @@ AXOM_HOST_DEVICE double computePhysicalMeasureFactor(const ShapeType& zone, } // namespace detail } // namespace bump } // namespace axom - -#endif From bcf268bdb371168556e5a741590a492839d021e6 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 15 Jul 2026 16:07:33 -0700 Subject: [PATCH 71/72] Updated comments. Moved code into get_exact_degree helper function. --- src/axom/core/numerics/quadrature.cpp | 37 +++++++++++++++ src/axom/core/numerics/quadrature.hpp | 12 +++++ src/axom/core/tests/numerics_quadrature.hpp | 51 +++++++++++++++------ 3 files changed, 85 insertions(+), 15 deletions(-) diff --git a/src/axom/core/numerics/quadrature.cpp b/src/axom/core/numerics/quadrature.cpp index b361dedb39..630f368ab6 100644 --- a/src/axom/core/numerics/quadrature.cpp +++ b/src/axom/core/numerics/quadrature.cpp @@ -91,6 +91,19 @@ RuleStorage& get_cached_rule_storage(int npts, return it->second; } +/*! + * \brief Computes quadrature weights for an interpolatory rule on `[0, 1]` + * from its nodes. + * + * \param [in] nodes The interpolation nodes that define the Lagrange basis + * \param [out] weights The quadrature weights corresponding to `nodes` + * \param [in] allocatorID The allocator used for temporary storage and the + * output `weights` array + * + * The returned weights integrate the Lagrange basis associated with `nodes` + * by evaluating those basis polynomials with a Gauss-Legendre rule that is + * exact for degree `npts - 1`. + */ void compute_interpolatory_weights(const axom::Array& nodes, axom::Array& weights, int allocatorID) @@ -481,6 +494,30 @@ QuadratureRule get_quadrature_rule(QuadratureType quadratureType, int npts, int return get_gauss_legendre(npts, allocatorID); } +int get_exact_degree(QuadratureType quadratureType, int npts) +{ + assert("Quadrature rules must have >= 1 point" && (npts >= 1)); + assert("Invalid Axom quadrature type." && + is_valid_quadrature_type(static_cast(quadratureType))); + + switch(quadratureType) + { + case QuadratureType::Invalid: + case QuadratureType::GaussLegendre: + return 2 * npts - 1; + case QuadratureType::GaussLobatto: + return npts == 1 ? 1 : 2 * npts - 3; + case QuadratureType::OpenUniform: + case QuadratureType::ClosedUniform: + case QuadratureType::OpenHalfUniform: + case QuadratureType::ClosedGL: + return npts - 1 + npts % 2; + } + + assert("Unhandled Axom quadrature type." && false); + return 2 * npts - 1; +} + QuadratureRule get_open_uniform(int npts, int allocatorID) { assert("Quadrature rules must have >= 1 point" && (npts >= 1)); diff --git a/src/axom/core/numerics/quadrature.hpp b/src/axom/core/numerics/quadrature.hpp index b8cb82ba34..01e3502c33 100644 --- a/src/axom/core/numerics/quadrature.hpp +++ b/src/axom/core/numerics/quadrature.hpp @@ -160,6 +160,18 @@ QuadratureRule get_quadrature_rule(QuadratureType quadratureType, int npts, int allocatorID = axom::getDefaultAllocatorID()); +/*! + * \brief Returns the highest polynomial degree integrated exactly by a 1D + * quadrature family with `npts` points. + * + * \param [in] quadratureType The quadrature family to query. + * \param [in] npts The number of quadrature points in the rule. + * + * \note `QuadratureType::Invalid` follows Axom's default rule, which is + * currently Gauss-Legendre. + */ +int get_exact_degree(QuadratureType quadratureType, int npts); + /*! * \brief Computes a 1D quadrature rule of open uniform Newton-Cotes points. * diff --git a/src/axom/core/tests/numerics_quadrature.hpp b/src/axom/core/tests/numerics_quadrature.hpp index 7fe4728297..5ac06874a3 100644 --- a/src/axom/core/tests/numerics_quadrature.hpp +++ b/src/axom/core/tests/numerics_quadrature.hpp @@ -318,37 +318,58 @@ TEST(numerics_quadrature, quadrature_type_dispatch) EXPECT_DOUBLE_EQ(rule.node(3), 1.0); } +TEST(numerics_quadrature, exact_degree_by_type) +{ + using axom::numerics::QuadratureType; + + EXPECT_EQ(axom::numerics::get_exact_degree(QuadratureType::Invalid, 3), 5); + EXPECT_EQ(axom::numerics::get_exact_degree(QuadratureType::GaussLegendre, 3), 5); + EXPECT_EQ(axom::numerics::get_exact_degree(QuadratureType::GaussLobatto, 1), 1); + EXPECT_EQ(axom::numerics::get_exact_degree(QuadratureType::GaussLobatto, 4), 5); + EXPECT_EQ(axom::numerics::get_exact_degree(QuadratureType::OpenUniform, 5), 5); + EXPECT_EQ(axom::numerics::get_exact_degree(QuadratureType::ClosedUniform, 4), 3); + EXPECT_EQ(axom::numerics::get_exact_degree(QuadratureType::OpenHalfUniform, 5), 5); + EXPECT_EQ(axom::numerics::get_exact_degree(QuadratureType::ClosedGL, 4), 3); +} + TEST(numerics_quadrature, open_uniform_exactness) { - check_polynomial_exactness([](int npts) { return axom::numerics::get_open_uniform(npts); }, - [](int npts) { return npts - 1 + npts % 2; }, - 10); + check_polynomial_exactness( + [](int npts) { return axom::numerics::get_open_uniform(npts); }, + [](int npts) { return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::OpenUniform, npts); }, + 10); } TEST(numerics_quadrature, closed_uniform_exactness) { - check_polynomial_exactness([](int npts) { return axom::numerics::get_closed_uniform(npts); }, - [](int npts) { return npts - 1 + npts % 2; }, - 10); + check_polynomial_exactness( + [](int npts) { return axom::numerics::get_closed_uniform(npts); }, + [](int npts) { return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::ClosedUniform, npts); }, + 10); } TEST(numerics_quadrature, gauss_lobatto_exactness) { - check_polynomial_exactness([](int npts) { return axom::numerics::get_gauss_lobatto(npts); }, - [](int npts) { return npts == 1 ? 1 : 2 * npts - 3; }, - 10); + check_polynomial_exactness( + [](int npts) { return axom::numerics::get_gauss_lobatto(npts); }, + [](int npts) { return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::GaussLobatto, npts); }, + 10); } TEST(numerics_quadrature, open_half_uniform_exactness) { - check_polynomial_exactness([](int npts) { return axom::numerics::get_open_half_uniform(npts); }, - [](int npts) { return npts - 1 + npts % 2; }, - 10); + check_polynomial_exactness( + [](int npts) { return axom::numerics::get_open_half_uniform(npts); }, + [](int npts) { + return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::OpenHalfUniform, npts); + }, + 10); } TEST(numerics_quadrature, closed_gl_exactness) { - check_polynomial_exactness([](int npts) { return axom::numerics::get_closed_gl(npts); }, - [](int npts) { return npts - 1 + npts % 2; }, - 10); + check_polynomial_exactness( + [](int npts) { return axom::numerics::get_closed_gl(npts); }, + [](int npts) { return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::ClosedGL, npts); }, + 10); } From 2e57df41f2616793819bac4ed25fe663be77b9f0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 15 Jul 2026 16:08:04 -0700 Subject: [PATCH 72/72] make style --- src/axom/core/tests/numerics_quadrature.hpp | 16 ++++++++++++---- src/axom/quest/SamplingShaper.cpp | 3 +-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/axom/core/tests/numerics_quadrature.hpp b/src/axom/core/tests/numerics_quadrature.hpp index 5ac06874a3..26c7818531 100644 --- a/src/axom/core/tests/numerics_quadrature.hpp +++ b/src/axom/core/tests/numerics_quadrature.hpp @@ -336,7 +336,9 @@ TEST(numerics_quadrature, open_uniform_exactness) { check_polynomial_exactness( [](int npts) { return axom::numerics::get_open_uniform(npts); }, - [](int npts) { return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::OpenUniform, npts); }, + [](int npts) { + return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::OpenUniform, npts); + }, 10); } @@ -344,7 +346,9 @@ TEST(numerics_quadrature, closed_uniform_exactness) { check_polynomial_exactness( [](int npts) { return axom::numerics::get_closed_uniform(npts); }, - [](int npts) { return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::ClosedUniform, npts); }, + [](int npts) { + return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::ClosedUniform, npts); + }, 10); } @@ -352,7 +356,9 @@ TEST(numerics_quadrature, gauss_lobatto_exactness) { check_polynomial_exactness( [](int npts) { return axom::numerics::get_gauss_lobatto(npts); }, - [](int npts) { return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::GaussLobatto, npts); }, + [](int npts) { + return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::GaussLobatto, npts); + }, 10); } @@ -370,6 +376,8 @@ TEST(numerics_quadrature, closed_gl_exactness) { check_polynomial_exactness( [](int npts) { return axom::numerics::get_closed_gl(npts); }, - [](int npts) { return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::ClosedGL, npts); }, + [](int npts) { + return axom::numerics::get_exact_degree(axom::numerics::QuadratureType::ClosedGL, npts); + }, 10); } diff --git a/src/axom/quest/SamplingShaper.cpp b/src/axom/quest/SamplingShaper.cpp index 2017f2ecae..183f1a9c44 100644 --- a/src/axom/quest/SamplingShaper.cpp +++ b/src/axom/quest/SamplingShaper.cpp @@ -324,8 +324,7 @@ void SamplingShaper::prepareShapeQuery(klee::Dimensions shapeDimension, const kl SLIC_INFO(axom::fmt::format("After welding, surface mesh has {} vertices and {} elements.", nVerts, nCells)); - mint::write_vtk(m_surfaceMesh.get(), - axom::fmt::format("melded_shape_mesh_{}.vtk", shapeName)); + mint::write_vtk(m_surfaceMesh.get(), axom::fmt::format("melded_shape_mesh_{}.vtk", shapeName)); } else if(!m_contours.empty()) {