Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
66a889c
Initial mesh clipper refactoring.
gunney1 Sep 24, 2025
2f0b0dc
Merge remote-tracking branch 'gh/feature/gunney/extend-klee-geometry'…
gunney1 Sep 24, 2025
9273714
Merge remote-tracking branch 'gh/develop' into feature/gunney/initial…
gunney1 Oct 1, 2025
07fd4e9
Adjust to removed template parameters in methoc clip.
gunney1 Oct 1, 2025
eff080d
Merge remote-tracking branch 'gh/develop' into feature/gunney/initial…
gunney1 Oct 2, 2025
30ffbd1
Fix misspelled macro.
gunney1 Oct 3, 2025
a0c2ed5
Same symbol for number of tets per hex across mesh clipping codes.
gunney1 Oct 3, 2025
f07c028
Comment corrections and clarifications from code review.
gunney1 Oct 3, 2025
6a42566
Remove obsolete code in TetClipper.
gunney1 Oct 3, 2025
2bd1559
Comment changes from code review.
gunney1 Oct 3, 2025
962c4b0
Make zero-threshold user settable.
gunney1 Oct 3, 2025
b4f68b4
Corrections and suggestions from code review.
gunney1 Oct 31, 2025
98ae346
Clarify remapTetIndices documentation.
gunney1 Oct 31, 2025
fb151ac
A few more corrections and code review suggestions.
gunney1 Oct 31, 2025
92750c7
Merge remote-tracking branch 'gh/develop' into feature/gunney/initial…
gunney1 Oct 31, 2025
02f7c01
Advance tioga CI LLVM device compiler to version 6.4.2.
gunney1 Nov 4, 2025
0495721
Revert "Advance tioga CI LLVM device compiler to version 6.4.2."
gunney1 Nov 4, 2025
43501b7
Work around a probable AMD GPU 6.3 compiler bug.
gunney1 Nov 4, 2025
88d9466
Fix minor error in output.
gunney1 Nov 1, 2025
7973827
Add guards for non-sidre builds.
gunney1 Nov 5, 2025
96c030c
Various fixes and suggestions from code review.
gunney1 Nov 5, 2025
905579d
Change LabelType from constexpr to enum.
gunney1 Nov 5, 2025
6ebef6f
Fix comment.
gunney1 Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/axom/core/NumericArray.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class NumericArray // NOLINT
* array. If the size is not the same as the size of this array, this
* behaves the same way as the constructor which takes a pointer and size.
*/
AXOM_HOST_DEVICE
NumericArray(std::initializer_list<T> values)
: NumericArray {values.begin(), static_cast<int>(values.size())}
{ }
Expand Down
8 changes: 7 additions & 1 deletion src/axom/primal/operators/detail/clip_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,13 @@ AXOM_HOST_DEVICE void poly_clip_vertices(Polyhedron<T, NDIMS>& poly,
intersect(plane, seg, lerp_val);

int newVertexIndex = poly.addVertex(seg.at(lerp_val));
SLIC_ASSERT(newVertexIndex == expectedVertexIndex);
// A probable AMD GPU 6.3 compiler bug caused the first assert to fail.
// The second assert is equivalent because we expect addVertex call to
// consume the next index and increment the number of vertices.
// However, when the compilers no longar cause false failures, we should
// restore the first assert.
// SLIC_ASSERT(newVertexIndex == expectedVertexIndex);
SLIC_ASSERT(newVertexIndex == poly.numVertices() - 1);

poly.addNeighbors(newVertexIndex, {i, neighborIndex});

Expand Down
19 changes: 15 additions & 4 deletions src/axom/quest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,22 @@ if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE)
list(APPEND quest_headers Shaper.hpp
DiscreteShape.hpp
IntersectionShaper.hpp
detail/shaping/shaping_helpers.hpp)
detail/shaping/shaping_helpers.hpp
ShapeMesh.hpp)
list(APPEND quest_sources Shaper.cpp
DiscreteShape.cpp
detail/shaping/shaping_helpers.cpp)
list(APPEND quest_depends_on klee)
DiscreteShape.cpp
detail/shaping/shaping_helpers.cpp
ShapeMesh.cpp)
list(APPEND quest_depends_on klee)
if(RAJA_FOUND)
list(APPEND quest_headers MeshClipper.hpp
MeshClipperStrategy.hpp
detail/clipping/TetClipper.hpp
detail/clipping/MeshClipperImpl.hpp)
list(APPEND quest_sources MeshClipper.cpp
detail/clipping/TetClipper.cpp
MeshClipperStrategy.cpp)
endif()
endif()

