Skip to content

Enhance ElviraAlgorithm so it can output plane offset and point mesh. #1570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
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
7 changes: 7 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/
- The ``axom::mir::ElviraAlgorithm`` class, which performs material interface reconstruction using
the ELVIRA algorithm, was enhanced so it supports 3D structured mesh inputs. The output mesh is a
Blueprint mesh with a 3D unstructured polyhedral topology.
- The ``axom::mir::ElviraAlgorithm`` class, was enhanced to accept a "plane" option that causes it
to return clipping plane origin and normal as fields on the mesh.
- The ``axom::mir::ElviraAlgorithm`` class, was enhanced to accept a "pointmesh" option that causes it
to return a mesh consisting of points located at clipping plane origins for each clipped material
fragment, instead of returning polygonal or polyhedral meshes. This option is off by default.
- Adds ``axom::mir::utilities::blueprint::MakePolyhedralTopology`` class that takes an input Blueprint
topology and turns it into a polyhedral topology. The mesh will contain duplicate faces, which can
later be merged.
Expand All @@ -36,6 +41,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/
into the revised coordset, as well as a map of old point indices to new point indices, which can
be used to revise fields.
- Exposed primal clip operations for clipping various shapes with a plane.
- Adds ``axom::mir::utilities::blueprint::MakePointMesh`` class that creates a new Blueprint mesh
consisting of points located at the zone centers of the input mesh.

### Changed
- Fixed `Timer::elapsed*()` methods so they properly report the sum of all start/stop cycles
Expand Down
1 change: 1 addition & 0 deletions src/axom/mir/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ set(mir_headers
utilities/ExtrudeMesh.hpp
utilities/FieldBlender.hpp
utilities/FieldSlicer.hpp
utilities/MakePointMesh.hpp
utilities/MakePolyhedralTopology.hpp
utilities/MakeZoneCenters.hpp
utilities/MakeZoneVolumes.hpp
Expand Down
140 changes: 94 additions & 46 deletions src/axom/mir/ElviraAlgorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#include "axom/slic.hpp"

#include "axom/mir/MIRAlgorithm.hpp"
#include "axom/mir/utilities/MakePointMesh.hpp"
#include "axom/mir/utilities/MakeZoneCenters.hpp"
#include "axom/mir/utilities/MatsetSlicer.hpp"
#include "axom/mir/utilities/MergeMeshes.hpp"
#include "axom/mir/utilities/PrimalAdaptor.hpp"
#include "axom/mir/utilities/SelectedZones.hpp"
Expand Down Expand Up @@ -191,7 +193,13 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm

// Make the clean mesh.
conduit::Node n_cleanOutput;
makeCleanOutput(n_root, n_topo.name(), n_options_copy, cleanZones.view(), n_cleanOutput);
makeCleanZones(cleanZones.view(),
n_root,
n_root_topo,
n_root_coordset,
n_root_matset,
n_options_copy,
n_cleanOutput);

// Process the mixed part of the mesh.
processMixedZones(mixedZones.view(),
Expand Down Expand Up @@ -262,11 +270,8 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm
}

// Add an originalElements array.
const std::string originalElementsField(MIROptions(n_options).originalElementsField());
addOriginal(n_newFields[originalElementsField],
n_topo.name(),
"element",
m_topologyView.numberOfZones());
const std::string originalElementsField(ELVIRAOptions(n_options).originalElementsField());
addOriginal(n_newFields[originalElementsField], n_topo.name(), "element", cleanZones);
}
}

Expand Down Expand Up @@ -316,18 +321,20 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm
* \param n_field The new field node.
* \param topoName The topology name for the field.
* \param association The field association.
* \param nvalues The number of nodes in the field.
* \param selectedZonesView A view containing the values to store in the field.
*
*/
void addOriginal(conduit::Node &n_field,
const std::string &topoName,
const std::string &association,
axom::IndexType nvalues) const
axom::ArrayView<axom::IndexType> selectedZonesView) const
{
AXOM_ANNOTATE_SCOPE("addOriginal");
namespace bputils = axom::mir::utilities::blueprint;
bputils::ConduitAllocateThroughAxom<ExecSpace> c2a;

const auto nvalues = selectedZonesView.size();

// Add a new field for the original ids.
n_field["topology"] = topoName;
n_field["association"] = association;
Expand All @@ -336,55 +343,85 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm
auto view = bputils::make_array_view<ConnectivityType>(n_field["values"]);
axom::for_all<ExecSpace>(
nvalues,
AXOM_LAMBDA(axom::IndexType index) { view[index] = static_cast<ConnectivityType>(index); });
AXOM_LAMBDA(axom::IndexType index) { view[index] = selectedZonesView[index]; });
}

/*!
* \brief Take the mesh in n_root and extract the zones identified by the
* \a cleanZones array and store the results into the \a n_cleanOutput
* node.
*
* \param cleanZones An array of clean zone ids.
* \param n_root The input mesh from which zones are being extracted.
* \param topoName The name of the topology.
* \param n_topology The node that contains the topology for the input mesh.
* \param n_coordset The node that contains the coordset for the input mesh.
* \param n_matset The node that contains the matset for the input mesh.
* \param n_options The options to inherit.
* \param cleanZones An array of clean zone ids.
* \param[out] n_cleanOutput The node that will contain the clean mesh output.
*
* \return The number of nodes in the clean mesh output.
*/
void makeCleanOutput(const conduit::Node &n_root,
const std::string &topoName,
const conduit::Node &n_options,
const axom::ArrayView<axom::IndexType> &cleanZones,
conduit::Node &n_cleanOutput) const
void makeCleanZones(const axom::ArrayView<axom::IndexType> &cleanZones,
const conduit::Node &n_root,
const conduit::Node &n_topology,
const conduit::Node &n_coordset,
const conduit::Node &n_matset,
const conduit::Node &n_options,
conduit::Node &n_cleanOutput) const
{
AXOM_ANNOTATE_SCOPE("makeCleanOutput");

// Make options for the ExtractZones* algorithms.
conduit::Node n_ezopts;
n_ezopts["topology"] = topoName;
n_ezopts["compact"] = 1;
n_ezopts["originalElementsField"] = Options(n_options).originalElementsField();
// Forward some options involved in naming the objects.
const std::vector<std::string> keys {"topologyName", "coordsetName", "matsetName"};
for(const auto &key : keys)
AXOM_ANNOTATE_SCOPE("makeCleanZones");
namespace bputils = axom::mir::utilities::blueprint;

// Make the clean mesh (it might be a point mesh).
ELVIRAOptions opts(n_options);
if(opts.pointmesh())
{
if(n_options.has_path(key))
// _mir_utilities_makepointmesh_begin
// Make a point mesh of the selected zones.
bputils::MakePointMesh<ExecSpace, TopologyView, CoordsetView> pm(m_topologyView,
m_coordsetView);
pm.execute(cleanZones, n_topology, n_coordset, n_options, n_cleanOutput);
// _mir_utilities_makepointmesh_end

// Slice the input material.
bputils::MatsetSlicer<ExecSpace, MatsetView> mslicer(m_matsetView);
bputils::SliceData slice;
slice.m_indicesView = cleanZones;
mslicer.execute(slice, n_matset, n_cleanOutput["matsets/" + opts.matsetName(n_matset.name())]);

// Add an originalElements array.
addOriginal(n_cleanOutput["fields/" + opts.originalElementsField()],
n_topology.name(),
"element",
cleanZones);
}
else
{
// Make options for the ExtractZones* algorithms.
conduit::Node n_ezopts;
n_ezopts["topology"] = n_topology.name();
n_ezopts["compact"] = 1;
n_ezopts["originalElementsField"] = opts.originalElementsField();
// Forward some options involved in naming the objects.
const std::vector<std::string> keys {"topologyName", "coordsetName", "matsetName"};
for(const auto &key : keys)
{
n_ezopts[key].set(n_options[key]);
if(n_options.has_path(key))
{
n_ezopts[key].set(n_options[key]);
}
}
}

// Make the clean mesh.
using CleanOutput =
detail::MakeCleanOutput<ExecSpace, TopologyView, CoordsetView, MatsetView, IndexPolicy::dimension()>;
CleanOutput::execute(cleanZones,
n_root,
n_ezopts,
m_topologyView,
m_coordsetView,
m_matsetView,
n_cleanOutput);
using MakeCleanZones =
detail::MakeCleanZones<ExecSpace, TopologyView, CoordsetView, MatsetView, IndexPolicy::dimension()>;
MakeCleanZones::execute(cleanZones,
n_root,
n_ezopts,
m_topologyView,
m_coordsetView,
m_matsetView,
n_cleanOutput);
}

#if defined(AXOM_ELVIRA_DEBUG)
{
Expand Down Expand Up @@ -812,7 +849,7 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm
}