if(MFEM_FOUND AND AXOM_ENABLE_MFEM_SIDRE_DATACOLLECTION)
Expand Down
250 changes: 250 additions & 0 deletions src/axom/quest/MeshClipper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and
// other Axom Project Developers. See the top-level LICENSE file for details.
//
// SPDX-License-Identifier: (BSD-3-Clause)

#include "axom/config.hpp"

#include "axom/quest/MeshClipper.hpp"
#include "axom/quest/detail/clipping/MeshClipperImpl.hpp"
#include "axom/core/execution/execution_space.hpp"
#include "axom/core/execution/runtime_policy.hpp"
#include "axom/slic/interface/slic_macros.hpp"
#include "axom/fmt.hpp"

namespace axom
{
namespace quest
{
namespace experimental
{

MeshClipper::MeshClipper(quest::experimental::ShapeMesh& shapeMesh,
const std::shared_ptr<quest::experimental::MeshClipperStrategy>& strategy)
: m_shapeMesh(shapeMesh)
, m_strategy(strategy)
, m_impl(newImpl())
, m_verbose(false)
{ }

void MeshClipper::clip(axom::Array<double>& ovlap)
{
const int allocId = m_shapeMesh.getAllocatorID();
const axom::IndexType cellCount = m_shapeMesh.getCellCount();

// Resize output array and use appropriate allocator.
if(ovlap.size() < cellCount || ovlap.getAllocatorID() != allocId)
{
AXOM_ANNOTATE_SCOPE("MeshClipper::clip_alloc");
ovlap = axom::Array<double>(ArrayOptions::Uninitialized(), cellCount, cellCount, allocId);
}
clip(ovlap.view());
}

/**
* @brief Orchestrates the geometry clipping by using the capabilities of the
* MeshClipperStrategy implementation.
*
* If the strategy can label cells as inside/on/outside geometry
* boundary, use that to reduce reliance on expensive clipping methods.
*
* Regardless of labeling, try to use specialized clipping first.
* If specialized methods aren't implemented, resort to discretizing
* geometry into tets or octs for clipping against mesh cells.
*/
void MeshClipper::clip(axom::ArrayView<double> ovlap)
{
const int allocId = m_shapeMesh.getAllocatorID();
const axom::IndexType cellCount = m_shapeMesh.getCellCount();
SLIC_ASSERT(ovlap.size() == cellCount);

// Try to label cells as inside, outside or on shape boundary
axom::Array<LabelType> cellLabels;
bool withCellInOut = m_strategy->labelCellsInOut(m_shapeMesh, cellLabels);

bool done = false;

if(withCellInOut)
{
SLIC_ERROR_IF(
cellLabels.size() != m_shapeMesh.getCellCount(),
axom::fmt::format("MeshClipperStrategy '{}' did not return the correct array size of {}",
m_strategy->name(),
m_shapeMesh.getCellCount()));
SLIC_ERROR_IF(cellLabels.getAllocatorID() != allocId,
axom::fmt::format("MeshClipperStrategy '{}' failed to provide cellLabels data "
"with the required allocator id {}",
m_strategy->name(),
allocId));

if(m_verbose)
{
logLabelStats(cellLabels, "cells");
}

AXOM_ANNOTATE_BEGIN("MeshClipper::processInOut");

m_impl->initVolumeOverlaps(cellLabels.view(), ovlap);

axom::Array<axom::IndexType> cellsOnBdry;
m_impl->collectOnIndices(cellLabels.view(), cellsOnBdry);

axom::Array<LabelType> tetLabels;
bool withTetInOut = m_strategy->labelTetsInOut(m_shapeMesh, cellsOnBdry.view(), tetLabels);

axom::Array<axom::IndexType> tetsOnBdry;

if(withTetInOut)
{
if(m_verbose)
{
logLabelStats(tetLabels, "tets");
}
m_impl->collectOnIndices(tetLabels.view(), tetsOnBdry);
m_impl->remapTetIndices(tetsOnBdry, cellsOnBdry);

SLIC_ASSERT(tetsOnBdry.getAllocatorID() == m_shapeMesh.getAllocatorID());
SLIC_ASSERT(tetsOnBdry.size() <= cellsOnBdry.size() * NUM_TETS_PER_HEX);

m_impl->addVolumesOfInteriorTets(cellsOnBdry.view(), tetLabels.view(), ovlap);
}

AXOM_ANNOTATE_END("MeshClipper::processInOut");

//
// If implementation has a specialized clip, use it.
//
if(withTetInOut)
{
done = m_strategy->specializedClipTets(m_shapeMesh, ovlap, tetsOnBdry);
}
else
{
done = m_strategy->specializedClipCells(m_shapeMesh, ovlap, cellsOnBdry);
}

if(!done)
{
AXOM_ANNOTATE_SCOPE("MeshClipper::clip3D_limited");
if(withTetInOut)
{
m_impl->computeClipVolumes3DTets(tetsOnBdry.view(), ovlap);
}
else
{
m_impl->computeClipVolumes3D(cellsOnBdry.view(), ovlap);
}
}

m_localCellInCount = cellsOnBdry.size();
}
else // !withCellInOut
{
std::string msg =
axom::fmt::format("MeshClipper strategy '{}' did not provide in/out cell labels.\n",
m_strategy->name());
SLIC_INFO(msg);
m_impl->zeroVolumeOverlaps(ovlap);
done = m_strategy->specializedClipCells(m_shapeMesh, ovlap);

if(!done)
{
AXOM_ANNOTATE_SCOPE("MeshClipper::clip3D");
m_impl->computeClipVolumes3D(ovlap);
}

m_localCellInCount = cellCount;
}
}

/*!
* @brief Allocate an Impl for the execution-space computations
* of this clipper.
*/
std::unique_ptr<MeshClipper::Impl> MeshClipper::newImpl()
{
using RuntimePolicy = axom::runtime_policy::Policy;

auto runtimePolicy = m_shapeMesh.getRuntimePolicy();

std::unique_ptr<Impl> impl;
if(runtimePolicy == RuntimePolicy::seq)
{
impl.reset(new detail::MeshClipperImpl<axom::SEQ_EXEC>(*this));
}
#ifdef AXOM_RUNTIME_POLICY_USE_OPENMP
else if(runtimePolicy == RuntimePolicy::omp)
{
impl.reset(new detail::MeshClipperImpl<axom::OMP_EXEC>(*this));
}
#endif
#ifdef AXOM_RUNTIME_POLICY_USE_CUDA
else if(runtimePolicy == RuntimePolicy::cuda)
{
impl.reset(new detail::MeshClipperImpl<axom::CUDA_EXEC<256>>(*this));
}
#endif
#ifdef AXOM_RUNTIME_POLICY_USE_HIP
else if(runtimePolicy == RuntimePolicy::hip)
{
impl.reset(new detail::MeshClipperImpl<axom::HIP_EXEC<256>>(*this));
}
#endif
else
{
SLIC_ERROR(axom::fmt::format("MeshClipper has no impl for runtime policy {}", runtimePolicy));
}
return impl;
}

void MeshClipper::logLabelStats(axom::ArrayView<const LabelType> labels, const std::string& labelType)
{
axom::IndexType countsa[4];
axom::IndexType countsb[4];
getLabelCounts(labels, countsa[0], countsa[1], countsa[2]);
countsa[3] = m_shapeMesh.getCellCount();
#ifdef AXOM_USE_MPI
MPI_Reduce(countsa, countsb, 4, axom::mpi_traits<axom::IndexType>::type, MPI_SUM, 0, MPI_COMM_WORLD);
#endif
std::string msg = axom::fmt::format(
"MeshClipper strategy '{}' globally labeled {} {} inside, {} on and {} outside, for mesh with "
"{} cells ({} tets)\n",
m_strategy->name(),
labelType,
countsb[0],
countsb[1],
countsb[2],
countsb[3],
countsb[3] * NUM_TETS_PER_HEX);
SLIC_INFO(msg);
}

void MeshClipper::getClippingStats(axom::IndexType& localCellInCount,
axom::IndexType& globalCellInCount,
axom::IndexType& maxLocalCellInCount) const
{
localCellInCount = m_localCellInCount;
#ifdef AXOM_USE_MPI
MPI_Reduce(&localCellInCount,
&globalCellInCount,
1,
axom::mpi_traits<axom::IndexType>::type,
MPI_SUM,
0,
MPI_COMM_WORLD);
MPI_Reduce(&localCellInCount,
&maxLocalCellInCount,
1,
axom::mpi_traits<axom::IndexType>::type,
MPI_MAX,
0,
MPI_COMM_WORLD);
#else
maxLocalCellInCount = localCellInCount;
globalCellInCount = localCellInCount;
#endif
}

} // namespace experimental
} // end namespace quest
} // end namespace axom
Loading