/*!
* \brief Use the normal vectors for each fragment to bulid the fragment shapes.
* \brief Use the normal vectors for each fragment to build the fragment shapes.
*
* \param buildView A view that lets us add a shape to the Blueprint output.
* \param matZoneView A view containing zone ids sorted by material count in the zone.
Expand Down Expand Up @@ -859,6 +896,9 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm
#if defined(AXOM_ELVIRA_DEBUG_MAKE_FRAGMENTS) && !defined(AXOM_DEVICE_CODE)
SLIC_DEBUG("makeFragments: zoneIndex=" << zoneIndex << ", matCount=" << matCount);
#endif
PointType pt {};
VectorType normal {};
double planeOffset;
for(axom::IndexType m = 0; m < matCount - 1; m++)
{
const auto fragmentIndex = offset + m;
Expand All @@ -874,15 +914,13 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm
const auto matVolume = zoneVol * fragmentVFStencilView[si];

// Make the normal
VectorType normal;
for(int d = 0; d < NDIMS; d++)
{
normal[d] = static_cast<CoordType>(normalPtr[d]);
}

ClipResultType clippedShape;
PointType range[2];
PointType pt {};

if(m == 0)
{
Expand Down Expand Up @@ -924,11 +962,14 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm
pt);
}

// Make clipping plane for remaining fragment.
const auto P = PlaneType(normal, pt, false);
planeOffset = P.getOffset();

// Emit clippedShape as material matId
buildView.addShape(zoneIndex, fragmentIndex, clippedShape, matId, normalPtr);
buildView.addShape(zoneIndex, fragmentIndex, clippedShape, matId, pt, planeOffset, normalPtr);

// Clip in the other direction to get the remaining fragment for the next material.
const auto P = PlaneType(normal, pt, false);
if(m == 0)
{
#if defined(AXOM_ELVIRA_DEBUG_MAKE_FRAGMENTS) && !defined(AXOM_DEVICE_CODE)
Expand All @@ -954,8 +995,15 @@ class ElviraAlgorithm : public axom::mir::MIRAlgorithm
// Emit the last leftover fragment.
const auto fragmentIndex = offset + matCount - 1;
const auto matId = sortedMaterialIdsView[fragmentIndex];
const double *normalPtr = fragmentVectorsView.data() + (fragmentIndex * numVectorComponents);
buildView.addShape(zoneIndex, fragmentIndex, remaining, matId, normalPtr);
// The last fragment's normals are just (1,0,0). These are accessible at
// fragmentVectorsView[fragmentIndex * numVectorComponents] but it seems
// more useful to emit the opposite of the last fragment's normal instead.
double lastNormal[NDIMS];
for(int d = 0; d < NDIMS; d++)
{
lastNormal[d] = -normal[d];
}
buildView.addShape(zoneIndex, fragmentIndex, remaining, matId, pt, -planeOffset, lastNormal);
});
}

Expand Down
14 changes: 7 additions & 7 deletions src/axom/mir/EquiZAlgorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ class EquiZAlgorithm : public axom::mir::MIRAlgorithm

// Make the clean mesh.
conduit::Node n_cleanOutput;
makeCleanOutput(n_root, n_topo.name(), n_options_copy, cleanZones.view(), n_cleanOutput);
makeCleanZones(n_root, n_topo.name(), n_options_copy, cleanZones.view(), n_cleanOutput);

// Add an original nodes field on the root mesh.
addOriginal(n_root_fields[originalNodesFieldName()],
Expand Down Expand Up @@ -596,13 +596,13 @@ class EquiZAlgorithm : public axom::mir::MIRAlgorithm
*
* \return The number of nodes in the clean mesh output.
*/
void makeCleanOutput(const conduit::Node &n_root,
const std::string &topoName,
const conduit::Node &n_options,
const axom::ArrayView<axom::IndexType> &cleanZones,
conduit::Node &n_cleanOutput) const
void makeCleanZones(const conduit::Node &n_root,
const std::string &topoName,
const conduit::Node &n_options,
const axom::ArrayView<axom::IndexType> &cleanZones,
conduit::Node &n_cleanOutput) const
{
AXOM_ANNOTATE_SCOPE("makeCleanOutput");
AXOM_ANNOTATE_SCOPE("makeCleanZones");
namespace bputils = axom::mir::utilities::blueprint;

// Make the clean mesh. Set compact=0 so it does not change the number of nodes.
Expand Down
6 changes: 3 additions & 3 deletions src/axom/mir/MIRAlgorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class MIRAlgorithm
options:
topology: main
matset: matset
new_topology: mirtopo
new_coordset: mircoords
new_matset: cleanmat
topologyName: mirtopo
coordsetName: mircoords
matsetName: cleanmat
fields:
- temperature
- pressure
Expand Down
2 changes: 1 addition & 1 deletion src/axom/mir/MIROptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class MIROptions : public axom::mir::Options
return name;
}

private:
protected:
/// Access the base class' options.
const conduit::Node &options() const { return this->m_options; }
};
Expand Down
Loading